diff --git a/lib/app/app.dart b/lib/app/app.dart index 6db28e1..10fc195 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -35,7 +35,6 @@ import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view import 'package:yimaru_app/ui/views/learn_practice/learn_practice_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'; -import 'package:yimaru_app/ui/views/course_category/course_category_view.dart'; import 'package:yimaru_app/ui/views/failure/failure_view.dart'; import 'package:yimaru_app/ui/views/course_lesson/course_lesson_view.dart'; import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart'; @@ -43,7 +42,6 @@ import 'package:yimaru_app/services/notification_service.dart'; import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart'; import 'package:yimaru_app/services/smart_auth_service.dart'; import 'package:yimaru_app/services/course_service.dart'; -import 'package:yimaru_app/ui/views/course_subcategory/course_subcategory_view.dart'; import 'package:yimaru_app/ui/views/course/course_view.dart'; import 'package:yimaru_app/services/audio_player_service.dart'; import 'package:yimaru_app/services/voice_recorder_service.dart'; @@ -55,6 +53,11 @@ import 'package:yimaru_app/ui/views/assessment/assessment_view.dart'; import 'package:yimaru_app/services/vimeo_service.dart'; import 'package:yimaru_app/services/url_launcher_service.dart'; import 'package:yimaru_app/services/phone_caller_service.dart'; +import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart'; +import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart'; +import 'package:yimaru_app/services/learn_service.dart'; +import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart'; +import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart'; // @stacked-import @StackedApp( @@ -83,17 +86,19 @@ import 'package:yimaru_app/services/phone_caller_service.dart'; MaterialRoute(page: LearnPracticeView), MaterialRoute(page: CoursePracticeView), MaterialRoute(page: CoursePaymentView), - MaterialRoute(page: CourseCategoryView), MaterialRoute(page: FailureView), MaterialRoute(page: CourseLessonView), MaterialRoute(page: CourseLessonDetailView), MaterialRoute(page: DuolingoView), - MaterialRoute(page: CourseSubcategoryView), MaterialRoute(page: CourseView), MaterialRoute(page: CoursePracticeQuestionView), MaterialRoute(page: LearnProgramView), MaterialRoute(page: LearnCourseView), MaterialRoute(page: AssessmentView), + MaterialRoute(page: LearnSubscriptionView), + MaterialRoute(page: ArifPayView), + MaterialRoute(page: CourseCatalogView), + MaterialRoute(page: CourseUnitView), // @stacked-route ], dependencies: [ @@ -118,6 +123,7 @@ import 'package:yimaru_app/services/phone_caller_service.dart'; LazySingleton(classType: VimeoService), LazySingleton(classType: UrlLauncherService), LazySingleton(classType: PhoneCallerService), + LazySingleton(classType: LearnService), // @stacked-service ], bottomsheets: [ diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart index d770977..a1330b4 100644 --- a/lib/app/app.locator.dart +++ b/lib/app/app.locator.dart @@ -21,6 +21,7 @@ import '../services/google_auth_service.dart'; import '../services/image_downloader_service.dart'; import '../services/image_picker_service.dart'; import '../services/in_app_update_service.dart'; +import '../services/learn_service.dart'; import '../services/notification_service.dart'; import '../services/permission_handler_service.dart'; import '../services/phone_caller_service.dart'; @@ -61,4 +62,5 @@ Future setupLocator( locator.registerLazySingleton(() => VimeoService()); locator.registerLazySingleton(() => UrlLauncherService()); locator.registerLazySingleton(() => PhoneCallerService()); + locator.registerLazySingleton(() => LearnService()); } diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index 04c7c04..367b249 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -6,47 +6,45 @@ // ************************************************************************** // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:flutter/material.dart' as _i37; +import 'package:flutter/material.dart' as _i39; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart' as _i1; import 'package:stacked_services/stacked_services.dart' as _i46; -import 'package:yimaru_app/models/category.dart' as _i44; -import 'package:yimaru_app/models/course.dart' as _i42; -import 'package:yimaru_app/models/course_lesson.dart' as _i43; -import 'package:yimaru_app/models/learn_course.dart' as _i38; -import 'package:yimaru_app/models/learn_lesson.dart' as _i40; -import 'package:yimaru_app/models/learn_module.dart' as _i39; -import 'package:yimaru_app/models/subcategory.dart' as _i45; -import 'package:yimaru_app/ui/common/enmus.dart' as _i41; +import 'package:yimaru_app/models/course.dart' as _i44; +import 'package:yimaru_app/models/course_lesson.dart' as _i45; +import 'package:yimaru_app/models/learn_course.dart' as _i40; +import 'package:yimaru_app/models/learn_lesson.dart' as _i42; +import 'package:yimaru_app/models/learn_module.dart' as _i41; +import 'package:yimaru_app/ui/common/enmus.dart' as _i43; import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart' as _i9; -import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i36; +import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i36; +import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i34; import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' as _i12; -import 'package:yimaru_app/ui/views/course/course_view.dart' as _i32; -import 'package:yimaru_app/ui/views/course_category/course_category_view.dart' - as _i26; +import 'package:yimaru_app/ui/views/course/course_view.dart' as _i30; +import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart' + as _i37; import 'package:yimaru_app/ui/views/course_lesson/course_lesson_view.dart' - as _i28; + as _i27; import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart' - as _i29; + as _i28; import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart' as _i25; import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart' as _i24; import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart' - as _i33; -import 'package:yimaru_app/ui/views/course_subcategory/course_subcategory_view.dart' as _i31; +import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart' as _i38; import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; -import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i30; -import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i27; +import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i29; +import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i26; import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart' as _i21; import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2; import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13; import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart' - as _i35; + as _i33; import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart' as _i20; import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart' @@ -56,7 +54,9 @@ import 'package:yimaru_app/ui/views/learn_module/learn_module_view.dart' import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart' as _i23; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart' - as _i34; + as _i32; +import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart' + as _i35; import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3; import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart' @@ -123,8 +123,6 @@ class Routes { static const coursePaymentView = '/course-payment-view'; - static const courseCategoryView = '/course-category-view'; - static const failureView = '/failure-view'; static const courseLessonView = '/course-lesson-view'; @@ -133,8 +131,6 @@ class Routes { static const duolingoView = '/duolingo-view'; - static const courseSubcategoryView = '/course-subcategory-view'; - static const courseView = '/course-view'; static const coursePracticeQuestionView = '/course-practice-question-view'; @@ -145,6 +141,14 @@ class Routes { static const assessmentView = '/assessment-view'; + static const learnSubscriptionView = '/learn-subscription-view'; + + static const arifPayView = '/arif-pay-view'; + + static const courseCatalogView = '/course-catalog-view'; + + static const courseUnitView = '/course-unit-view'; + static const all = { homeView, onboardingView, @@ -170,17 +174,19 @@ class Routes { learnPracticeView, coursePracticeView, coursePaymentView, - courseCategoryView, failureView, courseLessonView, courseLessonDetailView, duolingoView, - courseSubcategoryView, courseView, coursePracticeQuestionView, learnProgramView, learnCourseView, assessmentView, + learnSubscriptionView, + arifPayView, + courseCatalogView, + courseUnitView, }; } @@ -282,49 +288,57 @@ class StackedRouter extends _i1.RouterBase { Routes.coursePaymentView, page: _i25.CoursePaymentView, ), - _i1.RouteDef( - Routes.courseCategoryView, - page: _i26.CourseCategoryView, - ), _i1.RouteDef( Routes.failureView, - page: _i27.FailureView, + page: _i26.FailureView, ), _i1.RouteDef( Routes.courseLessonView, - page: _i28.CourseLessonView, + page: _i27.CourseLessonView, ), _i1.RouteDef( Routes.courseLessonDetailView, - page: _i29.CourseLessonDetailView, + page: _i28.CourseLessonDetailView, ), _i1.RouteDef( Routes.duolingoView, - page: _i30.DuolingoView, - ), - _i1.RouteDef( - Routes.courseSubcategoryView, - page: _i31.CourseSubcategoryView, + page: _i29.DuolingoView, ), _i1.RouteDef( Routes.courseView, - page: _i32.CourseView, + page: _i30.CourseView, ), _i1.RouteDef( Routes.coursePracticeQuestionView, - page: _i33.CoursePracticeQuestionView, + page: _i31.CoursePracticeQuestionView, ), _i1.RouteDef( Routes.learnProgramView, - page: _i34.LearnProgramView, + page: _i32.LearnProgramView, ), _i1.RouteDef( Routes.learnCourseView, - page: _i35.LearnCourseView, + page: _i33.LearnCourseView, ), _i1.RouteDef( Routes.assessmentView, - page: _i36.AssessmentView, + page: _i34.AssessmentView, + ), + _i1.RouteDef( + Routes.learnSubscriptionView, + page: _i35.LearnSubscriptionView, + ), + _i1.RouteDef( + Routes.arifPayView, + page: _i36.ArifPayView, + ), + _i1.RouteDef( + Routes.courseCatalogView, + page: _i37.CourseCatalogView, + ), + _i1.RouteDef( + Routes.courseUnitView, + page: _i38.CourseUnitView, ), ]; @@ -333,7 +347,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const HomeViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i2.HomeView(key: args.key), settings: data, ); @@ -342,7 +356,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const OnboardingViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i3.OnboardingView(key: args.key), settings: data, ); @@ -351,7 +365,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const StartupViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i4.StartupView(key: args.key, label: args.label), settings: data, ); @@ -360,7 +374,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProfileViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i5.ProfileView(key: args.key), settings: data, ); @@ -369,7 +383,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProfileDetailViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i6.ProfileDetailView(key: args.key), settings: data, ); @@ -378,7 +392,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const DownloadsViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i7.DownloadsView(key: args.key), settings: data, ); @@ -387,7 +401,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProgressViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i8.ProgressView(key: args.key), settings: data, ); @@ -396,7 +410,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const AccountPrivacyViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i9.AccountPrivacyView(key: args.key), settings: data, ); @@ -405,7 +419,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const SupportViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i10.SupportView(key: args.key), settings: data, ); @@ -414,7 +428,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const TelegramSupportViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i11.TelegramSupportView(key: args.key), settings: data, ); @@ -423,7 +437,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const CallSupportViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i12.CallSupportView(key: args.key), settings: data, ); @@ -432,7 +446,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LanguageViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i13.LanguageView(key: args.key), settings: data, ); @@ -441,7 +455,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const PrivacyPolicyViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i14.PrivacyPolicyView(key: args.key), settings: data, ); @@ -450,7 +464,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const TermsAndConditionsViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i15.TermsAndConditionsView(key: args.key), settings: data, ); @@ -459,7 +473,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const RegisterViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i16.RegisterView(key: args.key), settings: data, ); @@ -468,14 +482,14 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LoginViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i17.LoginView(key: args.key), settings: data, ); }, _i18.LearnModuleView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i18.LearnModuleView(key: args.key, course: args.course), settings: data, @@ -485,14 +499,14 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const WelcomeViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i19.WelcomeView(key: args.key), settings: data, ); }, _i20.LearnLessonView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i20.LearnLessonView(key: args.key, module: args.module), settings: data, @@ -502,22 +516,25 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ForgetPasswordViewArguments(), ); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i21.ForgetPasswordView(key: args.key), settings: data, ); }, _i22.LearnLessonDetailView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i22.LearnLessonDetailView( - key: args.key, lesson: args.lesson, hasPractice: args.hasPractice), + key: args.key, + lesson: args.lesson, + module: args.module, + hasPractice: args.hasPractice), settings: data, ); }, _i23.LearnPracticeView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i23.LearnPracticeView( key: args.key, level: args.level, @@ -531,7 +548,7 @@ class StackedRouter extends _i1.RouterBase { }, _i24.CoursePracticeView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i24.CoursePracticeView(key: args.key, id: args.id), settings: data, @@ -539,100 +556,119 @@ class StackedRouter extends _i1.RouterBase { }, _i25.CoursePaymentView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => _i25.CoursePaymentView(key: args.key, course: args.course), settings: data, ); }, - _i26.CourseCategoryView: (data) { - final args = data.getArgs( - orElse: () => const CourseCategoryViewArguments(), - ); - return _i37.MaterialPageRoute( - builder: (context) => _i26.CourseCategoryView(key: args.key), - settings: data, - ); - }, - _i27.FailureView: (data) { + _i26.FailureView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( - builder: (context) => _i27.FailureView( + return _i39.MaterialPageRoute( + builder: (context) => _i26.FailureView( key: args.key, onTap: args.onTap, label: args.label), settings: data, ); }, - _i28.CourseLessonView: (data) { + _i27.CourseLessonView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => - _i28.CourseLessonView(key: args.key, course: args.course), + _i27.CourseLessonView(key: args.key, course: args.course), settings: data, ); }, - _i29.CourseLessonDetailView: (data) { + _i28.CourseLessonDetailView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => - _i29.CourseLessonDetailView(key: args.key, lesson: args.lesson), + _i28.CourseLessonDetailView(key: args.key, lesson: args.lesson), settings: data, ); }, - _i30.DuolingoView: (data) { + _i29.DuolingoView: (data) { final args = data.getArgs( orElse: () => const DuolingoViewArguments(), ); - return _i37.MaterialPageRoute( - builder: (context) => _i30.DuolingoView(key: args.key), + return _i39.MaterialPageRoute( + builder: (context) => _i29.DuolingoView(key: args.key), settings: data, ); }, - _i31.CourseSubcategoryView: (data) { - final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( - builder: (context) => - _i31.CourseSubcategoryView(key: args.key, category: args.category), + _i30.CourseView: (data) { + final args = data.getArgs( + orElse: () => const CourseViewArguments(), + ); + return _i39.MaterialPageRoute( + builder: (context) => _i30.CourseView(key: args.key), settings: data, ); }, - _i32.CourseView: (data) { - final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( - builder: (context) => - _i32.CourseView(key: args.key, subcategory: args.subcategory), - settings: data, - ); - }, - _i33.CoursePracticeQuestionView: (data) { + _i31.CoursePracticeQuestionView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => - _i33.CoursePracticeQuestionView(key: args.key, id: args.id), + _i31.CoursePracticeQuestionView(key: args.key, id: args.id), settings: data, ); }, - _i34.LearnProgramView: (data) { + _i32.LearnProgramView: (data) { final args = data.getArgs( orElse: () => const LearnProgramViewArguments(), ); - return _i37.MaterialPageRoute( - builder: (context) => _i34.LearnProgramView(key: args.key), + return _i39.MaterialPageRoute( + builder: (context) => _i32.LearnProgramView(key: args.key), settings: data, ); }, - _i35.LearnCourseView: (data) { + _i33.LearnCourseView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( - builder: (context) => _i35.LearnCourseView(key: args.key, id: args.id), + return _i39.MaterialPageRoute( + builder: (context) => _i33.LearnCourseView(key: args.key, id: args.id), settings: data, ); }, - _i36.AssessmentView: (data) { + _i34.AssessmentView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i39.MaterialPageRoute( builder: (context) => - _i36.AssessmentView(key: args.key, data: args.data), + _i34.AssessmentView(key: args.key, data: args.data), + settings: data, + ); + }, + _i35.LearnSubscriptionView: (data) { + final args = data.getArgs( + orElse: () => const LearnSubscriptionViewArguments(), + ); + return _i39.MaterialPageRoute( + builder: (context) => _i35.LearnSubscriptionView(key: args.key), + settings: data, + ); + }, + _i36.ArifPayView: (data) { + final args = data.getArgs(nullOk: false); + return _i39.MaterialPageRoute( + builder: (context) => + _i36.ArifPayView(key: args.key, phone: args.phone), + settings: data, + ); + }, + _i37.CourseCatalogView: (data) { + final args = data.getArgs( + orElse: () => const CourseCatalogViewArguments(), + ); + return _i39.MaterialPageRoute( + builder: (context) => _i37.CourseCatalogView(key: args.key), + settings: data, + ); + }, + _i38.CourseUnitView: (data) { + final args = data.getArgs( + orElse: () => const CourseUnitViewArguments(), + ); + return _i39.MaterialPageRoute( + builder: (context) => _i38.CourseUnitView(key: args.key), settings: data, ); }, @@ -648,7 +684,7 @@ class StackedRouter extends _i1.RouterBase { class HomeViewArguments { const HomeViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -670,7 +706,7 @@ class HomeViewArguments { class OnboardingViewArguments { const OnboardingViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -695,7 +731,7 @@ class StartupViewArguments { this.label = 'Loading', }); - final _i37.Key? key; + final _i39.Key? key; final String label; @@ -719,7 +755,7 @@ class StartupViewArguments { class ProfileViewArguments { const ProfileViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -741,7 +777,7 @@ class ProfileViewArguments { class ProfileDetailViewArguments { const ProfileDetailViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -763,7 +799,7 @@ class ProfileDetailViewArguments { class DownloadsViewArguments { const DownloadsViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -785,7 +821,7 @@ class DownloadsViewArguments { class ProgressViewArguments { const ProgressViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -807,7 +843,7 @@ class ProgressViewArguments { class AccountPrivacyViewArguments { const AccountPrivacyViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -829,7 +865,7 @@ class AccountPrivacyViewArguments { class SupportViewArguments { const SupportViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -851,7 +887,7 @@ class SupportViewArguments { class TelegramSupportViewArguments { const TelegramSupportViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -873,7 +909,7 @@ class TelegramSupportViewArguments { class CallSupportViewArguments { const CallSupportViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -895,7 +931,7 @@ class CallSupportViewArguments { class LanguageViewArguments { const LanguageViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -917,7 +953,7 @@ class LanguageViewArguments { class PrivacyPolicyViewArguments { const PrivacyPolicyViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -939,7 +975,7 @@ class PrivacyPolicyViewArguments { class TermsAndConditionsViewArguments { const TermsAndConditionsViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -961,7 +997,7 @@ class TermsAndConditionsViewArguments { class RegisterViewArguments { const RegisterViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -983,7 +1019,7 @@ class RegisterViewArguments { class LoginViewArguments { const LoginViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -1008,9 +1044,9 @@ class LearnModuleViewArguments { required this.course, }); - final _i37.Key? key; + final _i39.Key? key; - final _i38.LearnCourse course; + final _i40.LearnCourse course; @override String toString() { @@ -1032,7 +1068,7 @@ class LearnModuleViewArguments { class WelcomeViewArguments { const WelcomeViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -1057,9 +1093,9 @@ class LearnLessonViewArguments { required this.module, }); - final _i37.Key? key; + final _i39.Key? key; - final _i39.LearnModule module; + final _i41.LearnModule module; @override String toString() { @@ -1081,7 +1117,7 @@ class LearnLessonViewArguments { class ForgetPasswordViewArguments { const ForgetPasswordViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -1104,18 +1140,21 @@ class LearnLessonDetailViewArguments { const LearnLessonDetailViewArguments({ this.key, required this.lesson, + required this.module, required this.hasPractice, }); - final _i37.Key? key; + final _i39.Key? key; - final _i40.LearnLesson lesson; + final _i42.LearnLesson lesson; + + final _i41.LearnModule module; final bool hasPractice; @override String toString() { - return '{"key": "$key", "lesson": "$lesson", "hasPractice": "$hasPractice"}'; + return '{"key": "$key", "lesson": "$lesson", "module": "$module", "hasPractice": "$hasPractice"}'; } @override @@ -1123,12 +1162,16 @@ class LearnLessonDetailViewArguments { if (identical(this, other)) return true; return other.key == key && other.lesson == lesson && + other.module == module && other.hasPractice == hasPractice; } @override int get hashCode { - return key.hashCode ^ lesson.hashCode ^ hasPractice.hashCode; + return key.hashCode ^ + lesson.hashCode ^ + module.hashCode ^ + hasPractice.hashCode; } } @@ -1143,7 +1186,7 @@ class LearnPracticeViewArguments { required this.subtitle, }); - final _i37.Key? key; + final _i39.Key? key; final String? level; @@ -1153,7 +1196,7 @@ class LearnPracticeViewArguments { final String title; - final _i41.LearnPractices practice; + final _i43.LearnPractices practice; final String subtitle; @@ -1192,7 +1235,7 @@ class CoursePracticeViewArguments { required this.id, }); - final _i37.Key? key; + final _i39.Key? key; final int id; @@ -1219,9 +1262,9 @@ class CoursePaymentViewArguments { required this.course, }); - final _i37.Key? key; + final _i39.Key? key; - final _i42.Course course; + final _i44.Course course; @override String toString() { @@ -1240,28 +1283,6 @@ class CoursePaymentViewArguments { } } -class CourseCategoryViewArguments { - const CourseCategoryViewArguments({this.key}); - - final _i37.Key? key; - - @override - String toString() { - return '{"key": "$key"}'; - } - - @override - bool operator ==(covariant CourseCategoryViewArguments other) { - if (identical(this, other)) return true; - return other.key == key; - } - - @override - int get hashCode { - return key.hashCode; - } -} - class FailureViewArguments { const FailureViewArguments({ this.key, @@ -1269,7 +1290,7 @@ class FailureViewArguments { required this.label, }); - final _i37.Key? key; + final _i39.Key? key; final void Function() onTap; @@ -1298,9 +1319,9 @@ class CourseLessonViewArguments { required this.course, }); - final _i37.Key? key; + final _i39.Key? key; - final _i42.Course course; + final _i44.Course course; @override String toString() { @@ -1325,9 +1346,9 @@ class CourseLessonDetailViewArguments { required this.lesson, }); - final _i37.Key? key; + final _i39.Key? key; - final _i43.CourseLesson lesson; + final _i45.CourseLesson lesson; @override String toString() { @@ -1349,7 +1370,7 @@ class CourseLessonDetailViewArguments { class DuolingoViewArguments { const DuolingoViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -1368,57 +1389,25 @@ class DuolingoViewArguments { } } -class CourseSubcategoryViewArguments { - const CourseSubcategoryViewArguments({ - this.key, - required this.category, - }); - - final _i37.Key? key; - - final _i44.Category category; - - @override - String toString() { - return '{"key": "$key", "category": "$category"}'; - } - - @override - bool operator ==(covariant CourseSubcategoryViewArguments other) { - if (identical(this, other)) return true; - return other.key == key && other.category == category; - } - - @override - int get hashCode { - return key.hashCode ^ category.hashCode; - } -} - class CourseViewArguments { - const CourseViewArguments({ - this.key, - required this.subcategory, - }); + const CourseViewArguments({this.key}); - final _i37.Key? key; - - final _i45.Subcategory subcategory; + final _i39.Key? key; @override String toString() { - return '{"key": "$key", "subcategory": "$subcategory"}'; + return '{"key": "$key"}'; } @override bool operator ==(covariant CourseViewArguments other) { if (identical(this, other)) return true; - return other.key == key && other.subcategory == subcategory; + return other.key == key; } @override int get hashCode { - return key.hashCode ^ subcategory.hashCode; + return key.hashCode; } } @@ -1428,7 +1417,7 @@ class CoursePracticeQuestionViewArguments { required this.id, }); - final _i37.Key? key; + final _i39.Key? key; final int id; @@ -1452,7 +1441,7 @@ class CoursePracticeQuestionViewArguments { class LearnProgramViewArguments { const LearnProgramViewArguments({this.key}); - final _i37.Key? key; + final _i39.Key? key; @override String toString() { @@ -1477,7 +1466,7 @@ class LearnCourseViewArguments { required this.id, }); - final _i37.Key? key; + final _i39.Key? key; final int id; @@ -1504,7 +1493,7 @@ class AssessmentViewArguments { required this.data, }); - final _i37.Key? key; + final _i39.Key? key; final Map data; @@ -1525,9 +1514,102 @@ class AssessmentViewArguments { } } +class LearnSubscriptionViewArguments { + const LearnSubscriptionViewArguments({this.key}); + + final _i39.Key? key; + + @override + String toString() { + return '{"key": "$key"}'; + } + + @override + bool operator ==(covariant LearnSubscriptionViewArguments other) { + if (identical(this, other)) return true; + return other.key == key; + } + + @override + int get hashCode { + return key.hashCode; + } +} + +class ArifPayViewArguments { + const ArifPayViewArguments({ + this.key, + required this.phone, + }); + + final _i39.Key? key; + + final String phone; + + @override + String toString() { + return '{"key": "$key", "phone": "$phone"}'; + } + + @override + bool operator ==(covariant ArifPayViewArguments other) { + if (identical(this, other)) return true; + return other.key == key && other.phone == phone; + } + + @override + int get hashCode { + return key.hashCode ^ phone.hashCode; + } +} + +class CourseCatalogViewArguments { + const CourseCatalogViewArguments({this.key}); + + final _i39.Key? key; + + @override + String toString() { + return '{"key": "$key"}'; + } + + @override + bool operator ==(covariant CourseCatalogViewArguments other) { + if (identical(this, other)) return true; + return other.key == key; + } + + @override + int get hashCode { + return key.hashCode; + } +} + +class CourseUnitViewArguments { + const CourseUnitViewArguments({this.key}); + + final _i39.Key? key; + + @override + String toString() { + return '{"key": "$key"}'; + } + + @override + bool operator ==(covariant CourseUnitViewArguments other) { + if (identical(this, other)) return true; + return other.key == key; + } + + @override + int get hashCode { + return key.hashCode; + } +} + extension NavigatorStateExtension on _i46.NavigationService { Future navigateToHomeView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1543,7 +1625,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToOnboardingView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1559,7 +1641,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToStartupView({ - _i37.Key? key, + _i39.Key? key, String label = 'Loading', int? routerId, bool preventDuplicates = true, @@ -1576,7 +1658,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToProfileView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1592,7 +1674,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToProfileDetailView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1608,7 +1690,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToDownloadsView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1624,7 +1706,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToProgressView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1640,7 +1722,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToAccountPrivacyView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1656,7 +1738,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToSupportView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1672,7 +1754,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToTelegramSupportView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1688,7 +1770,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToCallSupportView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1704,7 +1786,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLanguageView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1720,7 +1802,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToPrivacyPolicyView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1736,7 +1818,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToTermsAndConditionsView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1752,7 +1834,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToRegisterView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1768,7 +1850,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLoginView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1784,8 +1866,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLearnModuleView({ - _i37.Key? key, - required _i38.LearnCourse course, + _i39.Key? key, + required _i40.LearnCourse course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1801,7 +1883,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToWelcomeView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1817,8 +1899,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLearnLessonView({ - _i37.Key? key, - required _i39.LearnModule module, + _i39.Key? key, + required _i41.LearnModule module, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1834,7 +1916,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToForgetPasswordView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1850,8 +1932,9 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLearnLessonDetailView({ - _i37.Key? key, - required _i40.LearnLesson lesson, + _i39.Key? key, + required _i42.LearnLesson lesson, + required _i41.LearnModule module, required bool hasPractice, int? routerId, bool preventDuplicates = true, @@ -1861,7 +1944,7 @@ extension NavigatorStateExtension on _i46.NavigationService { }) async { return navigateTo(Routes.learnLessonDetailView, arguments: LearnLessonDetailViewArguments( - key: key, lesson: lesson, hasPractice: hasPractice), + key: key, lesson: lesson, module: module, hasPractice: hasPractice), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -1869,12 +1952,12 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLearnPracticeView({ - _i37.Key? key, + _i39.Key? key, String? level, required int id, required String label, required String title, - required _i41.LearnPractices practice, + required _i43.LearnPractices practice, required String subtitle, int? routerId, bool preventDuplicates = true, @@ -1898,7 +1981,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToCoursePracticeView({ - _i37.Key? key, + _i39.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -1915,8 +1998,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToCoursePaymentView({ - _i37.Key? key, - required _i42.Course course, + _i39.Key? key, + required _i44.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1931,24 +2014,8 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - Future navigateToCourseCategoryView({ - _i37.Key? key, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo(Routes.courseCategoryView, - arguments: CourseCategoryViewArguments(key: key), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToFailureView({ - _i37.Key? key, + _i39.Key? key, required void Function() onTap, required String label, int? routerId, @@ -1966,8 +2033,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToCourseLessonView({ - _i37.Key? key, - required _i42.Course course, + _i39.Key? key, + required _i44.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1983,8 +2050,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToCourseLessonDetailView({ - _i37.Key? key, - required _i43.CourseLesson lesson, + _i39.Key? key, + required _i45.CourseLesson lesson, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2000,7 +2067,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToDuolingoView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2015,26 +2082,8 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - Future navigateToCourseSubcategoryView({ - _i37.Key? key, - required _i44.Category category, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo(Routes.courseSubcategoryView, - arguments: CourseSubcategoryViewArguments(key: key, category: category), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToCourseView({ - _i37.Key? key, - required _i45.Subcategory subcategory, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2042,7 +2091,7 @@ extension NavigatorStateExtension on _i46.NavigationService { transition, }) async { return navigateTo(Routes.courseView, - arguments: CourseViewArguments(key: key, subcategory: subcategory), + arguments: CourseViewArguments(key: key), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -2050,7 +2099,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToCoursePracticeQuestionView({ - _i37.Key? key, + _i39.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -2067,7 +2116,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLearnProgramView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2083,7 +2132,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToLearnCourseView({ - _i37.Key? key, + _i39.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -2100,7 +2149,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future navigateToAssessmentView({ - _i37.Key? key, + _i39.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -2116,8 +2165,73 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } + Future navigateToLearnSubscriptionView({ + _i39.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.learnSubscriptionView, + arguments: LearnSubscriptionViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToArifPayView({ + _i39.Key? key, + required String phone, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.arifPayView, + arguments: ArifPayViewArguments(key: key, phone: phone), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToCourseCatalogView({ + _i39.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.courseCatalogView, + arguments: CourseCatalogViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToCourseUnitView({ + _i39.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.courseUnitView, + arguments: CourseUnitViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + Future replaceWithHomeView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2133,7 +2247,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithOnboardingView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2149,7 +2263,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithStartupView({ - _i37.Key? key, + _i39.Key? key, String label = 'Loading', int? routerId, bool preventDuplicates = true, @@ -2166,7 +2280,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithProfileView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2182,7 +2296,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithProfileDetailView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2198,7 +2312,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithDownloadsView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2214,7 +2328,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithProgressView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2230,7 +2344,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithAccountPrivacyView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2246,7 +2360,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithSupportView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2262,7 +2376,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithTelegramSupportView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2278,7 +2392,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithCallSupportView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2294,7 +2408,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLanguageView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2310,7 +2424,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithPrivacyPolicyView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2326,7 +2440,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithTermsAndConditionsView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2342,7 +2456,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithRegisterView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2358,7 +2472,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLoginView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2374,8 +2488,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLearnModuleView({ - _i37.Key? key, - required _i38.LearnCourse course, + _i39.Key? key, + required _i40.LearnCourse course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2391,7 +2505,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithWelcomeView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2407,8 +2521,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLearnLessonView({ - _i37.Key? key, - required _i39.LearnModule module, + _i39.Key? key, + required _i41.LearnModule module, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2424,7 +2538,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithForgetPasswordView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2440,8 +2554,9 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLearnLessonDetailView({ - _i37.Key? key, - required _i40.LearnLesson lesson, + _i39.Key? key, + required _i42.LearnLesson lesson, + required _i41.LearnModule module, required bool hasPractice, int? routerId, bool preventDuplicates = true, @@ -2451,7 +2566,7 @@ extension NavigatorStateExtension on _i46.NavigationService { }) async { return replaceWith(Routes.learnLessonDetailView, arguments: LearnLessonDetailViewArguments( - key: key, lesson: lesson, hasPractice: hasPractice), + key: key, lesson: lesson, module: module, hasPractice: hasPractice), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -2459,12 +2574,12 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLearnPracticeView({ - _i37.Key? key, + _i39.Key? key, String? level, required int id, required String label, required String title, - required _i41.LearnPractices practice, + required _i43.LearnPractices practice, required String subtitle, int? routerId, bool preventDuplicates = true, @@ -2488,7 +2603,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithCoursePracticeView({ - _i37.Key? key, + _i39.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -2505,8 +2620,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithCoursePaymentView({ - _i37.Key? key, - required _i42.Course course, + _i39.Key? key, + required _i44.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2521,24 +2636,8 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - Future replaceWithCourseCategoryView({ - _i37.Key? key, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return replaceWith(Routes.courseCategoryView, - arguments: CourseCategoryViewArguments(key: key), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future replaceWithFailureView({ - _i37.Key? key, + _i39.Key? key, required void Function() onTap, required String label, int? routerId, @@ -2556,8 +2655,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithCourseLessonView({ - _i37.Key? key, - required _i42.Course course, + _i39.Key? key, + required _i44.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2573,8 +2672,8 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithCourseLessonDetailView({ - _i37.Key? key, - required _i43.CourseLesson lesson, + _i39.Key? key, + required _i45.CourseLesson lesson, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2590,7 +2689,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithDuolingoView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2605,26 +2704,8 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - Future replaceWithCourseSubcategoryView({ - _i37.Key? key, - required _i44.Category category, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return replaceWith(Routes.courseSubcategoryView, - arguments: CourseSubcategoryViewArguments(key: key, category: category), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future replaceWithCourseView({ - _i37.Key? key, - required _i45.Subcategory subcategory, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2632,7 +2713,7 @@ extension NavigatorStateExtension on _i46.NavigationService { transition, }) async { return replaceWith(Routes.courseView, - arguments: CourseViewArguments(key: key, subcategory: subcategory), + arguments: CourseViewArguments(key: key), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -2640,7 +2721,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithCoursePracticeQuestionView({ - _i37.Key? key, + _i39.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -2657,7 +2738,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLearnProgramView({ - _i37.Key? key, + _i39.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2673,7 +2754,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithLearnCourseView({ - _i37.Key? key, + _i39.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -2690,7 +2771,7 @@ extension NavigatorStateExtension on _i46.NavigationService { } Future replaceWithAssessmentView({ - _i37.Key? key, + _i39.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -2705,4 +2786,69 @@ extension NavigatorStateExtension on _i46.NavigationService { parameters: parameters, transition: transition); } + + Future replaceWithLearnSubscriptionView({ + _i39.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.learnSubscriptionView, + arguments: LearnSubscriptionViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithArifPayView({ + _i39.Key? key, + required String phone, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.arifPayView, + arguments: ArifPayViewArguments(key: key, phone: phone), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithCourseCatalogView({ + _i39.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.courseCatalogView, + arguments: CourseCatalogViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithCourseUnitView({ + _i39.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.courseUnitView, + arguments: CourseUnitViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } } diff --git a/lib/models/category.dart b/lib/models/category.dart deleted file mode 100644 index 7da14a8..0000000 --- a/lib/models/category.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'category.g.dart'; - -@JsonSerializable() -class Category { - final int? id; - - final String? name; - - @JsonKey(name: 'is_active') - final bool? isActive; - - @JsonKey(name: 'total_count') - final int? totalCount; - - const Category({this.id, this.name, this.isActive, this.totalCount}); - - factory Category.fromJson(Map json) => - _$CategoryFromJson(json); - - Map toJson() => _$CategoryToJson(this); -} diff --git a/lib/models/category.g.dart b/lib/models/category.g.dart deleted file mode 100644 index c4f1ea2..0000000 --- a/lib/models/category.g.dart +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'category.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Category _$CategoryFromJson(Map json) => Category( - id: (json['id'] as num?)?.toInt(), - name: json['name'] as String?, - isActive: json['is_active'] as bool?, - totalCount: (json['total_count'] as num?)?.toInt(), - ); - -Map _$CategoryToJson(Category instance) => { - 'id': instance.id, - 'name': instance.name, - 'is_active': instance.isActive, - 'total_count': instance.totalCount, - }; diff --git a/lib/models/course_catalog.dart b/lib/models/course_catalog.dart new file mode 100644 index 0000000..0af16c6 --- /dev/null +++ b/lib/models/course_catalog.dart @@ -0,0 +1,45 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'course_catalog.g.dart'; + +@JsonSerializable() +class CourseCatalog { + final int? id; + + final String? name; + + final String? thumbnail; + + final String? description; + + @JsonKey(name: 'sort_order') + final int? sortOrder; + + @JsonKey(name: 'units_count') + final int? unitsCount; + + @JsonKey(name: 'modules_count') + final int? modulesCount; + + @JsonKey(name: 'lessons_count') + final int? lessonsCount; + + @JsonKey(name: 'has_practice') + final bool? hasPractice; + + const CourseCatalog( + {this.id, + this.name, + this.thumbnail, + this.lessonsCount, + this.sortOrder, + this.unitsCount, + this.hasPractice, + this.description, + this.modulesCount}); + + factory CourseCatalog.fromJson(Map json) => + _$CourseCatalogFromJson(json); + + Map toJson() => _$CourseCatalogToJson(this); +} diff --git a/lib/models/course_catalog.g.dart b/lib/models/course_catalog.g.dart new file mode 100644 index 0000000..2acac14 --- /dev/null +++ b/lib/models/course_catalog.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'course_catalog.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CourseCatalog _$CourseCatalogFromJson(Map json) => + CourseCatalog( + id: (json['id'] as num?)?.toInt(), + name: json['name'] as String?, + thumbnail: json['thumbnail'] as String?, + lessonsCount: (json['lessons_count'] as num?)?.toInt(), + sortOrder: (json['sort_order'] as num?)?.toInt(), + unitsCount: (json['units_count'] as num?)?.toInt(), + hasPractice: json['has_practice'] as bool?, + description: json['description'] as String?, + modulesCount: (json['modules_count'] as num?)?.toInt(), + ); + +Map _$CourseCatalogToJson(CourseCatalog instance) => + { + 'id': instance.id, + 'name': instance.name, + 'thumbnail': instance.thumbnail, + 'description': instance.description, + 'sort_order': instance.sortOrder, + 'units_count': instance.unitsCount, + 'modules_count': instance.modulesCount, + 'lessons_count': instance.lessonsCount, + 'has_practice': instance.hasPractice, + }; diff --git a/lib/models/learn_subscription.dart b/lib/models/learn_subscription.dart new file mode 100644 index 0000000..7514375 --- /dev/null +++ b/lib/models/learn_subscription.dart @@ -0,0 +1,40 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'learn_subscription.g.dart'; + +@JsonSerializable() +class LearnSubscription { + final int? id; + + final String? name; + + final double? price; + + final String? currency; + + final String? description; + + @JsonKey(name: 'is_active') + final bool? isActive; + + @JsonKey(name: 'duration_unit') + final String? durationUnit; + + @JsonKey(name: 'duration_value') + final int? durationValue; + + const LearnSubscription( + {this.id, + this.name, + this.price, + this.isActive, + this.currency, + this.description, + this.durationUnit, + this.durationValue}); + + factory LearnSubscription.fromJson(Map json) => + _$LearnSubscriptionFromJson(json); + + Map toJson() => _$LearnSubscriptionToJson(this); +} diff --git a/lib/models/learn_subscription.g.dart b/lib/models/learn_subscription.g.dart new file mode 100644 index 0000000..b55fe4b --- /dev/null +++ b/lib/models/learn_subscription.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'learn_subscription.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LearnSubscription _$LearnSubscriptionFromJson(Map json) => + LearnSubscription( + id: (json['id'] as num?)?.toInt(), + name: json['name'] as String?, + price: (json['price'] as num?)?.toDouble(), + isActive: json['is_active'] as bool?, + currency: json['currency'] as String?, + description: json['description'] as String?, + durationUnit: json['duration_unit'] as String?, + durationValue: (json['duration_value'] as num?)?.toInt(), + ); + +Map _$LearnSubscriptionToJson(LearnSubscription instance) => + { + 'id': instance.id, + 'name': instance.name, + 'price': instance.price, + 'currency': instance.currency, + 'description': instance.description, + 'is_active': instance.isActive, + 'duration_unit': instance.durationUnit, + 'duration_value': instance.durationValue, + }; diff --git a/lib/models/learn_subscription_request.dart b/lib/models/learn_subscription_request.dart new file mode 100644 index 0000000..56ab93d --- /dev/null +++ b/lib/models/learn_subscription_request.dart @@ -0,0 +1,31 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'learn_subscription_request.g.dart'; + +@JsonSerializable() +class LearnSubscriptionRequest { + final double? amount; + + final String? currency; + + @JsonKey(name: 'payment_id') + final int? paymentId; + + @JsonKey(name: 'session_id') + final String? sessionId; + + @JsonKey(name: 'payment_url') + final String? paymentUrl; + + const LearnSubscriptionRequest( + {this.amount, + this.currency, + this.sessionId, + this.paymentId, + this.paymentUrl}); + + factory LearnSubscriptionRequest.fromJson(Map json) => + _$LearnSubscriptionRequestFromJson(json); + + Map toJson() => _$LearnSubscriptionRequestToJson(this); +} diff --git a/lib/models/learn_subscription_request.g.dart b/lib/models/learn_subscription_request.g.dart new file mode 100644 index 0000000..efc0e1f --- /dev/null +++ b/lib/models/learn_subscription_request.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'learn_subscription_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LearnSubscriptionRequest _$LearnSubscriptionRequestFromJson( + Map json) => + LearnSubscriptionRequest( + amount: (json['amount'] as num?)?.toDouble(), + currency: json['currency'] as String?, + sessionId: json['session_id'] as String?, + paymentId: (json['payment_id'] as num?)?.toInt(), + paymentUrl: json['payment_url'] as String?, + ); + +Map _$LearnSubscriptionRequestToJson( + LearnSubscriptionRequest instance) => + { + 'amount': instance.amount, + 'currency': instance.currency, + 'payment_id': instance.paymentId, + 'session_id': instance.sessionId, + 'payment_url': instance.paymentUrl, + }; diff --git a/lib/models/subcategory.dart b/lib/models/subcategory.dart deleted file mode 100644 index c658c7f..0000000 --- a/lib/models/subcategory.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'subcategory.g.dart'; - -@JsonSerializable() -class Subcategory { - final int? id; - - final String? name; - - final String? description; - - @JsonKey(name: 'is_active') - final bool? isActive; - - @JsonKey(name: 'total_count') - final int? totalCount; - - @JsonKey(name: 'category_id') - final int? categoryId; - - @JsonKey(name: 'category_name') - final String? categoryName; - - @JsonKey(name: 'display_order') - final int? displayOrder; - - const Subcategory( - {this.id, - this.name, - this.isActive, - this.totalCount, - this.categoryId, - this.description, - this.categoryName, - this.displayOrder}); - - factory Subcategory.fromJson(Map json) => - _$SubcategoryFromJson(json); - - Map toJson() => _$SubcategoryToJson(this); -} diff --git a/lib/models/subcategory.g.dart b/lib/models/subcategory.g.dart deleted file mode 100644 index ce5c15a..0000000 --- a/lib/models/subcategory.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'subcategory.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Subcategory _$SubcategoryFromJson(Map json) => Subcategory( - id: (json['id'] as num?)?.toInt(), - name: json['name'] as String?, - isActive: json['is_active'] as bool?, - totalCount: (json['total_count'] as num?)?.toInt(), - categoryId: (json['category_id'] as num?)?.toInt(), - description: json['description'] as String?, - categoryName: json['category_name'] as String?, - displayOrder: (json['display_order'] as num?)?.toInt(), - ); - -Map _$SubcategoryToJson(Subcategory instance) => - { - 'id': instance.id, - 'name': instance.name, - 'description': instance.description, - 'is_active': instance.isActive, - 'total_count': instance.totalCount, - 'category_id': instance.categoryId, - 'category_name': instance.categoryName, - 'display_order': instance.displayOrder, - }; diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 374d71f..3469704 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -4,8 +4,7 @@ import 'package:yimaru_app/models/learn_practice.dart'; import 'package:yimaru_app/models/learn_program.dart'; import 'package:yimaru_app/models/level.dart'; import 'package:yimaru_app/models/assessment_question.dart'; -import 'package:yimaru_app/models/subcategory.dart'; -import 'package:yimaru_app/models/category.dart'; +import 'package:yimaru_app/models/course_catalog.dart'; import 'package:yimaru_app/models/course_lesson.dart'; import 'package:yimaru_app/models/course_progress.dart'; import 'package:yimaru_app/models/course.dart'; @@ -18,10 +17,12 @@ import '../app/app.locator.dart'; import '../models/learn_course.dart'; import '../models/learn_module.dart'; import '../models/learn_question.dart'; +import '../models/learn_subscription.dart'; import '../models/lesson.dart'; import '../models/module.dart'; import '../models/assessment.dart'; import '../models/submodule.dart'; +import '../models/learn_subscription_request.dart'; import '../ui/common/enmus.dart'; class ApiService { @@ -432,7 +433,7 @@ class ApiService { } // Learn learn courses - Future> getLearnCourse(int id) async { + Future> getLearnCourses(int id) async { try { List learnCourses = []; @@ -552,6 +553,32 @@ class ApiService { } } + // Complete lesson + Future> completeLearnLesson(int id) async { + try { + Response response = await _service.dio.post( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kLessonsUrl/$id/$kCompleteUrl', + ); + + if (response.statusCode == 200 && response.data['success']) { + return { + 'message': 'Lesson completed', + 'status': ResponseStatus.success, + }; + } else { + return { + 'status': ResponseStatus.failure, + 'message': 'Unknown Error Occurred' + }; + } + } on DioException catch (e) { + return { + 'status': ResponseStatus.failure, + 'message': e.response?.data.toString(), + }; + } + } + // Learn lesson practices Future> getLearnLessonPractices(int id) async { try { @@ -601,49 +628,48 @@ class ApiService { } } - /* TO BE MODIFIED*/ - - // Get categories - Future> getCategories() async { + // Complete lesson + Future> completeLearnPractice(int id) async { try { - List categories = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kCategoryUrl'); + Response response = await _service.dio.post( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kProgressUrl/$kPracticesUrl/$id/$kCompleteUrl', + ); if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data']['categories'] as List; - categories = decodedData.map( - (e) { - return Category.fromJson(e); - }, - ).toList(); - return categories; + return { + 'status': ResponseStatus.success, + 'message': 'Lesson completed' + }; + } else { + return { + 'status': ResponseStatus.failure, + 'message': 'Unknown Error Occurred' + }; } - return []; - } catch (e) { - return []; + } on DioException catch (e) { + return { + 'status': ResponseStatus.failure, + 'message': e.response?.data.toString(), + }; } } - // Get course subcategory - Future> getSubcategories(int id) async { + Future> getLearnSubscriptions() async { try { - List subcategories = []; + List subscriptions = []; final Response response = await _service.dio - .get('$kBaseUrl/$kCourseBaseUrl/$kCategoryUrl/$id/$kCoursesUrl'); + .get('$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kSubscriptionsUrl'); if (response.statusCode == 200) { var data = response.data; - var decodedData = data['data']['courses'] as List; - subcategories = decodedData.map( + var decodedData = data['data'] as List; + subscriptions = decodedData.map( (e) { - return Subcategory.fromJson(e); + return LearnSubscription.fromJson(e); }, ).toList(); - return subcategories; + return subscriptions; } return []; } catch (e) { @@ -651,6 +677,60 @@ class ApiService { } } + // Create subscription + Future> createSubscriptionRequest( + Map data) async { + try { + Response response = await _service.dio.post( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kPaymentsUrl/$kSubscribeUrl', + data: data); + + if (response.statusCode == 200) { + return { + 'message': 'Lesson completed', + 'status': ResponseStatus.success, + 'data': LearnSubscriptionRequest.fromJson(response.data['data']), + }; + } else { + return { + 'status': ResponseStatus.failure, + 'message': 'Unknown Error Occurred' + }; + } + } on DioException catch (e) { + return { + 'status': ResponseStatus.failure, + 'message': e.response?.data.toString(), + }; + } + } + + // Get course catalogs + Future> getCourseCatalogs() async { + try { + List catalogs = []; + + final Response response = await _service.dio.get( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kExamPrepUrl/$kCatalogCoursesUrl'); + + if (response.statusCode == 200) { + var data = response.data; + var decodedData = data['data']['catalog_courses'] as List; + catalogs = decodedData.map( + (e) { + return CourseCatalog.fromJson(e); + }, + ).toList(); + return catalogs; + } + return []; + } catch (e) { + return []; + } + } + + /* TO BE MODIFIED*/ + // Get courses // Future> getCourses(int id) async { // try { @@ -727,7 +807,7 @@ class ApiService { Future> completeLesson(int id) async { try { Response response = await _service.dio.post( - '$kBaseUrl/$kLessonProgressUrl/$id/$kCompleteLessonUrl', + '$kBaseUrl/$kLessonProgressUrl/$id/$kCompleteUrl', ); if (response.statusCode == 200) { @@ -813,9 +893,9 @@ class ApiService { } // Get learn subcategories - Future> getLearnSubcategories() async { + Future> getLearnSubcategories() async { try { - List learnSubcategories = []; + List learnSubcategories = []; final Response response = await _service.dio.get( '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kLearnSubcategoriesUrl'); @@ -825,7 +905,7 @@ class ApiService { var decodedData = data['data']['sub_categories'] as List; learnSubcategories = decodedData.map( (e) { - return Subcategory.fromJson(e); + return CourseCatalog.fromJson(e); }, ).toList(); return learnSubcategories; diff --git a/lib/services/learn_service.dart b/lib/services/learn_service.dart new file mode 100644 index 0000000..2630296 --- /dev/null +++ b/lib/services/learn_service.dart @@ -0,0 +1,66 @@ +import 'package:stacked/stacked.dart'; + +import '../app/app.locator.dart'; +import '../models/learn_course.dart'; +import '../models/learn_lesson.dart'; +import '../models/learn_module.dart'; +import '../models/learn_program.dart'; +import 'api_service.dart'; + +class LearnService with ListenableServiceMixin { + // Dependency injection + final _apiService = locator(); + + // Initialization + LearnLessonService() { + listenToReactiveValues([_programs, _lessons]); + } + + // Learn program + List _programs = []; + + List get programs => _programs; + + // Learn course + List _courses = []; + + List get courses => _courses; + + // Learn module + List _modules = []; + + List get modules => _modules; + + // Learn lesson + List _lessons = []; + + List get lessons => _lessons; + + // Learn programs + Future getLearnPrograms() async { + _programs = await _apiService.getLearnPrograms(); + _programs.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); + notifyListeners(); + } + + // Learn modules + Future getLearnCourses(int id) async { + _courses = await _apiService.getLearnCourses(id); + _courses.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); + notifyListeners(); + } + + // Learn modules + Future getLearnModules(int id) async { + _modules = await _apiService.getLearnModules(id); + _modules.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); + notifyListeners(); + } + + // Learn lessons + Future getLearnLessons(int id) async { + _lessons = await _apiService.getLearnLessons(id); + _lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); + notifyListeners(); + } +} diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index 3789bd7..9c1cd8c 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -130,7 +130,7 @@ class NotificationService { } Future updateFCMToken() async { - // print('DEVICE TOKEN: ${await _messaging.getToken()}'); + print('DEVICE TOKEN: ${await _messaging.getToken()}'); _messaging.onTokenRefresh.listen((newToken) { // updateTokenOnServer(newToken); }); diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 1f75a8b..6cb4eb9 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -17,11 +17,19 @@ String kProgramsUrl = 'programs'; String kRegisterUrl = 'register'; +String kProgressUrl = 'progress'; + +String kCompleteUrl = 'complete'; + +String kPaymentsUrl = 'payments'; + +String kSubscribeUrl = 'subscribe'; + String kPracticesUrl = 'practices'; String kQuestionsUrl = 'questions'; -String kCategoryUrl = 'categories'; +String kExamPrepUrl = 'exam-prep'; String kCoursePractice = 'by-owner'; @@ -37,8 +45,6 @@ String kSubmodulesUrl = 'sub-modules'; String kSubcoursesUrl = 'sub-courses'; -String kCompleteLessonUrl = 'complete'; - String kResetPassword = 'resetPassword'; String kQuestionSetsUrl = 'question-sets'; @@ -51,8 +57,12 @@ String kPublishedVideos = 'videos/published'; String kCoursePracticeQuestions = 'questions'; +String kCatalogCoursesUrl = 'catalog-courses'; + String kUpdateProfileImage = 'profile-picture'; +String kSubscriptionsUrl = 'subscription-plans'; + String kRefreshTokenUrl = 'api/v1/auth/refresh'; String kLoginUrl = 'api/v1/auth/customer-login'; @@ -89,3 +99,10 @@ String kServerClientId = String kPhoneSupport = '+251946396655'; String kTelegramSupport = '@yimaruacademy2026'; + +String kTelegramSupportLink = 'https://t.me/yimaruacademy2026'; + +String kErrorUrl = 'https://yimaru.net/api/v1/payments/arifpay/error'; + +String kSuccessUrl = + 'https://api.yimaruacademy.com/api/v1/payments/arifpay/success'; diff --git a/lib/ui/common/enmus.dart b/lib/ui/common/enmus.dart index ccf4a8f..789f4d0 100644 --- a/lib/ui/common/enmus.dart +++ b/lib/ui/common/enmus.dart @@ -42,23 +42,25 @@ enum StateObjects { courseLessons, profileUpdate, resetPassword, - subcategories, + learnPractice, + courseCatalogs, loginWithEmail, coursePractice, - learnPractices, loginWithGoogle, loadLessonVideo, loadCourseVideo, learnSubmodules, requestResetCode, - courseCategories, profileCompletion, + learnSubscription, + learnSubscriptions, registerWithGoogle, learnPracticeSample, learnPracticeAnswer, loginWithPhoneNumber, assessmentQuestions, learnPracticeQuestion, + completeLearnPractice, coursePracticeQuestion, coursePracticeQuestions, recordLearnPracticeAnswer, diff --git a/lib/ui/common/helper_functions.dart b/lib/ui/common/helper_functions.dart index b0d8aa9..b132ff2 100644 --- a/lib/ui/common/helper_functions.dart +++ b/lib/ui/common/helper_functions.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:math'; import 'dart:ui'; diff --git a/lib/ui/views/arif_pay/arif_pay_view.dart b/lib/ui/views/arif_pay/arif_pay_view.dart new file mode 100644 index 0000000..565f5d6 --- /dev/null +++ b/lib/ui/views/arif_pay/arif_pay_view.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/models/learn_subscription.dart'; +import 'package:yimaru_app/ui/common/app_constants.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; + +import '../../../models/learn_subscription_request.dart'; +import 'arif_pay_viewmodel.dart'; + +class ArifPayView extends StackedView { + final String phone; + + const ArifPayView({Key? key, required this.phone}) : super(key: key); + + void _pop(ArifPayViewModel viewModel) => viewModel.pop; + + Future _error() async { + // await Navigator.pushNamed(context, AppRoutes.subscriptionErrorPage); + // Navigation.pop(); + } + + void _success() { + // Navigation.navigateTo( + // AppRoutes.subscriptionSuccessPage, + // arguments: widget.body, + // ); + } + + @override + void onViewModelReady(ArifPayViewModel viewModel) async { + await viewModel.createLearnSubscriptionRequest(phone); + super.onViewModelReady(viewModel); + } + + @override + ArifPayViewModel viewModelBuilder(BuildContext context) => ArifPayViewModel(); + + @override + Widget builder( + BuildContext context, + ArifPayViewModel viewModel, + Widget? child, + ) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(ArifPayViewModel viewModel) => + Scaffold(body: _buildScaffoldState(viewModel)); + + Widget _buildScaffoldState(ArifPayViewModel viewModel) => + viewModel.busy(StateObjects.learnSubscription) + ? const PageLoadingIndicator() + : _buildScaffold(viewModel); + + Widget _buildScaffold(ArifPayViewModel viewModel) => + SafeArea(child: _buildBody(viewModel)); + + Widget _buildBody(ArifPayViewModel viewModel) => InAppWebView( + initialUrlRequest: + URLRequest(url: WebUri(viewModel.request?.paymentUrl ?? '')), + onUpdateVisitedHistory: (controller, url, androidIsReload) { + if (url + .toString() + .contains("https://checkout.arifpay.net/canceled")) { + showErrorToast('Operation was cancelled'); + // _pop(); + } else if (url.toString().contains(kSuccessUrl)) { + _success(); + } else if (url.toString().contains(kErrorUrl)) { + showErrorToast('Operation was cancelled'); + // _pop(); + } else if (url.toString().contains("http://x.com/elonmusk/status/")) { + _error(); + } else if (url.toString().contains(kErrorUrl)) { + _error(); + } + }, + ); +} diff --git a/lib/ui/views/arif_pay/arif_pay_viewmodel.dart b/lib/ui/views/arif_pay/arif_pay_viewmodel.dart new file mode 100644 index 0000000..79d8595 --- /dev/null +++ b/lib/ui/views/arif_pay/arif_pay_viewmodel.dart @@ -0,0 +1,71 @@ +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; + +import '../../../app/app.locator.dart'; +import '../../../models/learn_subscription_request.dart'; +import '../../../services/api_service.dart'; +import '../../../services/status_checker_service.dart'; + +class ArifPayViewModel extends BaseViewModel { + // Dependency injection + + final _apiService = locator(); + + final _statusChecker = locator(); + + final _navigationService = locator(); + + // Learn subscription request + LearnSubscriptionRequest? _request; + + LearnSubscriptionRequest? get request => _request; + + // Navigation + void pop() => _navigationService.back(); + + // Remote api call + + // Learn subscription + Future createLearnSubscriptionRequest(String phone) async => + await runBusyFuture(_createLearnSubscriptionRequest(phone), + busyObject: StateObjects.learnSubscription); + + Future _createLearnSubscriptionRequest(String phone) async { + if (await _statusChecker.checkConnection()) { + Map data = { + 'plan_id': 1, + 'phone': '251$phone', + 'email': 'test@gmail.com' + }; + + Map response = + await _apiService.createSubscriptionRequest(data); + + if (response['status'] == ResponseStatus.success) { + _request = response['data']; + } + } + } + + // + // Future verifyLearnSubscription(String id) async => await runBusyFuture(_verifyLearnSubscription(phone), + // busyObject: StateObjects.learnSubscription); + // + // Future _verifyLearnSubscription(String id) async { + // if (await _statusChecker.checkConnection()) { + // Map data = { + // 'plan_id':1, + // 'phone': '251$phone', + // 'email':'test@gmail.com' + // }; + // + // Map response = + // await _apiService.createSubscriptionRequest(data); + // + // if (response['status'] == ResponseStatus.success) { + // _request = response['data']; + // } + // } + // } +} diff --git a/lib/ui/views/course/course_view.dart b/lib/ui/views/course/course_view.dart index b341e13..a955610 100644 --- a/lib/ui/views/course/course_view.dart +++ b/lib/ui/views/course/course_view.dart @@ -2,25 +2,19 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import '../../../models/course_detail.dart'; -import '../../../models/subcategory.dart'; +import '../../../models/course_catalog.dart'; import '../../common/app_colors.dart'; import '../../common/enmus.dart'; import '../../common/ui_helpers.dart'; +import '../../widgets/course_category_card.dart'; import '../../widgets/course_tile.dart'; import '../../widgets/custom_circular_progress_indicator.dart'; +import '../../widgets/profile_app_bar.dart'; import '../../widgets/small_app_bar.dart'; import 'course_viewmodel.dart'; class CourseView extends StackedView { - final Subcategory subcategory; - - const CourseView({Key? key, required this.subcategory}) : super(key: key); - - @override - void onViewModelReady(CourseViewModel viewModel) async { - await viewModel.getCourseDetails(subcategory.id ?? 0); - super.onViewModelReady(viewModel); - } + const CourseView({Key? key}) : super(key: key); @override CourseViewModel viewModelBuilder(BuildContext context) => CourseViewModel(); @@ -51,78 +45,63 @@ class CourseView extends StackedView { verticalSpaceMedium, _buildAppBar(viewModel), verticalSpaceMedium, - _buildCoursesColumnWrapper(viewModel), + _buildCategoryColumnWrapper(viewModel) ], ); - Widget _buildAppBar(CourseViewModel viewModel) => SmallAppBar( - onPop: viewModel.pop, - showBackButton: true, + Widget _buildAppBar(CourseViewModel viewModel) => ProfileAppBar( + name: viewModel.user?.firstName, + profileImage: viewModel.user?.profilePicture, ); - Widget _buildCoursesColumnWrapper(CourseViewModel viewModel) => - Expanded(child: _buildCoursesColumnScrollView(viewModel)); + Widget _buildCategoryColumnWrapper(CourseViewModel viewModel) => + Expanded(child: _buildCourseColumnScrollView(viewModel)); - Widget _buildCoursesColumnScrollView(CourseViewModel viewModel) => + Widget _buildCourseColumnScrollView(CourseViewModel viewModel) => SingleChildScrollView( - child: _buildCoursesColumn(viewModel), + child: _buildCourseColumn(viewModel), ); - Widget _buildCoursesColumn(CourseViewModel viewModel) => Column( + Widget _buildCourseColumn(CourseViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: _buildCoursesColumnChildren(viewModel), + children: _buildLevelsColumnChildren(viewModel), ); - List _buildCoursesColumnChildren(CourseViewModel viewModel) => [ - verticalSpaceMedium, + List _buildLevelsColumnChildren(CourseViewModel viewModel) => [ _buildTitle(), _buildSubtitle(), verticalSpaceMedium, - _buildListViewBuilder(viewModel) + _buildListView(viewModel) ]; Widget _buildTitle() => Text( - '${subcategory.name ?? ''} courses', + 'Courses', style: style18DG700, ); Widget _buildSubtitle() => Text( - 'Explore variety of courses on ${subcategory.name ?? ''}.', + 'Choose a course to improve your professional or exam skills.', style: style14DG400, ); - Widget _buildListViewBuilder(CourseViewModel viewModel) => - viewModel.busy(StateObjects.courses) - ? _buildProgressIndicator() - : _buildListView(viewModel); - - Widget _buildProgressIndicator() => const Center( - child: CustomCircularProgressIndicator(color: kcPrimaryColor), - ); - Widget _buildListView(CourseViewModel viewModel) => ListView.separated( shrinkWrap: true, - itemCount: viewModel.courseDetail.length, + itemCount: viewModel.courses.length, physics: const NeverScrollableScrollPhysics(), - separatorBuilder: (context, index) => verticalSpaceSmall, itemBuilder: (context, index) => _buildTile( - courseDetail: viewModel.courseDetail[index], - onCourseTap: () async => await viewModel - .navigateToCoursePayment(viewModel.courseDetail[index].course!), - onPracticeTap: () async => await viewModel.navigateToCoursePractice( - viewModel.courseDetail[index].course?.id ?? 0), - ), + course: viewModel.courses[index], + onTap: () async => await viewModel.navigateToCourseCatalog()), + separatorBuilder: (context, index) => verticalSpaceSmall, ); + // Widget _buildTile({ - GestureTapCallback? onCourseTap, - GestureTapCallback? onPracticeTap, - required CourseDetail courseDetail, + required GestureTapCallback onTap, + required Map course, }) => - CourseTile( - onCourseTap: onCourseTap, - courseDetail: courseDetail, - onPracticeTap: onPracticeTap, + CourseCard( + onTap: onTap, + course: course, ); } diff --git a/lib/ui/views/course/course_viewmodel.dart b/lib/ui/views/course/course_viewmodel.dart index 4de1a05..2409e66 100644 --- a/lib/ui/views/course/course_viewmodel.dart +++ b/lib/ui/views/course/course_viewmodel.dart @@ -5,44 +5,47 @@ import '../../../app/app.locator.dart'; import '../../../app/app.router.dart'; import '../../../models/course.dart'; import '../../../models/course_detail.dart'; +import '../../../models/user.dart'; +import '../../../services/authentication_service.dart'; import '../../../services/course_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; -class CourseViewModel extends BaseViewModel { +class CourseViewModel extends ReactiveViewModel { // Dependency injection - final _courseService = locator(); - - final _statusChecker = locator(); final _navigationService = locator(); - // Subcourse with progress - List _courseDetail = []; + final _authenticationService = locator(); - List get courseDetail => _courseDetail; + @override + List get listenableServices => + [_authenticationService]; + + // Current user + User? get _user => _authenticationService.user; + + User? get user => _user; + + // Course + final List> _courses = [ + { + 'title': 'English Proficiency Exams', + 'description': + 'Prepare for IELTS, TOEFL, or Duolingo with structured practice.' + }, + { + 'title': 'Skill-Based Courses', + 'description': + 'Learn English for the workplace, travel, and real-life communication.' + }, + ]; + + List> get courses => _courses; // Navigation void pop() => _navigationService.back(); - Future navigateToCoursePractice(int id) => - _navigationService.navigateToCoursePracticeView(id: id); - - Future navigateToCoursePayment(Course course) async => - _navigationService.navigateToCoursePaymentView(course: course); - - // Remote api call - - // Course detail - Future getCourseDetails(int id) async => - await runBusyFuture(_getCourseDetails(id), - busyObject: StateObjects.courses); - - Future _getCourseDetails(int id) async { - if (await _statusChecker.checkConnection()) { - _courseDetail = await _courseService.getCoursesDetail(id); - // _courseDetail.sort((a, b) => - // (a.course?.displayOrder ?? 0).compareTo(b.course?.displayOrder ?? 0)); - } - } + Future navigateToCourseCatalog() async => + await _navigationService.navigateToCourseCatalogView(); } diff --git a/lib/ui/views/course_subcategory/course_subcategory_view.dart b/lib/ui/views/course_catalog/course_catalog_view.dart similarity index 51% rename from lib/ui/views/course_subcategory/course_subcategory_view.dart rename to lib/ui/views/course_catalog/course_catalog_view.dart index ac49a00..fd0b1bc 100644 --- a/lib/ui/views/course_subcategory/course_subcategory_view.dart +++ b/lib/ui/views/course_catalog/course_catalog_view.dart @@ -1,55 +1,50 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/models/course_catalog.dart'; -import '../../../models/category.dart'; -import '../../../models/subcategory.dart'; import '../../common/app_colors.dart'; import '../../common/enmus.dart'; import '../../common/ui_helpers.dart'; -import '../../widgets/course_subcategory_tile.dart'; +import '../../widgets/course_catalog_tile.dart'; import '../../widgets/custom_circular_progress_indicator.dart'; import '../../widgets/small_app_bar.dart'; -import 'course_subcategory_viewmodel.dart'; +import 'course_catalog_viewmodel.dart'; -class CourseSubcategoryView extends StackedView { - final Category category; - - const CourseSubcategoryView({Key? key, required this.category}) - : super(key: key); +class CourseCatalogView extends StackedView { + const CourseCatalogView({Key? key}) : super(key: key); @override - void onViewModelReady(CourseSubcategoryViewModel viewModel) async { - await viewModel.getSubcategories(category.id ?? 0); + void onViewModelReady(CourseCatalogViewModel viewModel) async { + await viewModel.getCourseCatalogs(); super.onViewModelReady(viewModel); } @override - CourseSubcategoryViewModel viewModelBuilder(BuildContext context) => - CourseSubcategoryViewModel(); + CourseCatalogViewModel viewModelBuilder(BuildContext context) => + CourseCatalogViewModel(); @override Widget builder( BuildContext context, - CourseSubcategoryViewModel viewModel, + CourseCatalogViewModel viewModel, Widget? child, ) => _buildScaffoldWrapper(viewModel); - Widget _buildScaffoldWrapper(CourseSubcategoryViewModel viewModel) => - Scaffold( + Widget _buildScaffoldWrapper(CourseCatalogViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, body: _buildScaffold(viewModel), ); - Widget _buildScaffold(CourseSubcategoryViewModel viewModel) => + Widget _buildScaffold(CourseCatalogViewModel viewModel) => SafeArea(child: _buildBody(viewModel)); - Widget _buildBody(CourseSubcategoryViewModel viewModel) => Padding( + Widget _buildBody(CourseCatalogViewModel viewModel) => Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: _buildColumn(viewModel), ); - Widget _buildColumn(CourseSubcategoryViewModel viewModel) => Column( + Widget _buildColumn(CourseCatalogViewModel viewModel) => Column( children: [ verticalSpaceMedium, _buildAppBar(viewModel), @@ -58,27 +53,26 @@ class CourseSubcategoryView extends StackedView { ], ); - Widget _buildAppBar(CourseSubcategoryViewModel viewModel) => SmallAppBar( + Widget _buildAppBar(CourseCatalogViewModel viewModel) => SmallAppBar( onPop: viewModel.pop, showBackButton: true, ); - Widget _buildCoursesColumnWrapper(CourseSubcategoryViewModel viewModel) => + Widget _buildCoursesColumnWrapper(CourseCatalogViewModel viewModel) => Expanded(child: _buildCoursesColumnScrollView(viewModel)); - Widget _buildCoursesColumnScrollView(CourseSubcategoryViewModel viewModel) => + Widget _buildCoursesColumnScrollView(CourseCatalogViewModel viewModel) => SingleChildScrollView( child: _buildCoursesColumn(viewModel), ); - Widget _buildCoursesColumn(CourseSubcategoryViewModel viewModel) => Column( + Widget _buildCoursesColumn(CourseCatalogViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: _buildCoursesColumnChildren(viewModel), ); - List _buildCoursesColumnChildren( - CourseSubcategoryViewModel viewModel) => + List _buildCoursesColumnChildren(CourseCatalogViewModel viewModel) => [ _buildTitle(), _buildSubtitle(), @@ -87,17 +81,17 @@ class CourseSubcategoryView extends StackedView { ]; Widget _buildTitle() => Text( - '${category.name ?? ''} courses', + 'English Proficiency Exams', style: style18DG700, ); Widget _buildSubtitle() => Text( - 'Explore variety of courses on this category.', + 'Select your target exam and start preparing', style: style14DG400, ); - Widget _buildListViewBuilder(CourseSubcategoryViewModel viewModel) => - viewModel.busy(StateObjects.subcategories) + Widget _buildListViewBuilder(CourseCatalogViewModel viewModel) => + viewModel.busy(StateObjects.courseCatalogs) ? _buildProgressIndicator() : _buildListView(viewModel); @@ -105,27 +99,25 @@ class CourseSubcategoryView extends StackedView { child: CustomCircularProgressIndicator(color: kcPrimaryColor), ); - Widget _buildListView(CourseSubcategoryViewModel viewModel) => - ListView.separated( + Widget _buildListView(CourseCatalogViewModel viewModel) => ListView.separated( shrinkWrap: true, - itemCount: viewModel.subcategories.length, + itemCount: viewModel.courseCatalogs.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTile( - subcategory: viewModel.subcategories[index], - onCourseTap: () async => await viewModel - .navigateToSubcourse(viewModel.subcategories[index]), - ), + courseCatalog: viewModel.courseCatalogs[index], + onCourseTap: () {}, + onPracticeTap: () {}), separatorBuilder: (context, index) => verticalSpaceSmall, ); Widget _buildTile({ - GestureTapCallback? onCourseTap, - GestureTapCallback? onPracticeTap, - required Subcategory subcategory, + required CourseCatalog courseCatalog, + required GestureTapCallback onCourseTap, + required GestureTapCallback onPracticeTap, }) => - CourseSubcategoryTile( - subcategory: subcategory, + CourseCatalogTile( onCourseTap: onCourseTap, onPracticeTap: onPracticeTap, + courseCatalog: courseCatalog, ); } diff --git a/lib/ui/views/course_subcategory/course_subcategory_viewmodel.dart b/lib/ui/views/course_catalog/course_catalog_viewmodel.dart similarity index 54% rename from lib/ui/views/course_subcategory/course_subcategory_viewmodel.dart rename to lib/ui/views/course_catalog/course_catalog_viewmodel.dart index 4d33faf..e9243dd 100644 --- a/lib/ui/views/course_subcategory/course_subcategory_viewmodel.dart +++ b/lib/ui/views/course_catalog/course_catalog_viewmodel.dart @@ -1,14 +1,14 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; +import 'package:yimaru_app/models/course_catalog.dart'; import '../../../app/app.locator.dart'; import '../../../app/app.router.dart'; -import '../../../models/subcategory.dart'; import '../../../services/api_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; -class CourseSubcategoryViewModel extends BaseViewModel { +class CourseCatalogViewModel extends BaseViewModel { // Dependency injection final _apiService = locator(); @@ -16,10 +16,10 @@ class CourseSubcategoryViewModel extends BaseViewModel { final _navigationService = locator(); - // Course subcategories - List _subcategories = []; + // Course catalogs + List _courseCatalogs = []; - List get subcategories => _subcategories; + List get courseCatalogs => _courseCatalogs; // Navigation void pop() => _navigationService.back(); @@ -27,19 +27,19 @@ class CourseSubcategoryViewModel extends BaseViewModel { Future navigateToCoursePractice(int id) async => _navigationService.navigateToCoursePracticeView(id: id); - Future navigateToSubcourse(Subcategory subcategory) async => - _navigationService.navigateToCourseView(subcategory: subcategory); + // Future navigateToSubcourse(Subcategory subcategory) async => + // _navigationService.navigateToCourseView(subcategory: subcategory); // Remote api call - // Course subcategories - Future getSubcategories(int id) async => - await runBusyFuture(_getSubcategories(id), - busyObject: StateObjects.subcategories); + // Course catalogs + Future getCourseCatalogs() async => + await runBusyFuture(_getSubcategories(), + busyObject: StateObjects.courseCatalogs); - Future _getSubcategories(int id) async { + Future _getSubcategories() async { if (await _statusChecker.checkConnection()) { - _subcategories = await _apiService.getSubcategories(id); + _courseCatalogs = await _apiService.getCourseCatalogs(); } } } diff --git a/lib/ui/views/course_category/course_category_view.dart b/lib/ui/views/course_category/course_category_view.dart deleted file mode 100644 index 05d11eb..0000000 --- a/lib/ui/views/course_category/course_category_view.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; - -import '../../../models/category.dart'; -import '../../common/app_colors.dart'; -import '../../common/enmus.dart'; -import '../../common/ui_helpers.dart'; -import '../../widgets/course_category_card.dart'; -import '../../widgets/custom_circular_progress_indicator.dart'; -import '../../widgets/profile_app_bar.dart'; -import 'course_category_viewmodel.dart'; - -class CourseCategoryView extends StackedView { - const CourseCategoryView({Key? key}) : super(key: key); - - @override - void onViewModelReady(CourseCategoryViewModel viewModel) async { - await viewModel.getCategories(); - super.onViewModelReady(viewModel); - } - - @override - CourseCategoryViewModel viewModelBuilder(BuildContext context) => - CourseCategoryViewModel(); - - @override - Widget builder( - BuildContext context, - CourseCategoryViewModel viewModel, - Widget? child, - ) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(CourseCategoryViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(CourseCategoryViewModel viewModel) => - SafeArea(child: _buildBody(viewModel)); - - Widget _buildBody(CourseCategoryViewModel viewModel) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(viewModel), - ); - - Widget _buildColumn(CourseCategoryViewModel viewModel) => Column( - children: [ - verticalSpaceMedium, - _buildAppBar(viewModel), - verticalSpaceMedium, - _buildCategoryColumnWrapper(viewModel) - ], - ); - - Widget _buildAppBar(CourseCategoryViewModel viewModel) => ProfileAppBar( - name: viewModel.user?.firstName, - profileImage: viewModel.user?.profilePicture, - ); - - Widget _buildCategoryColumnWrapper(CourseCategoryViewModel viewModel) => - Expanded(child: _buildCourseColumnScrollView(viewModel)); - - Widget _buildCourseColumnScrollView(CourseCategoryViewModel viewModel) => - SingleChildScrollView( - child: _buildCourseColumn(viewModel), - ); - - Widget _buildCourseColumn(CourseCategoryViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildLevelsColumnChildren(viewModel), - ); - - List _buildLevelsColumnChildren(CourseCategoryViewModel viewModel) => - [ - _buildTitle(), - _buildSubtitle(), - verticalSpaceMedium, - _buildListViewBuilder(viewModel) - ]; - - Widget _buildTitle() => Text( - 'Courses', - style: style18DG700, - ); - - Widget _buildSubtitle() => Text( - 'Choose a course to improve your professional or exam skills.', - style: style14DG400, - ); - - Widget _buildListViewBuilder(CourseCategoryViewModel viewModel) => - viewModel.busy(StateObjects.courseCategories) - ? _buildProgressIndicator() - : _buildListView(viewModel); - - Widget _buildProgressIndicator() => const Center( - child: CustomCircularProgressIndicator(color: kcPrimaryColor), - ); - - Widget _buildListView(CourseCategoryViewModel viewModel) => - ListView.separated( - shrinkWrap: true, - itemCount: viewModel.categories.length, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => _buildTile( - category: viewModel.categories[index], - onTap: () async => await viewModel - .navigateToCourseCategory(viewModel.categories[index]), - ), - separatorBuilder: (context, index) => verticalSpaceSmall, - ); - - // - Widget _buildTile({ - required Category category, - required GestureTapCallback onTap, - }) => - CourseCategoryCard( - onTap: onTap, - category: category, - ); -} diff --git a/lib/ui/views/course_category/course_category_viewmodel.dart b/lib/ui/views/course_category/course_category_viewmodel.dart deleted file mode 100644 index b0dfed8..0000000 --- a/lib/ui/views/course_category/course_category_viewmodel.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:stacked/stacked.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import '../../../app/app.locator.dart'; -import '../../../app/app.router.dart'; -import '../../../models/category.dart'; -import '../../../models/user.dart'; -import '../../../services/api_service.dart'; -import '../../../services/authentication_service.dart'; -import '../../../services/status_checker_service.dart'; -import '../../common/enmus.dart'; - -class CourseCategoryViewModel extends ReactiveViewModel { - // Dependency injection - final _apiService = locator(); - - final _statusChecker = locator(); - - final _navigationService = locator(); - - final _authenticationService = locator(); - - @override - List get listenableServices => - [_authenticationService]; - - // Current user - User? get _user => _authenticationService.user; - - User? get user => _user; - - // Course categories - List _categories = []; - - List get categories => _categories; - - // Navigation - Future navigateToCourseCategory(Category category) async => - _navigationService.navigateToCourseSubcategoryView(category: category); - - // Remote api call - - // Course categories - Future getCategories() async => await runBusyFuture(_getCategories(), - busyObject: StateObjects.courseCategories); - - Future _getCategories() async { - if (categories.isEmpty) { - if (await _statusChecker.checkConnection()) { - _categories = await _apiService.getCategories(); - } - } - } -} diff --git a/lib/ui/views/course_unit/course_unit_view.dart b/lib/ui/views/course_unit/course_unit_view.dart new file mode 100644 index 0000000..6324926 --- /dev/null +++ b/lib/ui/views/course_unit/course_unit_view.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'course_unit_viewmodel.dart'; + +class CourseUnitView extends StackedView { + const CourseUnitView({Key? key}) : super(key: key); + + @override + Widget builder( + BuildContext context, + CourseUnitViewModel viewModel, + Widget? child, + ) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + body: Container( + padding: const EdgeInsets.only(left: 25.0, right: 25.0), + child: const Center(child: Text("CourseUnitView")), + ), + ); + } + + @override + CourseUnitViewModel viewModelBuilder( + BuildContext context, + ) => + CourseUnitViewModel(); +} diff --git a/lib/ui/views/course_unit/course_unit_viewmodel.dart b/lib/ui/views/course_unit/course_unit_viewmodel.dart new file mode 100644 index 0000000..32b5f84 --- /dev/null +++ b/lib/ui/views/course_unit/course_unit_viewmodel.dart @@ -0,0 +1,3 @@ +import 'package:stacked/stacked.dart'; + +class CourseUnitViewModel extends BaseViewModel {} diff --git a/lib/ui/views/forget_password/forget_password_viewmodel.dart b/lib/ui/views/forget_password/forget_password_viewmodel.dart index 10f672a..65237fb 100644 --- a/lib/ui/views/forget_password/forget_password_viewmodel.dart +++ b/lib/ui/views/forget_password/forget_password_viewmodel.dart @@ -110,8 +110,6 @@ class ForgetPasswordViewModel extends FormViewModel { _length = false; } - - if (password == confirmPassword) { _passwordMatch = true; } else { diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index cf45d50..6848e8e 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -5,6 +5,7 @@ import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/widgets/coming_soon.dart'; +import '../course/course_view.dart'; import 'home_viewmodel.dart'; class HomeView extends StackedView { @@ -69,7 +70,7 @@ class HomeView extends StackedView { case 0: return const LearnProgramView(); case 1: - return const ComingSoon(); + return const CourseView(); default: return const ProfileView(); diff --git a/lib/ui/views/learn_course/learn_course_view.dart b/lib/ui/views/learn_course/learn_course_view.dart index 1e745aa..40fa591 100644 --- a/lib/ui/views/learn_course/learn_course_view.dart +++ b/lib/ui/views/learn_course/learn_course_view.dart @@ -78,15 +78,15 @@ class LearnCourseView extends StackedView { Widget _buildListView(LearnCourseViewModel viewModel) => ListView.separated( shrinkWrap: true, - itemCount: viewModel.learnCourses.length, + itemCount: viewModel.courses.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTile( - course: viewModel.learnCourses[index], - onViewTap: () async => await viewModel - .navigateToLearnModule(viewModel.learnCourses[index]), + course: viewModel.courses[index], + onViewTap: () async => + await viewModel.navigateToLearnModule(viewModel.courses[index]), onPracticeTap: () async => await viewModel.navigateToLearnPractice( - id: viewModel.learnCourses[index].id ?? 0, - level: viewModel.learnCourses[index].name ?? ''), + id: viewModel.courses[index].id ?? 0, + level: viewModel.courses[index].name ?? ''), ), separatorBuilder: (context, index) => verticalSpaceSmall, ); diff --git a/lib/ui/views/learn_course/learn_course_viewmodel.dart b/lib/ui/views/learn_course/learn_course_viewmodel.dart index 03c0066..eda1fe6 100644 --- a/lib/ui/views/learn_course/learn_course_viewmodel.dart +++ b/lib/ui/views/learn_course/learn_course_viewmodel.dart @@ -5,21 +5,25 @@ import 'package:yimaru_app/app/app.router.dart'; import '../../../app/app.locator.dart'; import '../../../models/learn_course.dart'; import '../../../services/api_service.dart'; +import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; -class LearnCourseViewModel extends BaseViewModel { +class LearnCourseViewModel extends ReactiveViewModel { // Dependency injection - final _apiService = locator(); + final _learnService = locator(); final _statusChecker = locator(); final _navigationService = locator(); - // Learn courses - List _learnCourses = []; + @override + List get listenableServices => [_learnService]; - List get learnCourses => _learnCourses; + // Learn lessons + List get _courses => _learnService.courses; + + List get courses => _courses; // Navigation void pop() => _navigationService.back(); @@ -46,12 +50,8 @@ class LearnCourseViewModel extends BaseViewModel { busyObject: StateObjects.learnCourses); Future _getLearnCourses(int id) async { - if (_learnCourses.isEmpty) { - if (await _statusChecker.checkConnection()) { - _learnCourses = await _apiService.getLearnCourse(id); - _learnCourses - .sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); - } + if (await _statusChecker.checkConnection()) { + await _learnService.getLearnCourses(id); } } } diff --git a/lib/ui/views/learn_lesson/learn_lesson_view.dart b/lib/ui/views/learn_lesson/learn_lesson_view.dart index 3e85e0c..e707403 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_view.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_view.dart @@ -133,7 +133,11 @@ class LearnLessonView extends StackedView { style: style14DG500, ); - Widget _buildModuleProgress() => const ModuleProgress(); + Widget _buildModuleProgress() => ModuleProgress( + total: module.access?.totalCount ?? 0, + completed: module.access?.completedCount ?? 0, + progress: module.access?.progressPercent ?? 0, + ); Widget _buildMotivationCard() => const MotivationCard(); @@ -159,6 +163,7 @@ class LearnLessonView extends StackedView { index: index, lesson: viewModel.lessons[index], onLessonTap: () async => await viewModel.navigateToLearnLessonDetail( + module: module, lesson: viewModel.lessons[index], hasPractice: index != viewModel.lessons.length - 1 ? true : false), diff --git a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart index 6b00a18..3bc8745 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart @@ -5,19 +5,23 @@ import 'package:yimaru_app/models/learn_lesson.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../app/app.locator.dart'; -import '../../../services/api_service.dart'; +import '../../../models/learn_module.dart'; +import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; -class LearnLessonViewModel extends BaseViewModel { +class LearnLessonViewModel extends ReactiveViewModel { // Dependency injection - final _apiService = locator(); + final _learnService = locator(); final _statusChecker = locator(); final _navigationService = locator(); + @override + List get listenableServices => [_learnService]; + // Learn lessons - List _lessons = []; + List get _lessons => _learnService.lessons; List get lessons => _lessons; @@ -35,9 +39,11 @@ class LearnLessonViewModel extends BaseViewModel { ); Future navigateToLearnLessonDetail( - {required bool hasPractice, required LearnLesson lesson}) async => + {required bool hasPractice, + required LearnLesson lesson, + required LearnModule module}) async => await _navigationService.navigateToLearnLessonDetailView( - lesson: lesson, hasPractice: hasPractice); + lesson: lesson, module: module, hasPractice: hasPractice); // Remote api call @@ -46,11 +52,8 @@ class LearnLessonViewModel extends BaseViewModel { busyObject: StateObjects.learnLessons); Future _getLessons(int id) async { - if (_lessons.isEmpty) { - if (await _statusChecker.checkConnection()) { - _lessons = await _apiService.getLearnLessons(id); - _lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); - } + if (await _statusChecker.checkConnection()) { + await _learnService.getLearnLessons(id); } } } diff --git a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart index f301d99..371ec46 100644 --- a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart @@ -1,8 +1,8 @@ import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; -import 'package:vimeo_video_player/vimeo_video_player.dart'; import 'package:yimaru_app/models/learn_lesson.dart'; +import 'package:yimaru_app/models/learn_module.dart'; import '../../common/app_colors.dart'; import '../../common/enmus.dart'; @@ -14,10 +14,14 @@ import 'learn_lesson_detail_viewmodel.dart'; class LearnLessonDetailView extends StackedView { final bool hasPractice; + final LearnModule module; final LearnLesson lesson; const LearnLessonDetailView( - {Key? key, required this.lesson, required this.hasPractice}) + {Key? key, + required this.lesson, + required this.module, + required this.hasPractice}) : super(key: key); Future _navigate(LearnLessonDetailViewModel viewModel) async { @@ -27,7 +31,11 @@ class LearnLessonDetailView extends StackedView { @override void onViewModelReady(LearnLessonDetailViewModel viewModel) async { - await viewModel.initializePlayer(lesson.videoUrl ?? ''); + await viewModel.initializePlayer( + lessonId: lesson.id ?? 0, + moduleId: module.id ?? 0, + url: lesson.videoUrl ?? '', + ); super.onViewModelReady(viewModel); } @@ -144,8 +152,9 @@ class LearnLessonDetailView extends StackedView { Widget _buildVideoPlayer(LearnLessonDetailViewModel viewModel) => _buildChewiePlayer(viewModel); - Widget _buildChewiePlayer(LearnLessonDetailViewModel viewModel) => - Chewie(controller: viewModel.chewieController!); + Widget _buildChewiePlayer(LearnLessonDetailViewModel viewModel) => Chewie( + controller: viewModel.chewieController!, + ); Widget _buildEmptyVideoPlayer() => const EmptyVideoPlayer(); diff --git a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart index aec507f..2f9e3d2 100644 --- a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart @@ -1,5 +1,4 @@ import 'package:chewie/chewie.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:video_player/video_player.dart'; @@ -7,19 +6,37 @@ import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../app/app.locator.dart'; import '../../../app/app.router.dart'; +import '../../../models/learn_lesson.dart'; +import '../../../services/api_service.dart'; +import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../../services/vimeo_service.dart'; -import '../../common/app_constants.dart'; -import '../../common/helper_functions.dart'; import '../../common/ui_helpers.dart'; -class LearnLessonDetailViewModel extends BaseViewModel { +class LearnLessonDetailViewModel extends ReactiveViewModel { // Dependency injection + + final _apiService = locator(); + + final _learnService = locator(); + final _vimeoService = locator(); + final _statusChecker = locator(); + final _navigationService = locator(); + @override + List get listenableServices => [_learnService]; + + // Learn lessons + List get _lessons => _learnService.lessons; + + List get lessons => _lessons; + // Video player config + bool _lessonCompleted = false; + ChewieController? _chewieController; ChewieController? get chewieController => _chewieController; @@ -38,11 +55,18 @@ class LearnLessonDetailViewModel extends BaseViewModel { await _chewieController?.pause(); } - Future initializePlayer(String url) async => - await runBusyFuture(_initializePlayer(url), + Future initializePlayer( + {required String url, + required int lessonId, + required int moduleId}) async => + await runBusyFuture( + _initializePlayer(url: url, moduleId: moduleId, lessonId: lessonId), busyObject: StateObjects.loadLessonVideo); - Future _initializePlayer(String url) async { + Future _initializePlayer( + {required String url, + required int lessonId, + required int moduleId}) async { final playableUrl = await _vimeoService.getVideoUrl(url); if (playableUrl == null) { @@ -54,11 +78,41 @@ class LearnLessonDetailViewModel extends BaseViewModel { await _videoPlayerController?.initialize(); + // Listen for video completion + _videoPlayerController?.addListener(() async { + final controller = _videoPlayerController; + + if (controller == null || _lessonCompleted) return; + + final position = controller.value.position.inSeconds; + final duration = controller.value.duration.inSeconds; + + if (duration <= 0) return; + + // Calculate watched percentage + final progress = position / duration; + + // Mark complete at 95% + if (progress >= 0.95) { + _lessonCompleted = true; + + await completeLearnLesson( + lessonId: lessonId, + moduleId: moduleId, + ); + } + }); + _chewieController = ChewieController( - videoPlayerController: _videoPlayerController!, + looping: false, autoPlay: true, - looping: true, + showOptions: true, + showControls: true, aspectRatio: 16 / 9, + autoInitialize: true, + allowedScreenSleep: false, + videoPlayerController: _videoPlayerController!, + materialProgressColors: buildChewieProgressIndicator, ); notifyListeners(); @@ -76,4 +130,24 @@ class LearnLessonDetailViewModel extends BaseViewModel { subtitle: 'I’ll ask you a few questions, and you can respond naturally.', ); + + // Remote api call + + // Mark lesson + Future completeLearnLesson( + {required int lessonId, required int moduleId}) async => + await runBusyFuture( + _completeLearnLesson(lessonId: lessonId, moduleId: moduleId), + busyObject: StateObjects.learnLessons); + + Future _completeLearnLesson( + {required int lessonId, required int moduleId}) async { + if (await _statusChecker.checkConnection()) { + Map response = + await _apiService.completeLearnLesson(lessonId); + if (response['status'] == ResponseStatus.success) { + await _learnService.getLearnLessons(moduleId); + } + } + } } diff --git a/lib/ui/views/learn_module/learn_module_view.dart b/lib/ui/views/learn_module/learn_module_view.dart index e7e337a..fe15390 100644 --- a/lib/ui/views/learn_module/learn_module_view.dart +++ b/lib/ui/views/learn_module/learn_module_view.dart @@ -98,6 +98,7 @@ class LearnModuleView extends StackedView { Widget _buildOverallProgress() => OverallLearnProgress( indicatorBackgroundColor: kcWhite, + progress: course.access?.progressPercent ?? 0, backgroundColor: kcPrimaryColor.withOpacity(0.1), ); @@ -116,6 +117,7 @@ class LearnModuleView extends StackedView { physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTile( module: viewModel.modules[index], + onLockTap: () async => await viewModel.navigateToLearnSubscription(), onPracticeTap: () async => await viewModel.navigateToLearnPractice( id: viewModel.modules[index].id ?? 0, module: viewModel.modules[index].name ?? ''), @@ -126,11 +128,13 @@ class LearnModuleView extends StackedView { Widget _buildTile({ required LearnModule module, + required GestureTapCallback onLockTap, required GestureTapCallback onModuleTap, required GestureTapCallback onPracticeTap, }) => LearnModuleTile( module: module, + onLockTap: onLockTap, onModuleTap: onModuleTap, onPracticeTap: onPracticeTap); } diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index a5b4ce5..e408fc5 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -5,25 +5,32 @@ import 'package:yimaru_app/models/learn_module.dart'; import '../../../app/app.locator.dart'; import '../../../services/api_service.dart'; +import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; -class LearnModuleViewModel extends BaseViewModel { +class LearnModuleViewModel extends ReactiveViewModel { // Dependency injection - final _apiService = locator(); + final _learnService = locator(); final _statusChecker = locator(); final _navigationService = locator(); + @override + List get listenableServices => [_learnService]; + // Learn module - List _modules = []; + List get _modules => _learnService.modules; List get modules => _modules; // Navigation void pop() => _navigationService.back(); + Future navigateToLearnSubscription() async => + await _navigationService.navigateToLearnSubscriptionView(); + Future navigateToLearnLesson(LearnModule module) async => await _navigationService.navigateToLearnLessonView(module: module); @@ -45,11 +52,8 @@ class LearnModuleViewModel extends BaseViewModel { busyObject: StateObjects.learnModules); Future _getLearnModules(int id) async { - if (_modules.isEmpty) { - if (await _statusChecker.checkConnection()) { - _modules = await _apiService.getLearnModules(id); - _modules.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); - } + if (await _statusChecker.checkConnection()) { + await _learnService.getLearnModules(id); } } } diff --git a/lib/ui/views/learn_practice/learn_practice_view.dart b/lib/ui/views/learn_practice/learn_practice_view.dart index 8354807..dafbd0c 100644 --- a/lib/ui/views/learn_practice/learn_practice_view.dart +++ b/lib/ui/views/learn_practice/learn_practice_view.dart @@ -93,7 +93,7 @@ class LearnPracticeView extends StackedView { ); Widget _buildBodyState(LearnPracticeViewModel viewModel) => - viewModel.busy(StateObjects.learnPractices) + viewModel.busy(StateObjects.learnPractice) ? const PageLoadingIndicator() : viewModel.practices.isEmpty || viewModel.questions.isEmpty ? _buildPageLoadingIndicator(viewModel) @@ -101,7 +101,7 @@ class LearnPracticeView extends StackedView { Widget _buildPageLoadingIndicator(LearnPracticeViewModel viewModel) => LearnLoadingScreen( - isLoading: viewModel.busy(StateObjects.learnPractices), + isLoading: viewModel.busy(StateObjects.learnPractice), onTap: () async => await viewModel.getLearnPractices(id: id, practice: practice), onPop: viewModel.practices.isEmpty || viewModel.questions.isEmpty @@ -142,6 +142,7 @@ class LearnPracticeView extends StackedView { Widget _buildLearnPracticeResultScreen() => LearnPracticeResultScreen(practice: practice); - Widget _buildLearnPracticeCompletionScreen() => - LearnPracticeCompletionScreen(level: level ?? ''); + Widget _buildLearnPracticeCompletionScreen() => LearnPracticeCompletionScreen( + level: level ?? '', + ); } diff --git a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart index 8ec654a..a6a5148 100644 --- a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart +++ b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart @@ -13,13 +13,17 @@ import '../../../app/app.locator.dart'; import '../../../models/learn_question.dart'; import '../../../services/api_service.dart'; import '../../../services/audio_player_service.dart'; +import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/app_colors.dart'; class LearnPracticeViewModel extends ReactiveViewModel { // Dependency injection + final _apiService = locator(); + final _learnService = locator(); + final _dialogService = locator(); final _statusChecker = locator(); @@ -37,8 +41,12 @@ class LearnPracticeViewModel extends ReactiveViewModel { } @override - List get listenableServices => - [_audioPlayerService, _voiceRecorderService, _authenticationService]; + List get listenableServices => [ + _learnService, + _audioPlayerService, + _voiceRecorderService, + _authenticationService + ]; // User User? get _user => _authenticationService.user; @@ -256,7 +264,7 @@ class LearnPracticeViewModel extends ReactiveViewModel { Future getLearnPractices( {required int id, required LearnPractices practice}) async => await runBusyFuture(_getLearnPractices(id: id, practice: practice), - busyObject: StateObjects.learnPractices); + busyObject: StateObjects.learnPractice); Future _getLearnPractices( {required int id, required LearnPractices practice}) async { @@ -279,4 +287,15 @@ class LearnPracticeViewModel extends ReactiveViewModel { Future _getLearnPracticeQuestions(int id) async { _questions = await _apiService.getLearnQuestions(id); } + + // Complete practice + Future completeLearnPractices() async => + await runBusyFuture(_completeLearnPractices(), + busyObject: StateObjects.completeLearnPractice); + + Future _completeLearnPractices() async { + if (await _statusChecker.checkConnection()) { + await _apiService.completeLearnPractice(_practices.first.id ?? 0); + } + } } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart index 62b1bc2..8064403 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart @@ -3,9 +3,12 @@ import 'package:flutter_svg/svg.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; +import '../../../../models/learn_practice.dart'; import '../../../common/app_colors.dart'; +import '../../../common/enmus.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/page_loading_indicator.dart'; class LearnPracticeCompletionScreen extends ViewModelWidget { diff --git a/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart index fdb338c..0144c2a 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart @@ -9,7 +9,6 @@ import '../../../common/ui_helpers.dart'; import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/small_app_bar.dart'; -import '../../../widgets/speaking_partner_image.dart'; class LearnPracticeDescriptionScreen extends ViewModelWidget { @@ -121,6 +120,7 @@ class LearnPracticeDescriptionScreen _buildSubtitle(viewModel), verticalSpaceMedium, _buildImageContainer(viewModel), + verticalSpaceLarge ]; Widget _buildTitle(LearnPracticeViewModel viewModel) => Text.rich( diff --git a/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart index daf14f8..7b98efd 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart @@ -9,6 +9,7 @@ import '../../../common/app_colors.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../../../widgets/small_app_bar.dart'; class LearnPracticeResultScreen @@ -18,6 +19,7 @@ class LearnPracticeResultScreen const LearnPracticeResultScreen({super.key, required this.practice}); Future _navigate(LearnPracticeViewModel viewModel) async { + await viewModel.completeLearnPractices(); if (practice == LearnPractices.course) { viewModel.goTo(5); } else { @@ -53,9 +55,16 @@ class LearnPracticeResultScreen required LearnPracticeViewModel viewModel}) => Scaffold( backgroundColor: kcBackgroundColor, - body: _buildScaffold(context: context, viewModel: viewModel), + body: _buildScaffoldState(context: context, viewModel: viewModel), ); + Widget _buildScaffoldState( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) => + viewModel.busy(StateObjects.completeLearnPractice) + ? const PageLoadingIndicator() + : _buildScaffold(context: context, viewModel: viewModel); + Widget _buildScaffold( {required BuildContext context, required LearnPracticeViewModel viewModel}) => diff --git a/lib/ui/views/learn_program/learn_program_viewmodel.dart b/lib/ui/views/learn_program/learn_program_viewmodel.dart index d8e5198..5415691 100644 --- a/lib/ui/views/learn_program/learn_program_viewmodel.dart +++ b/lib/ui/views/learn_program/learn_program_viewmodel.dart @@ -7,12 +7,14 @@ import '../../../app/app.locator.dart'; import '../../../models/user.dart'; import '../../../services/api_service.dart'; import '../../../services/authentication_service.dart'; +import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; class LearnProgramViewModel extends ReactiveViewModel { // Dependency injection - final _apiService = locator(); + + final _learnService = locator(); final _statusChecker = locator(); @@ -22,7 +24,7 @@ class LearnProgramViewModel extends ReactiveViewModel { @override List get listenableServices => - [_authenticationService]; + [_learnService, _authenticationService]; // Current user User? get _user => _authenticationService.user; @@ -30,7 +32,7 @@ class LearnProgramViewModel extends ReactiveViewModel { User? get user => _user; // Learn programs - List _learnPrograms = []; + List get _learnPrograms => _learnService.programs; List get learnPrograms => _learnPrograms; @@ -46,12 +48,8 @@ class LearnProgramViewModel extends ReactiveViewModel { busyObject: StateObjects.learnPrograms); Future _getLearnPrograms() async { - if (_learnPrograms.isEmpty) { - if (await _statusChecker.checkConnection()) { - _learnPrograms = await _apiService.getLearnPrograms(); - _learnPrograms - .sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); - } + if (await _statusChecker.checkConnection()) { + await _learnService.getLearnPrograms(); } } } diff --git a/lib/ui/views/learn_subscription/learn_subscription_view.dart b/lib/ui/views/learn_subscription/learn_subscription_view.dart new file mode 100644 index 0000000..18c8fbf --- /dev/null +++ b/lib/ui/views/learn_subscription/learn_subscription_view.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked/stacked_annotations.dart'; +import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.form.dart'; +import 'package:yimaru_app/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart'; +import 'package:yimaru_app/ui/views/learn_subscription/screens/learn_subscription_package_screen.dart'; +import 'package:yimaru_app/ui/widgets/learn_subscription_card.dart'; +import 'package:yimaru_app/ui/widgets/learn_subscription_pricing_section.dart'; + +import '../../common/app_colors.dart'; +import '../../common/ui_helpers.dart'; +import '../../common/validators/form_validator.dart'; +import '../../widgets/custom_elevated_button.dart'; +import '../../widgets/small_app_bar.dart'; +import 'learn_subscription_viewmodel.dart'; + +@FormView(fields: [ + FormTextField(name: 'phoneNumber', validator: FormValidator.validateForm) +]) +class LearnSubscriptionView extends StackedView + with $LearnSubscriptionView { + const LearnSubscriptionView({Key? key}) : super(key: key); + + @override + void onViewModelReady(LearnSubscriptionViewModel viewModel) async { + await viewModel.getLearnSubscriptions(); + _clearData(); + syncFormWithViewModel(viewModel); + super.onViewModelReady(viewModel); + } + + void _clearData() { + phoneNumberController.clear(); + } + + @override + LearnSubscriptionViewModel viewModelBuilder(BuildContext context) => + LearnSubscriptionViewModel(); + + @override + Widget builder( + BuildContext context, + LearnSubscriptionViewModel viewModel, + Widget? child, + ) => + _buildBodyWrapper(viewModel); + + Widget _buildBodyWrapper(LearnSubscriptionViewModel viewModel) => PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, data) { + if (!didPop) { + Future.microtask(() => viewModel.goBack()); + } + }, + child: _buildBody(viewModel)); + + Widget _buildBody(LearnSubscriptionViewModel viewModel) => IndexedStack( + index: viewModel.currentPage, children: _buildScreens(viewModel)); + + List _buildScreens(LearnSubscriptionViewModel viewModel) => [ + _buildLearnSubscriptionPackageScreen(), + _buildLearnSubscriptionFormScreen() + ]; + + Widget _buildLearnSubscriptionPackageScreen() => + const LearnSubscriptionPackageScreen(); + + Widget _buildLearnSubscriptionFormScreen() => + LearnSubscriptionFormScreen(phoneNumberController: phoneNumberController); +} diff --git a/lib/ui/views/learn_subscription/learn_subscription_view.form.dart b/lib/ui/views/learn_subscription/learn_subscription_view.form.dart new file mode 100644 index 0000000..8c8c1ad --- /dev/null +++ b/lib/ui/views/learn_subscription/learn_subscription_view.form.dart @@ -0,0 +1,182 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format width=80 + +// ************************************************************************** +// StackedFormGenerator +// ************************************************************************** + +// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this + +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/validators/form_validator.dart'; + +const bool _autoTextFieldValidation = true; + +const String PhoneNumberValueKey = 'phoneNumber'; + +final Map + _LearnSubscriptionViewTextEditingControllers = {}; + +final Map _LearnSubscriptionViewFocusNodes = {}; + +final Map + _LearnSubscriptionViewTextValidations = { + PhoneNumberValueKey: FormValidator.validateForm, +}; + +mixin $LearnSubscriptionView { + TextEditingController get phoneNumberController => + _getFormTextEditingController(PhoneNumberValueKey); + + FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey); + + TextEditingController _getFormTextEditingController( + String key, { + String? initialValue, + }) { + if (_LearnSubscriptionViewTextEditingControllers.containsKey(key)) { + return _LearnSubscriptionViewTextEditingControllers[key]!; + } + + _LearnSubscriptionViewTextEditingControllers[key] = + TextEditingController(text: initialValue); + return _LearnSubscriptionViewTextEditingControllers[key]!; + } + + FocusNode _getFormFocusNode(String key) { + if (_LearnSubscriptionViewFocusNodes.containsKey(key)) { + return _LearnSubscriptionViewFocusNodes[key]!; + } + _LearnSubscriptionViewFocusNodes[key] = FocusNode(); + return _LearnSubscriptionViewFocusNodes[key]!; + } + + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + void syncFormWithViewModel(FormStateHelper model) { + phoneNumberController.addListener(() => _updateFormData(model)); + + _updateFormData(model, forceValidate: _autoTextFieldValidation); + } + + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + @Deprecated( + 'Use syncFormWithViewModel instead.' + 'This feature was deprecated after 3.1.0.', + ) + void listenToFormUpdated(FormViewModel model) { + phoneNumberController.addListener(() => _updateFormData(model)); + + _updateFormData(model, forceValidate: _autoTextFieldValidation); + } + + /// Updates the formData on the FormViewModel + void _updateFormData(FormStateHelper model, {bool forceValidate = false}) { + model.setData( + model.formValueMap + ..addAll({ + PhoneNumberValueKey: phoneNumberController.text, + }), + ); + + if (_autoTextFieldValidation || forceValidate) { + updateValidationData(model); + } + } + + bool validateFormFields(FormViewModel model) { + _updateFormData(model, forceValidate: true); + return model.isFormValid; + } + + /// Calls dispose on all the generated controllers and focus nodes + void disposeForm() { + // The dispose function for a TextEditingController sets all listeners to null + + for (var controller + in _LearnSubscriptionViewTextEditingControllers.values) { + controller.dispose(); + } + for (var focusNode in _LearnSubscriptionViewFocusNodes.values) { + focusNode.dispose(); + } + + _LearnSubscriptionViewTextEditingControllers.clear(); + _LearnSubscriptionViewFocusNodes.clear(); + } +} + +extension ValueProperties on FormStateHelper { + bool get hasAnyValidationMessage => this + .fieldsValidationMessages + .values + .any((validation) => validation != null); + + bool get isFormValid { + if (!_autoTextFieldValidation) this.validateForm(); + + return !hasAnyValidationMessage; + } + + String? get phoneNumberValue => + this.formValueMap[PhoneNumberValueKey] as String?; + + set phoneNumberValue(String? value) { + this.setData( + this.formValueMap..addAll({PhoneNumberValueKey: value}), + ); + + if (_LearnSubscriptionViewTextEditingControllers.containsKey( + PhoneNumberValueKey)) { + _LearnSubscriptionViewTextEditingControllers[PhoneNumberValueKey]?.text = + value ?? ''; + } + } + + bool get hasPhoneNumber => + this.formValueMap.containsKey(PhoneNumberValueKey) && + (phoneNumberValue?.isNotEmpty ?? false); + + bool get hasPhoneNumberValidationMessage => + this.fieldsValidationMessages[PhoneNumberValueKey]?.isNotEmpty ?? false; + + String? get phoneNumberValidationMessage => + this.fieldsValidationMessages[PhoneNumberValueKey]; +} + +extension Methods on FormStateHelper { + void setPhoneNumberValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage; + + /// Clears text input fields on the Form + void clearForm() { + phoneNumberValue = ''; + } + + /// Validates text input fields on the Form + void validateForm() { + this.setValidationMessages({ + PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), + }); + } +} + +/// Returns the validation message for the given key +String? getValidationMessage(String key) { + final validatorForKey = _LearnSubscriptionViewTextValidations[key]; + if (validatorForKey == null) return null; + + String? validationMessageForKey = validatorForKey( + _LearnSubscriptionViewTextEditingControllers[key]?.text, + ); + + return validationMessageForKey; +} + +/// Updates the fieldsValidationMessages on the FormViewModel +void updateValidationData(FormStateHelper model) => + model.setValidationMessages({ + PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), + }); diff --git a/lib/ui/views/learn_subscription/learn_subscription_viewmodel.dart b/lib/ui/views/learn_subscription/learn_subscription_viewmodel.dart new file mode 100644 index 0000000..ade0e9c --- /dev/null +++ b/lib/ui/views/learn_subscription/learn_subscription_viewmodel.dart @@ -0,0 +1,89 @@ +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; +import 'package:yimaru_app/app/app.router.dart'; +import 'package:yimaru_app/models/learn_subscription.dart'; +import 'package:yimaru_app/models/learn_subscription_request.dart'; + +import '../../../app/app.locator.dart'; +import '../../../services/api_service.dart'; +import '../../../services/status_checker_service.dart'; +import '../../common/enmus.dart'; + +class LearnSubscriptionViewModel extends FormViewModel { + // Dependency injection + final _apiService = locator(); + + final _statusChecker = locator(); + + final _navigationService = locator(); + + // In-app navigation + int _currentPage = 0; + + int get currentPage => _currentPage; + + // Phone number + bool _focusPhoneNumber = false; + + bool get focusPhoneNumber => _focusPhoneNumber; + + // Learn subscriptions + int _selectedIndex = 0; + + int get selectedIndex => _selectedIndex; + + List _subscriptions = []; + + List get subscriptions => _subscriptions; + + // Phone number + void setPhoneNumberFocus() { + _focusPhoneNumber = true; + rebuildUi(); + } + + // In-app navigation + + void goBack() { + if (_currentPage == 0) { + _navigationService.back(); + } else { + _currentPage--; + rebuildUi(); + } + } + + void next() async { + _currentPage++; + rebuildUi(); + } + + // Navigation + void pop() => _navigationService.back(); + + Future navigateToArifPay(String phone) async { + pop(); + await _navigationService.navigateToArifPayView(phone: phone); + } + + //Learn subscriptions + + void setSelectedPricing(int index) { + _selectedIndex = index; + rebuildUi(); + } + + // Remote api call + + // Learn subscriptions + Future getLearnSubscriptions() async => + await runBusyFuture(_getLearnSubscriptions(), + busyObject: StateObjects.learnSubscriptions); + + Future _getLearnSubscriptions() async { + if (await _statusChecker.checkConnection()) { + _subscriptions = await _apiService.getLearnSubscriptions(); + _subscriptions = _subscriptions + _subscriptions + _subscriptions; + } + } +} diff --git a/lib/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart b/lib/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart new file mode 100644 index 0000000..668f24e --- /dev/null +++ b/lib/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/phone_number_prefix.dart'; +import 'package:yimaru_app/ui/widgets/speaking_partner_image.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/ui_helpers.dart'; +import '../../../widgets/small_app_bar.dart'; +import '../../course_practice_question/course_practice_question_view.form.dart'; +import '../../../widgets/custom_bottom_sheet.dart'; +import '../../../widgets/custom_elevated_button.dart'; +import '../learn_subscription_view.form.dart'; + +class LearnSubscriptionFormScreen + extends ViewModelWidget { + final TextEditingController phoneNumberController; + + const LearnSubscriptionFormScreen({ + super.key, + required this.phoneNumberController, + }); + + @override + Widget build(BuildContext context, LearnSubscriptionViewModel viewModel) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(LearnSubscriptionViewModel viewModel) => + Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(LearnSubscriptionViewModel viewModel) => + SafeArea(child: _buildBody(viewModel)); + + Widget _buildBody(LearnSubscriptionViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(viewModel), + ); + + Widget _buildColumn(LearnSubscriptionViewModel viewModel) => Column( + children: [ + verticalSpaceMedium, + _buildAppBar(viewModel), + _buildSubscriptionColumnWrapper(viewModel), + ], + ); + + Widget _buildAppBar(LearnSubscriptionViewModel viewModel) => SmallAppBar( + showBackButton: true, + onPop: viewModel.goBack, + ); + + Widget _buildSubscriptionColumnWrapper( + LearnSubscriptionViewModel viewModel) => + Expanded(child: _buildSubscriptionColumnScrollView(viewModel)); + + Widget _buildSubscriptionColumnScrollView( + LearnSubscriptionViewModel viewModel) => + SingleChildScrollView( + child: _buildSubscriptionColumn(viewModel), + ); + + Widget _buildSubscriptionColumn(LearnSubscriptionViewModel viewModel) => + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildSheetChildren(viewModel), + ); + + List _buildSheetChildren(LearnSubscriptionViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitleWrapper(), + verticalSpaceTiny, + _buildSubtitle(), + verticalSpaceMassive, + _buildPhoneNumberWrapper(viewModel), + if (viewModel.hasPhoneNumberValidationMessage && + viewModel.focusPhoneNumber) + verticalSpaceTiny, + if (viewModel.hasPhoneNumberValidationMessage && + viewModel.focusPhoneNumber) + _buildPhoneNumberValidatorWrapper(viewModel), + verticalSpaceLarge, + _buildContinueButton(viewModel), + verticalSpaceMassive, + _buildSecurePaymentWrapper() + ]; + + Widget _buildTitleWrapper() => Align( + alignment: Alignment.center, + child: _buildTitle(), + ); + + Widget _buildTitle() => Text( + 'Unlock Next Module', + style: style18DG700, + ); + + Widget _buildSubtitle() => Text( + 'Enter payment phone number, this will be used to process the payment.', + style: style14MG400, + textAlign: TextAlign.center, + ); + + Widget _buildPhoneNumberWrapper(LearnSubscriptionViewModel viewModel) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildPhoneNumberChildren(viewModel), + ); + + List _buildPhoneNumberChildren( + LearnSubscriptionViewModel viewModel) => + [ + _buildPhoneNumberPrefix(viewModel), + horizontalSpaceSmall, + _buildPhoneNumberFormFieldWrapper(viewModel), + ]; + + Widget _buildPhoneNumberPrefix(LearnSubscriptionViewModel viewModel) => + PhoneNumberPrefix(selected: viewModel.focusPhoneNumber); + + Widget _buildPhoneNumberFormFieldWrapper( + LearnSubscriptionViewModel viewModel) => + Expanded(child: _buildPhoneNumberFormField(viewModel)); + + Widget _buildPhoneNumberFormField(LearnSubscriptionViewModel viewModel) => + TextFormField( + maxLength: 9, + keyboardType: TextInputType.phone, + controller: phoneNumberController, + onTap: viewModel.setPhoneNumberFocus, + decoration: inputDecoration( + focus: viewModel.focusPhoneNumber, + filled: phoneNumberController.text.isNotEmpty), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + ); + + Widget _buildPhoneNumberValidatorWrapper( + LearnSubscriptionViewModel viewModel) => + viewModel.hasPhoneNumberValidationMessage + ? _buildPhoneNumberValidator(viewModel) + : Container(); + + Widget _buildPhoneNumberValidator(LearnSubscriptionViewModel viewModel) => + Text( + viewModel.phoneNumberValidationMessage!, + style: style12R700, + ); + + Widget _buildContinueButton(LearnSubscriptionViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + foregroundColor: kcWhite, + text: 'Proceed to Payment', + backgroundColor: phoneNumberController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.1), + onTap: phoneNumberController.text.isNotEmpty + ? () async => + await viewModel.navigateToArifPay(phoneNumberController.text) + : null, + ); + + Widget _buildSecurePaymentWrapper() => Align( + alignment: Alignment.center, + child: _buildSecurePayment(), + ); + + Widget _buildSecurePayment() => Row( + mainAxisSize: MainAxisSize.min, + children: _buildSecurePaymentChildren(), + ); + + List _buildSecurePaymentChildren() => + [_buildTileLeading(), horizontalSpaceTiny, _buildTileTitle()]; + + Widget _buildTileLeading() => const Icon( + Icons.lock_outline_rounded, + size: 16, + color: kcMediumGrey, + ); + + Widget _buildTileTitle() => Text( + 'Unlock All Lessons & Practices', + style: style14MG400, + textAlign: TextAlign.center, + ); +} diff --git a/lib/ui/views/learn_subscription/screens/learn_subscription_package_screen.dart b/lib/ui/views/learn_subscription/screens/learn_subscription_package_screen.dart new file mode 100644 index 0000000..7db45ea --- /dev/null +++ b/lib/ui/views/learn_subscription/screens/learn_subscription_package_screen.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; +import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/ui_helpers.dart'; +import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/learn_subscription_card.dart'; +import '../../../widgets/learn_subscription_pricing_section.dart'; +import '../../../widgets/small_app_bar.dart'; + +class LearnSubscriptionPackageScreen + extends ViewModelWidget { + const LearnSubscriptionPackageScreen({super.key}); + + @override + Widget build(BuildContext context, LearnSubscriptionViewModel viewModel) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(LearnSubscriptionViewModel viewModel) => + Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldState(viewModel), + ); + + Widget _buildScaffoldState(LearnSubscriptionViewModel viewModel) => + viewModel.busy(StateObjects.learnSubscriptions) + ? const PageLoadingIndicator() + : _buildScaffold(viewModel); + Widget _buildScaffold(LearnSubscriptionViewModel viewModel) => + SafeArea(child: _buildBody(viewModel)); + + Widget _buildBody(LearnSubscriptionViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(viewModel), + ); + + Widget _buildColumn(LearnSubscriptionViewModel viewModel) => Column( + children: [ + verticalSpaceMedium, + _buildAppBar(viewModel), + _buildSubscriptionColumnWrapper(viewModel), + ], + ); + + Widget _buildAppBar(LearnSubscriptionViewModel viewModel) => SmallAppBar( + showBackButton: true, + onPop: viewModel.goBack, + ); + + Widget _buildSubscriptionColumnWrapper( + LearnSubscriptionViewModel viewModel) => + Expanded(child: _buildSubscriptionColumnScrollView(viewModel)); + + Widget _buildSubscriptionColumnScrollView( + LearnSubscriptionViewModel viewModel) => + SingleChildScrollView( + child: _buildSubscriptionColumn(viewModel), + ); + + Widget _buildSubscriptionColumn(LearnSubscriptionViewModel viewModel) => + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildSubscriptionColumnChildren(viewModel), + ); + + List _buildSubscriptionColumnChildren( + LearnSubscriptionViewModel viewModel) => + [ + verticalSpaceSmall, + _buildTitleWrapper(), + verticalSpaceTiny, + _buildSubtitle(), + verticalSpaceMedium, + _buildFirstCard(), + verticalSpaceMedium, + _buildSecondCard(), + verticalSpaceLarge, + _buildHeadingWrapper(), + verticalSpaceSmall, + _buildSubscriptionPricingSection(), + verticalSpaceMedium, + _buildSubscribeButton(viewModel), + verticalSpaceMedium, + _buildSecurePaymentWrapper() + ]; + + Widget _buildTitleWrapper() => Align( + alignment: Alignment.center, + child: _buildTitle(), + ); + + Widget _buildTitle() => Text( + 'Unlock Next Module', + style: style18DG700, + ); + + Widget _buildSubtitle() => Text( + 'Unlock the next level to keep growing your English skills.', + style: style14MG400, + ); + + Widget _buildFirstCard() => const LearnSubscriptionCard( + icon: Icons.school, + title: '180+ New Lessons', + subtitle: 'Access fresh, advanced content', + ); + + Widget _buildSecondCard() => const LearnSubscriptionCard( + icon: Icons.developer_board, + title: 'Mastery Through Practice', + subtitle: 'Practice All Lessons, Modules & Levels', + ); + + Widget _buildHeadingWrapper() => Align( + alignment: Alignment.centerLeft, + child: _buildHeading(), + ); + + Widget _buildHeading() => Text( + 'Choose Your Learning Plan', + style: style16DG600, + ); + + Widget _buildSubscriptionPricingSection() => + const LearnSubscriptionPricingSection(); + + Widget _buildSubscribeButton(LearnSubscriptionViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + text: 'Subscribe Now', + foregroundColor: kcWhite, + onTap: () => viewModel.next(), + backgroundColor: kcPrimaryColor, + ); + + Widget _buildSecurePaymentWrapper() => Align( + alignment: Alignment.center, + child: _buildSecurePayment(), + ); + + Widget _buildSecurePayment() => Row( + mainAxisSize: MainAxisSize.min, + children: _buildSecurePaymentChildren(), + ); + + List _buildSecurePaymentChildren() => + [_buildTileLeading(), horizontalSpaceTiny, _buildTileTitle()]; + + Widget _buildTileLeading() => const Icon( + Icons.lock_outline_rounded, + size: 16, + color: kcMediumGrey, + ); + + Widget _buildTileTitle() => Text( + 'Unlock All Lessons & Practices', + style: style14MG400, + textAlign: TextAlign.center, + ); +} diff --git a/lib/ui/views/onboarding/screens/occupation_form_screen.dart b/lib/ui/views/onboarding/screens/occupation_form_screen.dart index aed9707..7453571 100644 --- a/lib/ui/views/onboarding/screens/occupation_form_screen.dart +++ b/lib/ui/views/onboarding/screens/occupation_form_screen.dart @@ -7,7 +7,6 @@ import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import '../../../widgets/custom_dropdown.dart'; -import '../onboarding_view.form.dart'; class OccupationFormScreen extends ViewModelWidget { const OccupationFormScreen({super.key}); diff --git a/lib/ui/views/profile/profile_viewmodel.dart b/lib/ui/views/profile/profile_viewmodel.dart index e18b087..083e9e4 100644 --- a/lib/ui/views/profile/profile_viewmodel.dart +++ b/lib/ui/views/profile/profile_viewmodel.dart @@ -2,8 +2,6 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/services/image_picker_service.dart'; -import 'package:yimaru_app/services/phone_caller_service.dart'; -import 'package:yimaru_app/ui/common/app_constants.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../app/app.locator.dart'; @@ -12,7 +10,6 @@ import '../../../services/api_service.dart'; import '../../../services/authentication_service.dart'; import '../../../services/google_auth_service.dart'; import '../../../services/status_checker_service.dart'; -import '../../../services/url_launcher_service.dart'; import '../../common/app_colors.dart'; class ProfileViewModel extends ReactiveViewModel { diff --git a/lib/ui/views/telegram_support/telegram_support_viewmodel.dart b/lib/ui/views/telegram_support/telegram_support_viewmodel.dart index 34fc720..9fbe703 100644 --- a/lib/ui/views/telegram_support/telegram_support_viewmodel.dart +++ b/lib/ui/views/telegram_support/telegram_support_viewmodel.dart @@ -13,7 +13,7 @@ class TelegramSupportViewModel extends BaseViewModel { // Launch telegram Future launchTelegram() => - _urlLauncherService.launchUri(kTelegramSupport); + _urlLauncherService.launchUri(kTelegramSupportLink); // Navigation void pop() => _navigationService.back(); diff --git a/lib/ui/views/welcome/welcome_viewmodel.dart b/lib/ui/views/welcome/welcome_viewmodel.dart index 1f41aa7..2e4e9e7 100644 --- a/lib/ui/views/welcome/welcome_viewmodel.dart +++ b/lib/ui/views/welcome/welcome_viewmodel.dart @@ -4,7 +4,6 @@ import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/services/authentication_service.dart'; import '../../../app/app.locator.dart'; -import '../../../services/status_checker_service.dart'; class WelcomeViewModel extends BaseViewModel { // Dependency Injection diff --git a/lib/ui/widgets/course_subcategory_tile.dart b/lib/ui/widgets/course_catalog_tile.dart similarity index 87% rename from lib/ui/widgets/course_subcategory_tile.dart rename to lib/ui/widgets/course_catalog_tile.dart index 1ba1c50..40ddcb2 100644 --- a/lib/ui/widgets/course_subcategory_tile.dart +++ b/lib/ui/widgets/course_catalog_tile.dart @@ -1,21 +1,21 @@ import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; -import '../../models/subcategory.dart'; +import '../../models/course_catalog.dart'; import '../common/app_colors.dart'; import '../common/ui_helpers.dart'; import 'custom_elevated_button.dart'; -class CourseSubcategoryTile extends StatelessWidget { - final Subcategory subcategory; +class CourseCatalogTile extends StatelessWidget { + final CourseCatalog courseCatalog; final GestureTapCallback? onCourseTap; final GestureTapCallback? onPracticeTap; - const CourseSubcategoryTile({ + const CourseCatalogTile({ super.key, this.onCourseTap, this.onPracticeTap, - required this.subcategory, + required this.courseCatalog, }); @override @@ -50,16 +50,14 @@ class CourseSubcategoryTile extends StatelessWidget { ); List _buildExpansionTileChildren() => [ - // _buildProgressRow(), - // verticalSpaceSmall, + _buildProgressRow(), + verticalSpaceSmall, _buildActionButtonWrapper(), verticalSpaceSmall ]; Widget _buildTitle() => Text( - (subcategory.name == null || subcategory.name!.isEmpty) - ? 'Course ${subcategory.id}' - : subcategory.name!, + courseCatalog.name ?? '', style: style16P600, ); @@ -76,12 +74,12 @@ class CourseSubcategoryTile extends StatelessWidget { ); Widget _buildProgressStatus() => const CustomLinearProgressIndicator( - progress: 0.75, + progress: 0, activeColor: kcPrimaryColor, backgroundColor: kcVeryLightGrey); Widget _buildProgress() => const Text( - '75%', + '0%', style: TextStyle(color: kcDarkGrey), ); @@ -113,7 +111,7 @@ class CourseSubcategoryTile extends StatelessWidget { ); Widget _buildExamButtonWrapper() => Expanded( - child: Container(), + child: _buildExamButton(), ); Widget _buildExamButton() => CustomElevatedButton( diff --git a/lib/ui/widgets/course_category_card.dart b/lib/ui/widgets/course_category_card.dart index e22094e..6adcdb9 100644 --- a/lib/ui/widgets/course_category_card.dart +++ b/lib/ui/widgets/course_category_card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:yimaru_app/models/category.dart'; import 'package:yimaru_app/ui/common/helper_functions.dart'; import '../common/app_colors.dart'; @@ -7,11 +6,11 @@ import '../common/app_strings.dart'; import '../common/ui_helpers.dart'; import 'custom_elevated_button.dart'; -class CourseCategoryCard extends StatelessWidget { - final Category category; +class CourseCard extends StatelessWidget { final GestureTapCallback? onTap; + final Map course; - const CourseCategoryCard({super.key, this.onTap, required this.category}); + const CourseCard({super.key, this.onTap, required this.course}); @override Widget build(BuildContext context) => _buildContainer(); @@ -42,12 +41,12 @@ class CourseCategoryCard extends StatelessWidget { ]; Widget _buildTitle() => Text( - category.name ?? '', + course['title'], style: style18DG700, ); Widget _buildSubtitle() => Text( - ksCategorySubtitle, + course['description'], maxLines: 3, style: style16DG400, ); diff --git a/lib/ui/widgets/custom_container_shader.dart b/lib/ui/widgets/custom_container_shader.dart new file mode 100644 index 0000000..797c398 --- /dev/null +++ b/lib/ui/widgets/custom_container_shader.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +import '../common/app_colors.dart'; + +class CustomContainerShader extends StatelessWidget { + const CustomContainerShader({super.key}); + + @override + Widget build(BuildContext context) => _buildContainerShaderWrapper(); + + Widget _buildContainerShaderWrapper() => Positioned.fill( + child: _buildContainerShader(), + ); + + Widget _buildContainerShader() => Container( + decoration: BoxDecoration( + color: kcWhite.withOpacity(0.5), + borderRadius: BorderRadius.circular(5), + ), + ); +} diff --git a/lib/ui/widgets/large_app_bar.dart b/lib/ui/widgets/large_app_bar.dart index 3886135..9d2c675 100644 --- a/lib/ui/widgets/large_app_bar.dart +++ b/lib/ui/widgets/large_app_bar.dart @@ -35,7 +35,7 @@ class LargeAppBar extends StatelessWidget { ); Widget _buildStack() => Stack( - children: [ _buildPattern(),_buildAppBarWrapper()], + children: [_buildPattern(), _buildAppBarWrapper()], ); Widget _buildAppBarWrapper() => Container( diff --git a/lib/ui/widgets/learn_lesson_tile.dart b/lib/ui/widgets/learn_lesson_tile.dart index d710db6..856c6f9 100644 --- a/lib/ui/widgets/learn_lesson_tile.dart +++ b/lib/ui/widgets/learn_lesson_tile.dart @@ -8,6 +8,7 @@ import '../common/app_colors.dart'; import '../common/helper_functions.dart'; import '../common/ui_helpers.dart'; +import 'custom_container_shader.dart'; import 'custom_elevated_button.dart'; import 'custom_linear_progress_indicator.dart'; @@ -17,11 +18,12 @@ class LearnLessonTile extends ViewModelWidget { final GestureTapCallback? onLessonTap; final GestureTapCallback? onPracticeTap; - const LearnLessonTile({super.key, - this.onLessonTap, - this.onPracticeTap, - required this.index, - required this.lesson}); + const LearnLessonTile( + {super.key, + this.onLessonTap, + this.onPracticeTap, + required this.index, + required this.lesson}); @override Widget build(BuildContext context, LearnLessonViewModel viewModel) => @@ -33,41 +35,44 @@ class LearnLessonTile extends ViewModelWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), border: Border.all( - color: kcPrimaryColor.withOpacity(0.1), - // color: ProgressStatuses.pending == status - // ? kcPrimaryColor.withOpacity(0.1) - // : kcGreen.withOpacity(0.1), + color: (lesson.access?.isCompleted ?? false) + ? kcGreen.withOpacity(0.1) + : kcPrimaryColor.withOpacity(0.1), ), ), - child: _buildExpansionTile(viewModel), + child: _buildTileStack(viewModel), + ); + + Widget _buildTileStack(LearnLessonViewModel viewModel) => Stack( + children: [ + _buildExpansionTile(viewModel), + _buildContainerShaderState() + ], ); Widget _buildExpansionTile(LearnLessonViewModel viewModel) => ExpansionTile( - enabled: true, title: _buildTitle(), textColor: kcDarkGrey, showTrailingIcon: true, - initiallyExpanded: false, - trailing: _buildPendingIcon(), + trailing: _buildIconState(), collapsedIconColor: kcDarkGrey, collapsedTextColor: kcDarkGrey, leading: _buildLeadingWrapper(), shape: Border.all(color: kcTransparent), expandedAlignment: Alignment.centerLeft, - backgroundColor: kcPrimaryColor.withOpacity(0.1), + enabled: (lesson.access?.isAccessible ?? false), controlAffinity: ListTileControlAffinity.trailing, expandedCrossAxisAlignment: CrossAxisAlignment.start, tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 15), - collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1), + backgroundColor: (lesson.access?.isCompleted ?? false) + ? kcGreen.withOpacity(0.1) + : kcPrimaryColor.withOpacity(0.1), childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15), - // enabled: (lesson.access?.isAccessible ?? false), - // backgroundColor: ProgressStatuses.pending == status - // ? kcPrimaryColor.withOpacity(0.1) - // : kcGreen.withOpacity(0.1), - // collapsedBackgroundColor: ProgressStatuses.pending == status - // ? kcPrimaryColor.withOpacity(0.1) - // : kcGreen.withOpacity(0.1), - // initiallyExpanded: status != ProgressStatuses.completed ? true : false, + initiallyExpanded: (lesson.access?.isAccessible ?? false) && + !(lesson.access?.isCompleted ?? false), + collapsedBackgroundColor: (lesson.access?.isCompleted ?? false) + ? kcGreen.withOpacity(0.1) + : kcPrimaryColor.withOpacity(0.1), children: _buildExpansionTileChildren(viewModel), ); @@ -83,9 +88,9 @@ class LearnLessonTile extends ViewModelWidget { style: style16DG600, ); - // Widget _buildIconState() => ProgressStatuses.pending == status - // ? _buildPendingIcon() - // : _buildCompleteIcon(); + Widget _buildIconState() => (lesson.access?.isCompleted ?? false) + ? _buildCompleteIcon() + : _buildPendingIcon(); Widget _buildCompleteIcon() => const Icon( Icons.check, @@ -116,14 +121,14 @@ class LearnLessonTile extends ViewModelWidget { _buildActionButtonWrapper(viewModel) ]; - Widget _buildProgress() => const CustomLinearProgressIndicator( - progress: 0, + Widget _buildProgress() => CustomLinearProgressIndicator( activeColor: kcPrimaryColor, backgroundColor: kcVeryLightGrey, + progress: (lesson.access?.progressPercent ?? 0) / 100, ); Widget _buildProgressText() => Text( - 'In Progress', + (lesson.access?.isCompleted ?? false) ? 'Completed' : 'In Progress', style: style14P600, ); @@ -179,4 +184,10 @@ class LearnLessonTile extends ViewModelWidget { trailingIcon: Icons.play_arrow, backgroundColor: kcPrimaryColor, ); + + Widget _buildContainerShaderState() => !(lesson.access?.isAccessible ?? false) + ? _buildContainerShader() + : Container(); + + Widget _buildContainerShader() => const CustomContainerShader(); } diff --git a/lib/ui/widgets/learn_module_tile.dart b/lib/ui/widgets/learn_module_tile.dart index ee08d6c..da011ee 100644 --- a/lib/ui/widgets/learn_module_tile.dart +++ b/lib/ui/widgets/learn_module_tile.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/models/learn_module.dart'; import 'package:yimaru_app/ui/views/learn_module/learn_module_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_container_shader.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart'; @@ -11,11 +12,16 @@ import 'custom_elevated_button.dart'; class LearnModuleTile extends ViewModelWidget { final LearnModule module; + final GestureTapCallback? onLockTap; final GestureTapCallback? onModuleTap; final GestureTapCallback? onPracticeTap; const LearnModuleTile( - {super.key, this.onModuleTap, this.onPracticeTap, required this.module}); + {super.key, + this.onLockTap, + this.onModuleTap, + this.onPracticeTap, + required this.module}); Future _showSheet( {required BuildContext context, @@ -28,8 +34,15 @@ class LearnModuleTile extends ViewModelWidget { @override Widget build(BuildContext context, LearnModuleViewModel viewModel) => - _buildExpansionTileCard(context: context, viewModel: viewModel); + _buildExpansionTileWrapper(context: context, viewModel: viewModel); + Widget _buildExpansionTileWrapper( + {required BuildContext context, + required LearnModuleViewModel viewModel}) => + GestureDetector( + onTap: !(module.access?.isAccessible ?? false) ? onLockTap : null, + child: _buildExpansionTileCard(context: context, viewModel: viewModel), + ); Widget _buildExpansionTileCard( {required BuildContext context, required LearnModuleViewModel viewModel}) => @@ -48,7 +61,7 @@ class LearnModuleTile extends ViewModelWidget { Stack( children: [ _buildExpansionTile(context: context, viewModel: viewModel), - // _buildContainerShaderState() + _buildContainerShaderState() ], ); @@ -57,8 +70,8 @@ class LearnModuleTile extends ViewModelWidget { required LearnModuleViewModel viewModel}) => ExpansionTile( textColor: kcDarkGrey, - initiallyExpanded: true, subtitle: _buildContent(), + trailing: _buildLockIcon(), title: _buildTitleWrapper(), leading: _buildIconWrapper(), collapsedIconColor: kcDarkGrey, @@ -72,11 +85,18 @@ class LearnModuleTile extends ViewModelWidget { expandedCrossAxisAlignment: CrossAxisAlignment.start, tilePadding: const EdgeInsets.symmetric(horizontal: 15), childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15), - showTrailingIcon: (module.access?.isAccessible ?? false) ? true : false, + initiallyExpanded: (module.access?.isAccessible ?? false), + showTrailingIcon: + !(module.access?.isAccessible ?? false) ? true : false, children: _buildExpansionTileChildren(context: context, viewModel: viewModel), ); + Widget _buildLockIcon() => const Icon( + Icons.lock_outline_rounded, + color: kcLightGrey, + ); + Widget _buildIconWrapper() => CircleAvatar( backgroundColor: kcPrimaryColor.withOpacity(0.1), child: _buildIcon(), @@ -144,14 +164,15 @@ class LearnModuleTile extends ViewModelWidget { child: _buildProgressStatus(), ); - Widget _buildProgressStatus() => const CustomLinearProgressIndicator( - progress: 0, - activeColor: kcPrimaryColor, - backgroundColor: kcPrimaryColorLight); + Widget _buildProgressStatus() => CustomLinearProgressIndicator( + activeColor: kcPrimaryColor, + backgroundColor: kcPrimaryColorLight, + progress: (module.access?.progressPercent ?? 0) / 100, + ); - Widget _buildProgress() => const Text( - '0/0', - style: TextStyle(color: kcDarkGrey), + Widget _buildProgress() => Text( + '${module.access?.completedCount ?? 0}/${module.access?.totalCount ?? 0}', + style: style14DG500, ); Widget _buildActionButtonWrapper( @@ -207,17 +228,8 @@ class LearnModuleTile extends ViewModelWidget { ); Widget _buildContainerShaderState() => !(module.access?.isAccessible ?? false) - ? _buildContainerShaderWrapper() + ? _buildContainerShader() : Container(); - Widget _buildContainerShaderWrapper() => Positioned.fill( - child: _buildContainerShader(), - ); - - Widget _buildContainerShader() => Container( - decoration: BoxDecoration( - color: kcWhite.withOpacity(0.5), - borderRadius: BorderRadius.circular(5), - ), - ); + Widget _buildContainerShader() => const CustomContainerShader(); } diff --git a/lib/ui/widgets/learn_subscription_card.dart b/lib/ui/widgets/learn_subscription_card.dart new file mode 100644 index 0000000..e630e9c --- /dev/null +++ b/lib/ui/widgets/learn_subscription_card.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +import '../common/app_colors.dart'; +import '../common/ui_helpers.dart'; + +class LearnSubscriptionCard extends StatelessWidget { + final String title; + final IconData icon; + final String subtitle; + + const LearnSubscriptionCard( + {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, + ); +} diff --git a/lib/ui/widgets/learn_subscription_pricing_card.dart b/lib/ui/widgets/learn_subscription_pricing_card.dart new file mode 100644 index 0000000..c241aa8 --- /dev/null +++ b/lib/ui/widgets/learn_subscription_pricing_card.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +import '../common/app_colors.dart'; +import '../common/ui_helpers.dart'; + +class LearnSubscriptionPricingCard extends StatelessWidget { + final int index; + final String type; + final String price; + final String currency; + final int selectedIndex; + final GestureTapCallback? onTap; + + const LearnSubscriptionPricingCard({ + super.key, + this.onTap, + required this.type, + required this.price, + required this.index, + required this.currency, + required this.selectedIndex, + }); + + @override + Widget build(BuildContext context) => _buildContainerWrapper(); + + Widget _buildContainerWrapper() => GestureDetector( + onTap: onTap, + child: _buildContainer(), + ); + + Widget _buildContainer() => Container( + alignment: Alignment.center, + margin: const EdgeInsets.only(right: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: selectedIndex == index + ? kcPrimaryColor.withValues(alpha: 0.25) + : kcPrimaryColor.withValues(alpha: 0.1), + border: Border.all( + width: selectedIndex == index ? 2 : 1, + color: selectedIndex == index + ? kcPrimaryColor + : kcPrimaryColor.withValues(alpha: 0.25)), + ), + child: _buildColumn(), + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.min, + children: _buildColumnChildren(), + ); + + List _buildColumnChildren() => + [_buildPriceCardTitle(), _buildPriceCardSubtitle()]; + + Widget _buildPriceCardTitle() => Text( + '$price $currency', + style: style16DG600, + ); + + Widget _buildPriceCardSubtitle() => Text( + type, + style: style14DG400, + ); +} diff --git a/lib/ui/widgets/learn_subscription_pricing_section.dart b/lib/ui/widgets/learn_subscription_pricing_section.dart new file mode 100644 index 0000000..a40d185 --- /dev/null +++ b/lib/ui/widgets/learn_subscription_pricing_section.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/learn_subscription_pricing_card.dart'; + +import '../common/ui_helpers.dart'; + +class LearnSubscriptionPricingSection + extends ViewModelWidget { + const LearnSubscriptionPricingSection({super.key}); + + @override + Widget build(BuildContext context, LearnSubscriptionViewModel viewModel) => + _buildContainer(viewModel); + + Widget _buildContainer(LearnSubscriptionViewModel viewModel) => Container( + height: 200, + padding: const EdgeInsets.symmetric(vertical: 15), + decoration: BoxDecoration( + color: kcBackgroundColor, + borderRadius: BorderRadius.circular(5), + border: Border.all(color: kcPrimaryColor.withValues(alpha: 0.25)), + ), + child: _buildColumn(viewModel), + ); + + Widget _buildColumn(LearnSubscriptionViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + children: _buildColumnChildren(viewModel), + ); + + List _buildColumnChildren(LearnSubscriptionViewModel viewModel) => [ + _buildTileWrapper(), + verticalSpaceSmall, + _buildLearnPriceWrapper(viewModel) + ]; + + Widget _buildTileWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildTile(), + ); + + Widget _buildTile() => ListTile( + title: _buildTileTitle(), + leading: _buildTileLeading(), + subtitle: _buildTileSubtitle(), + contentPadding: EdgeInsets.zero, + ); + + Widget _buildTileTitle() => Text( + 'Subscription Plans', + style: style16DG600, + ); + + Widget _buildTileSubtitle() => + Text('This includes Monthly and 3-Month packages', style: style14MG400); + + Widget _buildTileLeading() => const Icon( + Icons.key, + size: 35, + color: kcPrimaryColor, + ); + + Widget _buildLearnPriceWrapper(LearnSubscriptionViewModel viewModel) => + Expanded( + child: _buildLearnPricing(viewModel), + ); + + Widget _buildLearnPricing(LearnSubscriptionViewModel viewModel) => + PageView.builder( + itemCount: viewModel.subscriptions.length, + controller: PageController(viewportFraction: 0.9), + itemBuilder: (context, index) => _buildPriceCard( + index: index, + selectedIndex: viewModel.selectedIndex, + type: viewModel.subscriptions[index].name ?? '', + onTap: () => viewModel.setSelectedPricing(index), + currency: viewModel.subscriptions[index].currency ?? '', + price: viewModel.subscriptions[index].price.toString() ?? '', + ), + ); + + Widget _buildPriceCard( + {required int index, + required String type, + required String price, + required String currency, + required int selectedIndex, + required GestureTapCallback onTap}) => + LearnSubscriptionPricingCard( + type: type, + price: price, + onTap: onTap, + index: index, + currency: currency, + selectedIndex: selectedIndex, + ); +} diff --git a/lib/ui/widgets/module_progress.dart b/lib/ui/widgets/module_progress.dart index 61d4dd3..9f6c5e8 100644 --- a/lib/ui/widgets/module_progress.dart +++ b/lib/ui/widgets/module_progress.dart @@ -5,7 +5,15 @@ import '../common/ui_helpers.dart'; import 'custom_linear_progress_indicator.dart'; class ModuleProgress extends StatelessWidget { - const ModuleProgress({super.key}); + final int total; + final int progress; + final int completed; + + const ModuleProgress( + {super.key, + required this.total, + required this.progress, + required this.completed}); @override Widget build(BuildContext context) => _buildContainer(); @@ -36,17 +44,17 @@ class ModuleProgress extends StatelessWidget { [_buildProgressInfo(), _buildProgress()]; Widget _buildProgressInfo() => Text( - '0% Progress', + '$progress% Progress', style: style16DG400, ); Widget _buildProgress() => Text( - '0/3', + '$completed/$total', style: style14P400, ); - Widget _buildProgressIndicator() => const CustomLinearProgressIndicator( - progress: 0, + Widget _buildProgressIndicator() => CustomLinearProgressIndicator( + progress: progress / 100, activeColor: kcPrimaryColor, backgroundColor: kcVeryLightGrey, ); diff --git a/lib/ui/widgets/overall_learn_progress.dart b/lib/ui/widgets/overall_learn_progress.dart index 8a88082..e79c4cf 100644 --- a/lib/ui/widgets/overall_learn_progress.dart +++ b/lib/ui/widgets/overall_learn_progress.dart @@ -4,10 +4,12 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; class OverallLearnProgress extends StatelessWidget { + final int progress; final Color backgroundColor; final Color indicatorBackgroundColor; const OverallLearnProgress( {super.key, + required this.progress, required this.backgroundColor, required this.indicatorBackgroundColor}); @@ -51,12 +53,12 @@ class OverallLearnProgress extends StatelessWidget { ); Widget _buildProgress() => Text( - '0%', + '$progress%', style: style14P400, ); Widget _buildProgressIndicator() => CustomLinearProgressIndicator( - progress: 0.0, + progress: progress / 100, activeColor: kcPrimaryColor, backgroundColor: indicatorBackgroundColor, ); diff --git a/pubspec.lock b/pubspec.lock index e5946e1..6ee6eba 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -873,7 +873,7 @@ packages: source: hosted version: "0.15.6" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" diff --git a/pubspec.yaml b/pubspec.yaml index 56b6d86..a4a743a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: yimaru_app -version: 0.1.14+16 +version: 0.1.15+17 publish_to: 'none' description: A new Flutter project. @@ -54,6 +54,7 @@ dependencies: flutter_local_notifications: ^20.1.0 internet_connection_checker_plus: ^2.9.1+2 + http: any dev_dependencies: flutter_test: sdk: flutter diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index c0c44d5..2cdea1c 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -20,6 +20,7 @@ import 'package:yimaru_app/services/in_app_update_service.dart'; import 'package:yimaru_app/services/vimeo_service.dart'; import 'package:yimaru_app/services/url_launcher_service.dart'; import 'package:yimaru_app/services/phone_caller_service.dart'; +import 'package:yimaru_app/services/learn_service.dart'; // @stacked-import import 'test_helpers.mocks.dart'; @@ -52,6 +53,9 @@ import 'test_helpers.mocks.dart'; MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec(onMissingStub: OnMissingStub.returnDefault), // @stacked-mock-spec ], ) @@ -79,6 +83,9 @@ void registerServices() { getAndRegisterUrlLauncherService(); getAndRegisterUrlLauncherService(); getAndRegisterPhoneCallerService(); + getAndRegisterLearnLessonService(); + getAndRegisterLearnService(); + getAndRegisterLearnService(); // @stacked-mock-register } @@ -261,6 +268,13 @@ MockPhoneCallerService getAndRegisterPhoneCallerService() { locator.registerSingleton(service); return service; } + +MockLearnService getAndRegisterLearnService() { + _removeRegistrationIfExists(); + final service = MockLearnService(); + locator.registerSingleton(service); + return service; +} // @stacked-mock-create void _removeRegistrationIfExists() { diff --git a/test/viewmodels/course_category_viewmodel_test.dart b/test/services/learn_service_test.dart similarity index 83% rename from test/viewmodels/course_category_viewmodel_test.dart rename to test/services/learn_service_test.dart index 14b6d70..6dfc1d6 100644 --- a/test/viewmodels/course_category_viewmodel_test.dart +++ b/test/services/learn_service_test.dart @@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart'; import '../helpers/test_helpers.dart'; void main() { - group('CourseCategoryViewModel Tests -', () { + group('LearnServiceTest -', () { setUp(() => registerServices()); tearDown(() => locator.reset()); }); diff --git a/test/viewmodels/course_subcategory_viewmodel_test.dart b/test/viewmodels/arif_pay_viewmodel_test.dart similarity index 82% rename from test/viewmodels/course_subcategory_viewmodel_test.dart rename to test/viewmodels/arif_pay_viewmodel_test.dart index 3e98771..e0d875e 100644 --- a/test/viewmodels/course_subcategory_viewmodel_test.dart +++ b/test/viewmodels/arif_pay_viewmodel_test.dart @@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart'; import '../helpers/test_helpers.dart'; void main() { - group('CourseSubcategoryViewModel Tests -', () { + group('ArifPayViewModel Tests -', () { setUp(() => registerServices()); tearDown(() => locator.reset()); }); diff --git a/test/viewmodels/course_catalog_viewmodel_test.dart b/test/viewmodels/course_catalog_viewmodel_test.dart new file mode 100644 index 0000000..4894eb7 --- /dev/null +++ b/test/viewmodels/course_catalog_viewmodel_test.dart @@ -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('CourseCatalogViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/viewmodels/course_unit_viewmodel_test.dart b/test/viewmodels/course_unit_viewmodel_test.dart new file mode 100644 index 0000000..83dc415 --- /dev/null +++ b/test/viewmodels/course_unit_viewmodel_test.dart @@ -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('CourseUnitViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/viewmodels/learn_subscription_viewmodel_test.dart b/test/viewmodels/learn_subscription_viewmodel_test.dart new file mode 100644 index 0000000..183888a --- /dev/null +++ b/test/viewmodels/learn_subscription_viewmodel_test.dart @@ -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('LearnSubscriptionViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +}