diff --git a/assets/images/pattern.png b/assets/images/pattern.png new file mode 100644 index 0000000..5b1ca37 Binary files /dev/null and b/assets/images/pattern.png differ diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index 3c56080..04c7c04 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -510,8 +510,8 @@ class StackedRouter extends _i1.RouterBase { _i22.LearnLessonDetailView: (data) { final args = data.getArgs(nullOk: false); return _i37.MaterialPageRoute( - builder: (context) => - _i22.LearnLessonDetailView(key: args.key, lesson: args.lesson), + builder: (context) => _i22.LearnLessonDetailView( + key: args.key, lesson: args.lesson, hasPractice: args.hasPractice), settings: data, ); }, @@ -519,7 +519,13 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs(nullOk: false); return _i37.MaterialPageRoute( builder: (context) => _i23.LearnPracticeView( - key: args.key, id: args.id, practice: args.practice), + key: args.key, + level: args.level, + id: args.id, + label: args.label, + title: args.title, + practice: args.practice, + subtitle: args.subtitle), settings: data, ); }, @@ -1098,56 +1104,85 @@ class LearnLessonDetailViewArguments { const LearnLessonDetailViewArguments({ this.key, required this.lesson, + required this.hasPractice, }); final _i37.Key? key; final _i40.LearnLesson lesson; + final bool hasPractice; + @override String toString() { - return '{"key": "$key", "lesson": "$lesson"}'; + return '{"key": "$key", "lesson": "$lesson", "hasPractice": "$hasPractice"}'; } @override bool operator ==(covariant LearnLessonDetailViewArguments other) { if (identical(this, other)) return true; - return other.key == key && other.lesson == lesson; + return other.key == key && + other.lesson == lesson && + other.hasPractice == hasPractice; } @override int get hashCode { - return key.hashCode ^ lesson.hashCode; + return key.hashCode ^ lesson.hashCode ^ hasPractice.hashCode; } } class LearnPracticeViewArguments { const LearnPracticeViewArguments({ this.key, + this.level, required this.id, + required this.label, + required this.title, required this.practice, + required this.subtitle, }); final _i37.Key? key; + final String? level; + final int id; + final String label; + + final String title; + final _i41.LearnPractices practice; + final String subtitle; + @override String toString() { - return '{"key": "$key", "id": "$id", "practice": "$practice"}'; + return '{"key": "$key", "level": "$level", "id": "$id", "label": "$label", "title": "$title", "practice": "$practice", "subtitle": "$subtitle"}'; } @override bool operator ==(covariant LearnPracticeViewArguments other) { if (identical(this, other)) return true; - return other.key == key && other.id == id && other.practice == practice; + return other.key == key && + other.level == level && + other.id == id && + other.label == label && + other.title == title && + other.practice == practice && + other.subtitle == subtitle; } @override int get hashCode { - return key.hashCode ^ id.hashCode ^ practice.hashCode; + return key.hashCode ^ + level.hashCode ^ + id.hashCode ^ + label.hashCode ^ + title.hashCode ^ + practice.hashCode ^ + subtitle.hashCode; } } @@ -1817,6 +1852,7 @@ extension NavigatorStateExtension on _i46.NavigationService { Future navigateToLearnLessonDetailView({ _i37.Key? key, required _i40.LearnLesson lesson, + required bool hasPractice, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1824,7 +1860,8 @@ extension NavigatorStateExtension on _i46.NavigationService { transition, }) async { return navigateTo(Routes.learnLessonDetailView, - arguments: LearnLessonDetailViewArguments(key: key, lesson: lesson), + arguments: LearnLessonDetailViewArguments( + key: key, lesson: lesson, hasPractice: hasPractice), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -1833,8 +1870,12 @@ extension NavigatorStateExtension on _i46.NavigationService { Future navigateToLearnPracticeView({ _i37.Key? key, + String? level, required int id, + required String label, + required String title, required _i41.LearnPractices practice, + required String subtitle, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1842,8 +1883,14 @@ extension NavigatorStateExtension on _i46.NavigationService { transition, }) async { return navigateTo(Routes.learnPracticeView, - arguments: - LearnPracticeViewArguments(key: key, id: id, practice: practice), + arguments: LearnPracticeViewArguments( + key: key, + level: level, + id: id, + label: label, + title: title, + practice: practice, + subtitle: subtitle), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -2395,6 +2442,7 @@ extension NavigatorStateExtension on _i46.NavigationService { Future replaceWithLearnLessonDetailView({ _i37.Key? key, required _i40.LearnLesson lesson, + required bool hasPractice, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2402,7 +2450,8 @@ extension NavigatorStateExtension on _i46.NavigationService { transition, }) async { return replaceWith(Routes.learnLessonDetailView, - arguments: LearnLessonDetailViewArguments(key: key, lesson: lesson), + arguments: LearnLessonDetailViewArguments( + key: key, lesson: lesson, hasPractice: hasPractice), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -2411,8 +2460,12 @@ extension NavigatorStateExtension on _i46.NavigationService { Future replaceWithLearnPracticeView({ _i37.Key? key, + String? level, required int id, + required String label, + required String title, required _i41.LearnPractices practice, + required String subtitle, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2420,8 +2473,14 @@ extension NavigatorStateExtension on _i46.NavigationService { transition, }) async { return replaceWith(Routes.learnPracticeView, - arguments: - LearnPracticeViewArguments(key: key, id: id, practice: practice), + arguments: LearnPracticeViewArguments( + key: key, + level: level, + id: id, + label: label, + title: title, + practice: practice, + subtitle: subtitle), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, diff --git a/lib/services/dio_service.dart b/lib/services/dio_service.dart index 2f57469..6bf2d54 100644 --- a/lib/services/dio_service.dart +++ b/lib/services/dio_service.dart @@ -158,6 +158,7 @@ class DioService { return true; } catch (e) { + print('REFRESH EXCEPTION: ${e.toString()}'); await _authenticationService.logout(); await _navigationService.replaceWithLoginView(); return false; diff --git a/lib/services/phone_caller_service.dart b/lib/services/phone_caller_service.dart index 0db16b0..d5da266 100644 --- a/lib/services/phone_caller_service.dart +++ b/lib/services/phone_caller_service.dart @@ -2,7 +2,5 @@ import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart'; class PhoneCallerService { Future call(String phone) async => - await FlutterPhoneDirectCaller.callNumber(phone); - - + await FlutterPhoneDirectCaller.callNumber(phone); } diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 1f07f4a..1f75a8b 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -88,4 +88,4 @@ String kServerClientId = // Other String kPhoneSupport = '+251946396655'; -String kTelegramSupport = '@yimaruacademy2026'; \ No newline at end of file +String kTelegramSupport = '@yimaruacademy2026'; diff --git a/lib/ui/common/app_strings.dart b/lib/ui/common/app_strings.dart index 995114f..0aee3bb 100644 --- a/lib/ui/common/app_strings.dart +++ b/lib/ui/common/app_strings.dart @@ -1,4 +1,3 @@ - const String ksHomeBottomSheetTitle = 'Build Great Apps!'; const String ksSuggestion = diff --git a/lib/ui/common/ui_helpers.dart b/lib/ui/common/ui_helpers.dart index 0318ae6..ff851cd 100644 --- a/lib/ui/common/ui_helpers.dart +++ b/lib/ui/common/ui_helpers.dart @@ -238,6 +238,12 @@ TextStyle style25P600 = const TextStyle( fontWeight: FontWeight.w600, ); +TextStyle style40P900 = const TextStyle( + fontSize: 40, + color: kcPrimaryColor, + fontWeight: FontWeight.w900, +); + TextStyle style25DG600 = const TextStyle( fontSize: 25, color: kcDarkGrey, @@ -309,16 +315,14 @@ TextStyle style14LG400 = const TextStyle( TextStyle style14MG400 = const TextStyle( color: kcMediumGrey, ); +TextStyle style14DG400 = const TextStyle(color: kcDarkGrey); + TextStyle style14DG500 = const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500); TextStyle style18MG500 = const TextStyle( fontSize: 18, color: kcMediumGrey, fontWeight: FontWeight.w500); -TextStyle style14DG400 = const TextStyle( - color: kcDarkGrey, -); - TextStyle style14DG600 = const TextStyle( color: kcDarkGrey, fontWeight: FontWeight.w600, diff --git a/lib/ui/views/assessment/screens/assessment_questions_screen.dart b/lib/ui/views/assessment/screens/assessment_questions_screen.dart index 2cb28bb..2235c4a 100644 --- a/lib/ui/views/assessment/screens/assessment_questions_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_questions_screen.dart @@ -53,62 +53,61 @@ class AssessmentQuestionsScreen extends ViewModelWidget { controller: viewModel.pageController, itemCount: viewModel.assessmentQuestions.length, physics: const NeverScrollableScrollPhysics(), - itemBuilder: (cotext, index) => - _buildBodyScroller( viewModel), + itemBuilder: (cotext, index) => _buildBodyScroller(viewModel), ); - Widget _buildBodyScroller( - AssessmentViewModel viewModel) => + Widget _buildBodyScroller(AssessmentViewModel viewModel) => SingleChildScrollView( - child: _buildBody( viewModel), + child: _buildBody(viewModel), ); - Widget _buildBody( - AssessmentViewModel viewModel) => - Column( + Widget _buildBody(AssessmentViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: _buildBodyChildren( viewModel), + children: _buildBodyChildren(viewModel), ); - List _buildBodyChildren( - AssessmentViewModel viewModel) =>[ - + List _buildBodyChildren(AssessmentViewModel viewModel) => [ verticalSpaceMedium, - _buildTitleState( viewModel), + _buildTitleState(viewModel), verticalSpaceMedium, - _buildAnswersState( viewModel), - _buildContinueButtonWrapper( viewModel) + _buildAnswersState(viewModel), + _buildContinueButtonWrapper(viewModel) ]; - Widget _buildTitleState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex == - viewModel.assessmentQuestions.length ?Container(): _buildTitle(viewModel); - Widget _buildTitle( - AssessmentViewModel viewModel) => - Text( + Widget _buildTitleState(AssessmentViewModel viewModel) => + viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length + ? Container() + : _buildTitle(viewModel); + Widget _buildTitle(AssessmentViewModel viewModel) => Text( 'Q${viewModel.currentQuestionIndex + 1}. ${viewModel.assessmentQuestions[viewModel.currentQuestionIndex].questionText} ', style: style16DG600, ); - Widget _buildAnswersState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex == - viewModel.assessmentQuestions.length ?Container(): _buildAnswers(viewModel); + Widget _buildAnswersState(AssessmentViewModel viewModel) => + viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length + ? Container() + : _buildAnswers(viewModel); - Widget _buildAnswers( - AssessmentViewModel viewModel) => - ListView.builder( + Widget _buildAnswers(AssessmentViewModel viewModel) => ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?.length, + itemCount: viewModel.assessmentQuestions[viewModel.currentQuestionIndex] + .options?.length, itemBuilder: (context, inner) => _buildAnswer( onTap: () => viewModel.setSelectedAnswer( question: viewModel.currentQuestionIndex + 1, - option: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner]), - title: - viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner].optionText ?? - '', + option: viewModel + .assessmentQuestions[viewModel.currentQuestionIndex] + .options?[inner]), + title: viewModel.assessmentQuestions[viewModel.currentQuestionIndex] + .options?[inner].optionText ?? + '', selected: viewModel.isSelectedAnswer( - question: viewModel.currentQuestionIndex + 1, + question: viewModel.currentQuestionIndex + 1, answer: viewModel - .assessmentQuestions[viewModel.currentQuestionIndex ].options?[inner].optionText ?? + .assessmentQuestions[viewModel.currentQuestionIndex] + .options?[inner] + .optionText ?? ''), ), ); @@ -123,30 +122,27 @@ class AssessmentQuestionsScreen extends ViewModelWidget { selected: selected, ); - Widget _buildContinueButtonWrapper( - AssessmentViewModel viewModel) => - Padding( + Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding( padding: const EdgeInsets.only(bottom: 50), - child: _buildContinueButton( viewModel), + child: _buildContinueButton(viewModel), ); - Widget _buildContinueButton( - AssessmentViewModel viewModel) => + Widget _buildContinueButton(AssessmentViewModel viewModel) => CustomElevatedButton( height: 55, borderRadius: 12, foregroundColor: kcWhite, text: viewModel.currentQuestionIndex == - viewModel.assessmentQuestions.length - 1 + viewModel.assessmentQuestions.length - 1 ? 'Finish Level' : 'Continue', - backgroundColor: - viewModel.selectedAnswers.containsKey('${viewModel.currentQuestionIndex + 1}') - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1), - onTap: viewModel.selectedAnswers.containsKey('${viewModel.currentQuestionIndex + 1}') + backgroundColor: viewModel.selectedAnswers + .containsKey('${viewModel.currentQuestionIndex + 1}') + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.1), + onTap: viewModel.selectedAnswers + .containsKey('${viewModel.currentQuestionIndex + 1}') ? () => viewModel.nextQuestion() : null, - ); } diff --git a/lib/ui/views/call_support/call_support_view.dart b/lib/ui/views/call_support/call_support_view.dart index 57c1c3e..68641b0 100644 --- a/lib/ui/views/call_support/call_support_view.dart +++ b/lib/ui/views/call_support/call_support_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_constants.dart'; import '../../common/app_colors.dart'; import '../../common/ui_helpers.dart'; @@ -83,9 +84,7 @@ class CallSupportView extends StackedView { verticalSpaceMedium, _buildTitle(), verticalSpaceMedium, - _buildSubtitle('+2519012345678'), - verticalSpaceSmall, - _buildSubtitle('+2519012345678'), + _buildSubtitle(kPhoneSupport), ]; Widget _buildIcon() => @@ -109,12 +108,13 @@ class CallSupportView extends StackedView { ); Widget _buildContinueButton(CallSupportViewModel viewModel) => - const CustomElevatedButton( + CustomElevatedButton( height: 55, borderRadius: 12, text: 'Tap to Call', leadingIcon: Icons.call, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, + onTap: () async => await viewModel.callSupport(), ); } diff --git a/lib/ui/views/call_support/call_support_viewmodel.dart b/lib/ui/views/call_support/call_support_viewmodel.dart index f618f8c..b36a784 100644 --- a/lib/ui/views/call_support/call_support_viewmodel.dart +++ b/lib/ui/views/call_support/call_support_viewmodel.dart @@ -2,11 +2,18 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import '../../../app/app.locator.dart'; +import '../../../services/phone_caller_service.dart'; +import '../../common/app_constants.dart'; class CallSupportViewModel extends BaseViewModel { // Dependency injection final _navigationService = locator(); + final _phoneCallerService = locator(); + + // Call support + Future callSupport() => _phoneCallerService.call(kPhoneSupport); + // Navigation void pop() => _navigationService.back(); } diff --git a/lib/ui/views/forget_password/forget_password_viewmodel.dart b/lib/ui/views/forget_password/forget_password_viewmodel.dart index 4e2fef3..10f672a 100644 --- a/lib/ui/views/forget_password/forget_password_viewmodel.dart +++ b/lib/ui/views/forget_password/forget_password_viewmodel.dart @@ -40,14 +40,6 @@ class ForgetPasswordViewModel extends FormViewModel { bool get length => _length; - bool _number = false; - - bool get number => _number; - - bool _specialChar = false; - - bool get specialChar => _specialChar; - bool _focusPassword = false; bool get focusPassword => _focusPassword; @@ -105,11 +97,9 @@ class ForgetPasswordViewModel extends FormViewModel { int completed = 0; if (_length) completed++; - if (_number) completed++; - if (_specialChar) completed++; if (_passwordMatch) completed++; - return completed / 4; // returns 0.0 → 1.0 + return completed / 2; // returns 0.0 → 1.0 } void validatePassword( @@ -120,17 +110,7 @@ class ForgetPasswordViewModel extends FormViewModel { _length = false; } - if (RegExp(r'\d').hasMatch(password)) { - _number = true; - } else { - _number = false; - } - if (RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(password)) { - _specialChar = true; - } else { - _specialChar = false; - } if (password == confirmPassword) { _passwordMatch = true; @@ -156,8 +136,6 @@ class ForgetPasswordViewModel extends FormViewModel { // Reset reset password screen void resetResetPasswordScreen() { _length = false; - _number = false; - _specialChar = false; _passwordMatch = false; _focusPassword = false; _focusResetCode = false; diff --git a/lib/ui/views/forget_password/screens/reset_password_screen.dart b/lib/ui/views/forget_password/screens/reset_password_screen.dart index 159c936..1e89530 100644 --- a/lib/ui/views/forget_password/screens/reset_password_screen.dart +++ b/lib/ui/views/forget_password/screens/reset_password_screen.dart @@ -140,8 +140,6 @@ class ResetPasswordScreen extends ViewModelWidget { _buildLinearProgressIndicator(viewModel), verticalSpaceSmall, _buildCharLengthValidator(viewModel), - _buildNumberValidator(viewModel), - _buildSymbolValidator(viewModel), _buildPasswordMatchValidator(viewModel), verticalSpaceSmall, _buildSignUpButton(viewModel), @@ -256,16 +254,6 @@ class ResetPasswordScreen extends ViewModelWidget { backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey, label: '8 characters minimum'); - Widget _buildNumberValidator(ForgetPasswordViewModel viewModel) => - ValidatorListTile( - backgroundColor: viewModel.number ? kcPrimaryColor : kcLightGrey, - label: 'a number'); - - Widget _buildSymbolValidator(ForgetPasswordViewModel viewModel) => - ValidatorListTile( - backgroundColor: viewModel.specialChar ? kcPrimaryColor : kcLightGrey, - label: 'one symbol minimum'); - Widget _buildPasswordMatchValidator(ForgetPasswordViewModel viewModel) => ValidatorListTile( backgroundColor: @@ -281,20 +269,14 @@ class ResetPasswordScreen extends ViewModelWidget { onTap: passwordController.text.isNotEmpty && confirmPasswordController.text.isNotEmpty && resetCodeController.text.isNotEmpty && - viewModel.number && viewModel.length && - viewModel.specialChar && - viewModel.specialChar && viewModel.passwordMatch ? () async => await _reset(viewModel) : null, backgroundColor: passwordController.text.isNotEmpty && confirmPasswordController.text.isNotEmpty && resetCodeController.text.isNotEmpty && - viewModel.number && viewModel.length && - viewModel.specialChar && - viewModel.specialChar && viewModel.passwordMatch ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1), diff --git a/lib/ui/views/learn_course/learn_course_view.dart b/lib/ui/views/learn_course/learn_course_view.dart index b36b9f8..1e745aa 100644 --- a/lib/ui/views/learn_course/learn_course_view.dart +++ b/lib/ui/views/learn_course/learn_course_view.dart @@ -84,8 +84,9 @@ class LearnCourseView extends StackedView { course: viewModel.learnCourses[index], onViewTap: () async => await viewModel .navigateToLearnModule(viewModel.learnCourses[index]), - onPracticeTap: () async => await viewModel - .navigateToLearnPractice(viewModel.learnCourses[index].id ?? 0), + onPracticeTap: () async => await viewModel.navigateToLearnPractice( + id: viewModel.learnCourses[index].id ?? 0, + level: viewModel.learnCourses[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 c27c5b6..03c0066 100644 --- a/lib/ui/views/learn_course/learn_course_viewmodel.dart +++ b/lib/ui/views/learn_course/learn_course_viewmodel.dart @@ -27,8 +27,16 @@ class LearnCourseViewModel extends BaseViewModel { Future navigateToLearnModule(LearnCourse course) async => _navigationService.navigateToLearnModuleView(course: course); - Future navigateToLearnPractice(int id) async => await _navigationService - .navigateToLearnPracticeView(id: id, practice: LearnPractices.course); + Future navigateToLearnPractice( + {required int id, required String level}) async => + await _navigationService.navigateToLearnPracticeView( + id: id, + level: level, + label: 'Begin Level Practice', + practice: LearnPractices.course, + title: 'Let’s Practice Course $level', + subtitle: 'Let’s quickly review what you’ve learned in this level!', + ); // Remote api call diff --git a/lib/ui/views/learn_lesson/learn_lesson_view.dart b/lib/ui/views/learn_lesson/learn_lesson_view.dart index 98ee354..3e85e0c 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_view.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_view.dart @@ -115,9 +115,9 @@ class LearnLessonView extends StackedView { _buildSubtitle(), verticalSpaceSmall, _buildModuleProgress(), - verticalSpaceMedium, + verticalSpaceLarge, _buildMotivationCard(), - verticalSpaceMedium, + verticalSpaceLarge, _buildHeader(), verticalSpaceMedium, _buildListViewBuilder(viewModel), @@ -156,20 +156,25 @@ class LearnLessonView extends StackedView { itemCount: viewModel.lessons.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTile( + index: index, lesson: viewModel.lessons[index], - onLessonTap: () async => await viewModel - .navigateToLearnLessonDetail(viewModel.lessons[index]), + onLessonTap: () async => await viewModel.navigateToLearnLessonDetail( + lesson: viewModel.lessons[index], + hasPractice: + index != viewModel.lessons.length - 1 ? true : false), onPracticeTap: () async => await viewModel .navigateToLearnPractice(viewModel.lessons[index].id ?? 0), ), ); Widget _buildTile({ + required int index, required LearnLesson lesson, required GestureTapCallback? onLessonTap, required GestureTapCallback? onPracticeTap, }) => LearnLessonTile( + index: index, lesson: lesson, onLessonTap: onLessonTap, onPracticeTap: onPracticeTap, diff --git a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart index b909469..6b00a18 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart @@ -24,11 +24,20 @@ class LearnLessonViewModel extends BaseViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToLearnPractice(int id) async => await _navigationService - .navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson); + Future navigateToLearnPractice(int id) async => + await _navigationService.navigateToLearnPracticeView( + id: id, + label: 'Start Practice', + practice: LearnPractices.lesson, + title: 'Let\'s practice what you just learnt!', + subtitle: + 'I’ll ask you a few questions, and you can respond naturally.', + ); - Future navigateToLearnLessonDetail(LearnLesson lesson) async => - await _navigationService.navigateToLearnLessonDetailView(lesson: lesson); + Future navigateToLearnLessonDetail( + {required bool hasPractice, required LearnLesson lesson}) async => + await _navigationService.navigateToLearnLessonDetailView( + lesson: lesson, hasPractice: hasPractice); // Remote api call 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 e28e4ab..f301d99 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 @@ -13,9 +13,11 @@ import '../../widgets/small_app_bar.dart'; import 'learn_lesson_detail_viewmodel.dart'; class LearnLessonDetailView extends StackedView { + final bool hasPractice; final LearnLesson lesson; - const LearnLessonDetailView({Key? key, required this.lesson}) + const LearnLessonDetailView( + {Key? key, required this.lesson, required this.hasPractice}) : super(key: key); Future _navigate(LearnLessonDetailViewModel viewModel) async { @@ -86,7 +88,7 @@ class LearnLessonDetailView extends StackedView { List _buildBodyColumnChildren(LearnLessonDetailViewModel viewModel) => [ _buildLevelsColumnWrapper(viewModel), - _buildContinueButtonWrapper(viewModel) + if (hasPractice) _buildPracticeButtonWrapper(viewModel) ]; Widget _buildLevelsColumnWrapper(LearnLessonDetailViewModel viewModel) => @@ -157,21 +159,21 @@ class LearnLessonDetailView extends StackedView { style: style14DG400, ); - Widget _buildContinueButtonWrapper(LearnLessonDetailViewModel viewModel) => + Widget _buildPracticeButtonWrapper(LearnLessonDetailViewModel viewModel) => Padding( padding: const EdgeInsets.only( left: 15, right: 15, bottom: 50, ), - child: _buildContinueButton(viewModel), + child: _buildPracticeButton(viewModel), ); - Widget _buildContinueButton(LearnLessonDetailViewModel viewModel) => + Widget _buildPracticeButton(LearnLessonDetailViewModel viewModel) => CustomElevatedButton( height: 55, borderRadius: 12, - text: 'Practices', + text: 'Take Practice', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, onTap: () async => await _navigate(viewModel), 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 defc758..aec507f 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 @@ -67,6 +67,13 @@ class LearnLessonDetailViewModel extends BaseViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToLearnPractice(int id) async => await _navigationService - .navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson); + Future navigateToLearnPractice(int id) async => + await _navigationService.navigateToLearnPracticeView( + id: id, + label: 'Start Practice', + practice: LearnPractices.lesson, + title: 'Let\'s practice what you just learnt!', + subtitle: + 'I’ll ask you a few questions, and you can respond naturally.', + ); } diff --git a/lib/ui/views/learn_module/learn_module_view.dart b/lib/ui/views/learn_module/learn_module_view.dart index 8086d38..e7e337a 100644 --- a/lib/ui/views/learn_module/learn_module_view.dart +++ b/lib/ui/views/learn_module/learn_module_view.dart @@ -116,8 +116,9 @@ class LearnModuleView extends StackedView { physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTile( module: viewModel.modules[index], - onPracticeTap: () async => await viewModel - .navigateToLearnPractice(viewModel.modules[index].id ?? 0), + onPracticeTap: () async => await viewModel.navigateToLearnPractice( + id: viewModel.modules[index].id ?? 0, + module: viewModel.modules[index].name ?? ''), onModuleTap: () async => await viewModel.navigateToLearnLesson(viewModel.modules[index]), ), diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index f350174..a5b4ce5 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -27,8 +27,15 @@ class LearnModuleViewModel extends BaseViewModel { Future navigateToLearnLesson(LearnModule module) async => await _navigationService.navigateToLearnLessonView(module: module); - Future navigateToLearnPractice(int id) async => await _navigationService - .navigateToLearnPracticeView(id: id, practice: LearnPractices.module); + Future navigateToLearnPractice( + {required int id, required String module}) async => + await _navigationService.navigateToLearnPracticeView( + id: id, + label: 'Begin Module Practice', + practice: LearnPractices.module, + title: 'Let’s Practice $module', + subtitle: 'Let’s quickly review what you’ve learned in this module! ', + ); // Remote api call diff --git a/lib/ui/views/learn_practice/learn_practice_view.dart b/lib/ui/views/learn_practice/learn_practice_view.dart index 02b4629..8354807 100644 --- a/lib/ui/views/learn_practice/learn_practice_view.dart +++ b/lib/ui/views/learn_practice/learn_practice_view.dart @@ -16,10 +16,21 @@ import 'learn_practice_viewmodel.dart'; class LearnPracticeView extends StackedView { final int id; + final String label; + final String title; + final String? level; + final String subtitle; final LearnPractices practice; - const LearnPracticeView({Key? key, required this.id, required this.practice}) - : super(key: key); + const LearnPracticeView({ + Key? key, + this.level, + required this.id, + required this.label, + required this.title, + required this.practice, + required this.subtitle, + }) : super(key: key); Future _cancel(LearnPracticeViewModel viewModel) async { await viewModel.stopRecording(); @@ -108,10 +119,17 @@ class LearnPracticeView extends StackedView { _buildLearnPracticeQuestionsScreen(), _buildFinishLearnPracticeScreen(), _buildLearnPracticeResultScreen(), - _buildLearnPracticeCompletionScreen() + if (practice == LearnPractices.course) + _buildLearnPracticeCompletionScreen() ]; - Widget _buildLearnPracticeIntroScreen() => const LearnPracticeIntroScreen(); + Widget _buildLearnPracticeIntroScreen() => LearnPracticeIntroScreen( + level: level, + title: title, + label: label, + practice: practice, + subtitle: subtitle, + ); Widget _buildLearnPracticeElementsScreen() => const LearnPracticeDescriptionScreen(); @@ -121,8 +139,9 @@ class LearnPracticeView extends StackedView { Widget _buildFinishLearnPracticeScreen() => const FinishLearnPracticeScreen(); - Widget _buildLearnPracticeResultScreen() => const LearnPracticeResultScreen(); + Widget _buildLearnPracticeResultScreen() => + LearnPracticeResultScreen(practice: practice); Widget _buildLearnPracticeCompletionScreen() => - const LearnPracticeCompletionScreen(); + 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 f37f157..8ec654a 100644 --- a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart +++ b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart @@ -225,7 +225,7 @@ class LearnPracticeViewModel extends ReactiveViewModel { await stopRecording(); _answers.add({ 'busy_object': question.id.toString(), - 'sample_text_answer': question.audioCorrectAnswerText, + 'question_text': question.questionText, 'sample_voice_answer': question.sampleAnswerVoicePrompt, 'recorded_voice_answer': await _voiceRecorderService.getRecordedAudio(), }); 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 0e29b1f..62b1bc2 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 @@ -9,7 +9,8 @@ import '../../../widgets/custom_elevated_button.dart'; class LearnPracticeCompletionScreen extends ViewModelWidget { - const LearnPracticeCompletionScreen({super.key}); + final String level; + const LearnPracticeCompletionScreen({super.key, required this.level}); @override Widget build(BuildContext context, LearnPracticeViewModel viewModel) => @@ -54,7 +55,7 @@ class LearnPracticeCompletionScreen ); Widget _buildTitle() => Text( - 'Yay, you’ve completed A1 ', + 'Yay, you’ve completed $level ', style: style25DG600, textAlign: TextAlign.center, ); diff --git a/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart index 0cb82be..5229abb 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart @@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import '../../../common/app_colors.dart'; +import '../../../common/enmus.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/custom_elevated_button.dart'; @@ -10,7 +11,19 @@ import '../../../widgets/small_app_bar.dart'; import '../../../widgets/speaking_partner_image.dart'; class LearnPracticeIntroScreen extends ViewModelWidget { - const LearnPracticeIntroScreen({super.key}); + final String title; + final String label; + final String? level; + final String subtitle; + final LearnPractices practice; + + const LearnPracticeIntroScreen( + {super.key, + this.level, + required this.label, + required this.title, + required this.subtitle, + required this.practice}); Future _cancel(LearnPracticeViewModel viewModel) async { await viewModel.stopRecording(); @@ -114,21 +127,44 @@ class LearnPracticeIntroScreen extends ViewModelWidget { List _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) => [ verticalSpaceMassive, - _buildImage(), + _buildImageState(), verticalSpaceMedium, - _buildPartnerName(), + _buildPartnerNameState(), verticalSpaceMedium, _buildTitle(), verticalSpaceMedium, _buildSubtitle() ]; - Widget _buildImage() => const SpeakingPartnerImage( + Widget _buildImageState() => + level != null ? _buildCourseSpeakingPartnerImage() : _buildImage(75); + + Widget _buildCourseSpeakingPartnerImage() => CircleAvatar( radius: 75, + backgroundColor: kcPrimaryColorLight, + child: _buildCourseSpeakingPartnerText(), ); + Widget _buildCourseSpeakingPartnerText() => Text( + level?.toUpperCase() ?? '', + style: style40P900, + ); + + Widget _buildImage(double radius) => SpeakingPartnerImage(radius: radius); + + Widget _buildPartnerNameState() => + level != null ? _buildCourseSpeakingPartner() : _buildPartnerName(); + + Widget _buildCourseSpeakingPartner() => Row( + mainAxisSize: MainAxisSize.min, + children: _buildCourseSpeakingPartnerChildren(), + ); + + List _buildCourseSpeakingPartnerChildren() => + [_buildImage(15), horizontalSpaceTiny, _buildPartnerName()]; + Widget _buildPartnerName() => Text.rich( - TextSpan(text: 'Dawit', style: style14DG600, children: [ + TextSpan(text: 'Daniel', style: style14DG600, children: [ TextSpan( text: ' - Your Speaking Partner', style: style14MG400, @@ -137,13 +173,13 @@ class LearnPracticeIntroScreen extends ViewModelWidget { ); Widget _buildTitle() => Text( - 'Let\'s practice what you just learnt!', + title, style: style25DG600, textAlign: TextAlign.center, ); Widget _buildSubtitle() => Text( - 'I’ll ask you a few questions, and you can respond naturally.', + subtitle, maxLines: 1, style: style14DG400, textAlign: TextAlign.center, @@ -158,8 +194,8 @@ class LearnPracticeIntroScreen extends ViewModelWidget { Widget _buildContinueButton(LearnPracticeViewModel viewModel) => CustomElevatedButton( height: 55, + text: label, borderRadius: 12, - text: 'Practice', foregroundColor: kcWhite, onTap: () => viewModel.goTo(1), backgroundColor: kcPrimaryColor, 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 b4d8060..daf14f8 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 @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/learn_practice_tip_section.dart'; import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart'; @@ -12,9 +13,19 @@ import '../../../widgets/small_app_bar.dart'; class LearnPracticeResultScreen extends ViewModelWidget { - const LearnPracticeResultScreen({super.key}); + final LearnPractices practice; - void _navigate(LearnPracticeViewModel viewModel) async => + const LearnPracticeResultScreen({super.key, required this.practice}); + + Future _navigate(LearnPracticeViewModel viewModel) async { + if (practice == LearnPractices.course) { + viewModel.goTo(5); + } else { + viewModel.pop(); + } + } + + Future _retry(LearnPracticeViewModel viewModel) async => await viewModel.reset(); Future _cancel(LearnPracticeViewModel viewModel) async { @@ -145,8 +156,8 @@ class LearnPracticeResultScreen text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, - onTap: () => viewModel.goTo(5), backgroundColor: kcPrimaryColor, + onTap: () async => await _navigate(viewModel), ); Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) => @@ -157,6 +168,6 @@ class LearnPracticeResultScreen backgroundColor: kcWhite, borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, - onTap: () => _navigate(viewModel), + onTap: () async => await _retry(viewModel), ); } diff --git a/lib/ui/views/onboarding/screens/country_region_form_screen.dart b/lib/ui/views/onboarding/screens/country_region_form_screen.dart index 5c322e9..6592e5e 100644 --- a/lib/ui/views/onboarding/screens/country_region_form_screen.dart +++ b/lib/ui/views/onboarding/screens/country_region_form_screen.dart @@ -98,9 +98,9 @@ class CountryRegionFormScreen extends ViewModelWidget { _buildCountryDropDown(viewModel), verticalSpaceMedium, _buildRegionFormState(viewModel), - if (viewModel.hasRegionValidationMessage && - !viewModel.dropdownRegion && - viewModel.focusRegion) + if (viewModel.hasRegionValidationMessage && + !viewModel.dropdownRegion && + viewModel.focusRegion) verticalSpaceTiny, if (viewModel.hasRegionValidationMessage && !viewModel.dropdownRegion && diff --git a/lib/ui/views/profile/profile_viewmodel.dart b/lib/ui/views/profile/profile_viewmodel.dart index e16caac..e18b087 100644 --- a/lib/ui/views/profile/profile_viewmodel.dart +++ b/lib/ui/views/profile/profile_viewmodel.dart @@ -26,10 +26,6 @@ class ProfileViewModel extends ReactiveViewModel { final _googleAuthService = locator(); - final _phoneCallerService = locator(); - - final _urlLauncherService = locator(); - final _imagePickerService = locator(); final _authenticationService = locator(); @@ -68,13 +64,6 @@ class ProfileViewModel extends ReactiveViewModel { } } - // Launch telegram - Future launchTelegram() => - _urlLauncherService.launchUri(kTelegramSupport); - - // Call support - Future callSupport() => _phoneCallerService.call(kPhoneSupport); - // Dialog Future showAbortDialog() async { DialogResponse? response = await _dialogService.showDialog( diff --git a/lib/ui/views/profile_detail/profile_detail_view.dart b/lib/ui/views/profile_detail/profile_detail_view.dart index 780a963..63dec45 100644 --- a/lib/ui/views/profile_detail/profile_detail_view.dart +++ b/lib/ui/views/profile_detail/profile_detail_view.dart @@ -66,13 +66,14 @@ class ProfileDetailView extends StackedView content: _buildImagePicker(context: context, viewModel: viewModel), ); - void _checkRegion(ProfileDetailViewModel viewModel){ - bool region = viewModel.checkRegion(region:viewModel.user?.region ?? 'Addis Ababa',country:viewModel.user?.country ?? 'Ethiopia' ); - if(region){ + void _checkRegion(ProfileDetailViewModel viewModel) { + bool region = viewModel.checkRegion( + region: viewModel.user?.region ?? 'Addis Ababa', + country: viewModel.user?.country ?? 'Ethiopia'); + if (region) { viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa'); - }else{ + } else { regionController.text = viewModel.user?.region ?? ''; - } } @@ -549,10 +550,12 @@ class ProfileDetailView extends StackedView _buildRegionFormState(viewModel), if (viewModel.hasRegionValidationMessage && !viewModel.dropdownRegion && - viewModel.focusRegion) verticalSpaceTiny, + viewModel.focusRegion) + verticalSpaceTiny, if (viewModel.hasRegionValidationMessage && !viewModel.dropdownRegion && - viewModel.focusRegion) _buildRegionValidatorWrapper(viewModel), + viewModel.focusRegion) + _buildRegionValidatorWrapper(viewModel), ]; Widget _buildRegionFormFieldLabel() => CustomFormLabel( @@ -574,14 +577,15 @@ class ProfileDetailView extends StackedView onChanged: (value) => viewModel.setSelectedRegion(value ?? 'Addis Ababa')); - Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => TextFormField( - controller: regionController, - onTap: viewModel.setRegionFocus, - decoration: inputDecoration( - hint: 'Enter Your City', - focus: viewModel.focusRegion, - filled: regionController.text.isNotEmpty), - ); + Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => + TextFormField( + controller: regionController, + onTap: viewModel.setRegionFocus, + decoration: inputDecoration( + hint: 'Enter Your City', + focus: viewModel.focusRegion, + filled: regionController.text.isNotEmpty), + ); Widget _buildRegionValidatorWrapper(ProfileDetailViewModel viewModel) => viewModel.hasRegionValidationMessage @@ -589,10 +593,9 @@ class ProfileDetailView extends StackedView : Container(); Widget _buildRegionValidator(ProfileDetailViewModel viewModel) => Text( - viewModel.regionValidationMessage!, - style: style12R700, - ); - + viewModel.regionValidationMessage!, + style: style12R700, + ); Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) => Column( @@ -608,7 +611,6 @@ class ProfileDetailView extends StackedView _buildOccupationDropdownLabel(), verticalSpaceSmall, _buildOccupationDropdown(viewModel) - ]; Widget _buildOccupationDropdownLabel() => CustomFormLabel( @@ -625,9 +627,9 @@ class ProfileDetailView extends StackedView onChanged: (value) => viewModel.setSelectedOccupation( value ?? 'Students (High school & University)')); Icon _buildSearchIcon() => const Icon( - Icons.search, - color: kcPrimaryColor, - ); + Icons.search, + color: kcPrimaryColor, + ); Widget _buildLowerColumn(ProfileDetailViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, children: _buildLowerColumnChildren(viewModel), diff --git a/lib/ui/views/profile_detail/profile_detail_view.form.dart b/lib/ui/views/profile_detail/profile_detail_view.form.dart index 23b9060..bfc4010 100644 --- a/lib/ui/views/profile_detail/profile_detail_view.form.dart +++ b/lib/ui/views/profile_detail/profile_detail_view.form.dart @@ -18,7 +18,6 @@ const String RegionValueKey = 'region'; const String PhoneNumberValueKey = 'phoneNumber'; const String LastNameValueKey = 'lastName'; const String FirstNameValueKey = 'firstName'; -const String OccupationValueKey = 'occupation'; final Map _ProfileDetailViewTextEditingControllers = {}; @@ -32,7 +31,6 @@ final Map PhoneNumberValueKey: FormValidator.validatePhoneNumberForm, LastNameValueKey: FormValidator.validateForm, FirstNameValueKey: FormValidator.validateForm, - OccupationValueKey: FormValidator.validateForm, }; mixin $ProfileDetailView { @@ -46,15 +44,12 @@ mixin $ProfileDetailView { _getFormTextEditingController(LastNameValueKey); TextEditingController get firstNameController => _getFormTextEditingController(FirstNameValueKey); - TextEditingController get occupationController => - _getFormTextEditingController(OccupationValueKey); FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey); FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey); FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey); FocusNode get lastNameFocusNode => _getFormFocusNode(LastNameValueKey); FocusNode get firstNameFocusNode => _getFormFocusNode(FirstNameValueKey); - FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey); TextEditingController _getFormTextEditingController( String key, { @@ -85,7 +80,6 @@ mixin $ProfileDetailView { phoneNumberController.addListener(() => _updateFormData(model)); lastNameController.addListener(() => _updateFormData(model)); firstNameController.addListener(() => _updateFormData(model)); - occupationController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); } @@ -102,7 +96,6 @@ mixin $ProfileDetailView { phoneNumberController.addListener(() => _updateFormData(model)); lastNameController.addListener(() => _updateFormData(model)); firstNameController.addListener(() => _updateFormData(model)); - occupationController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); } @@ -117,7 +110,6 @@ mixin $ProfileDetailView { PhoneNumberValueKey: phoneNumberController.text, LastNameValueKey: lastNameController.text, FirstNameValueKey: firstNameController.text, - OccupationValueKey: occupationController.text, }), ); @@ -165,8 +157,6 @@ extension ValueProperties on FormStateHelper { this.formValueMap[PhoneNumberValueKey] as String?; String? get lastNameValue => this.formValueMap[LastNameValueKey] as String?; String? get firstNameValue => this.formValueMap[FirstNameValueKey] as String?; - String? get occupationValue => - this.formValueMap[OccupationValueKey] as String?; set emailValue(String? value) { this.setData( @@ -226,18 +216,6 @@ extension ValueProperties on FormStateHelper { } } - set occupationValue(String? value) { - this.setData( - this.formValueMap..addAll({OccupationValueKey: value}), - ); - - if (_ProfileDetailViewTextEditingControllers.containsKey( - OccupationValueKey)) { - _ProfileDetailViewTextEditingControllers[OccupationValueKey]?.text = - value ?? ''; - } - } - bool get hasEmail => this.formValueMap.containsKey(EmailValueKey) && (emailValue?.isNotEmpty ?? false); @@ -253,9 +231,6 @@ extension ValueProperties on FormStateHelper { bool get hasFirstName => this.formValueMap.containsKey(FirstNameValueKey) && (firstNameValue?.isNotEmpty ?? false); - bool get hasOccupation => - this.formValueMap.containsKey(OccupationValueKey) && - (occupationValue?.isNotEmpty ?? false); bool get hasEmailValidationMessage => this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false; @@ -267,8 +242,6 @@ extension ValueProperties on FormStateHelper { this.fieldsValidationMessages[LastNameValueKey]?.isNotEmpty ?? false; bool get hasFirstNameValidationMessage => this.fieldsValidationMessages[FirstNameValueKey]?.isNotEmpty ?? false; - bool get hasOccupationValidationMessage => - this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false; String? get emailValidationMessage => this.fieldsValidationMessages[EmailValueKey]; @@ -280,8 +253,6 @@ extension ValueProperties on FormStateHelper { this.fieldsValidationMessages[LastNameValueKey]; String? get firstNameValidationMessage => this.fieldsValidationMessages[FirstNameValueKey]; - String? get occupationValidationMessage => - this.fieldsValidationMessages[OccupationValueKey]; } extension Methods on FormStateHelper { @@ -295,8 +266,6 @@ extension Methods on FormStateHelper { this.fieldsValidationMessages[LastNameValueKey] = validationMessage; void setFirstNameValidationMessage(String? validationMessage) => this.fieldsValidationMessages[FirstNameValueKey] = validationMessage; - void setOccupationValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[OccupationValueKey] = validationMessage; /// Clears text input fields on the Form void clearForm() { @@ -305,7 +274,6 @@ extension Methods on FormStateHelper { phoneNumberValue = ''; lastNameValue = ''; firstNameValue = ''; - occupationValue = ''; } /// Validates text input fields on the Form @@ -316,7 +284,6 @@ extension Methods on FormStateHelper { PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), LastNameValueKey: getValidationMessage(LastNameValueKey), FirstNameValueKey: getValidationMessage(FirstNameValueKey), - OccupationValueKey: getValidationMessage(OccupationValueKey), }); } } @@ -341,5 +308,4 @@ void updateValidationData(FormStateHelper model) => PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), LastNameValueKey: getValidationMessage(LastNameValueKey), FirstNameValueKey: getValidationMessage(FirstNameValueKey), - OccupationValueKey: getValidationMessage(OccupationValueKey), }); diff --git a/lib/ui/views/profile_detail/profile_detail_viewmodel.dart b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart index 9fc1cd0..db6fa55 100644 --- a/lib/ui/views/profile_detail/profile_detail_viewmodel.dart +++ b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart @@ -57,13 +57,11 @@ class ProfileDetailViewModel extends ReactiveViewModel bool get focusEmail => _focusEmail; - // Occupation String _selectedOccupation = 'Students (High school & University)'; String get selectedOccupation => _selectedOccupation; - // Country String _selectedCountry = 'Ethiopia'; @@ -117,18 +115,16 @@ class ProfileDetailViewModel extends ReactiveViewModel rebuildUi(); } - - // Occupation List getOccupations() => [ - 'Students (High school & University)', - 'Job Seekers / Fresh Graduates', - 'Working Professionals (Corporate/Office)', - 'Government & NGO Workers', - 'Entrepreneurs & Small Business Owners', - 'Hospitality & Tourism Workers', - 'Freelancers / Remote Workers (Digital Economy)' - ]; + 'Students (High school & University)', + 'Job Seekers / Fresh Graduates', + 'Working Professionals (Corporate/Office)', + 'Government & NGO Workers', + 'Entrepreneurs & Small Business Owners', + 'Hospitality & Tourism Workers', + 'Freelancers / Remote Workers (Digital Economy)' + ]; void setSelectedOccupation(String value) { _selectedOccupation = value; @@ -137,158 +133,158 @@ class ProfileDetailViewModel extends ReactiveViewModel // Country List getCountries() => [ - "Afghanistan", - "Albania", - "Algeria", - "Andorra", - "Angola", - "Argentina", - "Armenia", - "Australia", - "Austria", - "Azerbaijan", - "Bahrain", - "Bangladesh", - "Belarus", - "Belgium", - "Belize", - "Benin", - "Bhutan", - "Bolivia", - "Bosnia and Herzegovina", - "Botswana", - "Brazil", - "Brunei", - "Bulgaria", - "Burkina Faso", - "Burundi", - "Cambodia", - "Cameroon", - "Canada", - "Chad", - "Chile", - "China", - "Colombia", - "Comoros", - "Congo", - "Costa Rica", - "Croatia", - "Cuba", - "Cyprus", - "Czech Republic", - "Denmark", - "Djibouti", - "Dominican Republic", - "Ecuador", - "Egypt", - "El Salvador", - "Eritrea", - "Estonia", - "Eswatini", - "Ethiopia", - "Finland", - "France", - "Gabon", - "Gambia", - "Georgia", - "Germany", - "Ghana", - "Greece", - "Guatemala", - "Guinea", - "Haiti", - "Honduras", - "Hungary", - "Iceland", - "India", - "Indonesia", - "Iran", - "Iraq", - "Ireland", - "Israel", - "Italy", - "Jamaica", - "Japan", - "Jordan", - "Kazakhstan", - "Kenya", - "Kuwait", - "Kyrgyzstan", - "Laos", - "Latvia", - "Lebanon", - "Liberia", - "Libya", - "Lithuania", - "Luxembourg", - "Madagascar", - "Malawi", - "Malaysia", - "Maldives", - "Mali", - "Malta", - "Mexico", - "Moldova", - "Monaco", - "Mongolia", - "Morocco", - "Mozambique", - "Myanmar", - "Namibia", - "Nepal", - "Netherlands", - "New Zealand", - "Nicaragua", - "Niger", - "Nigeria", - "North Korea", - "Norway", - "Oman", - "Pakistan", - "Panama", - "Paraguay", - "Peru", - "Philippines", - "Poland", - "Portugal", - "Qatar", - "Romania", - "Russia", - "Rwanda", - "Saudi Arabia", - "Senegal", - "Serbia", - "Singapore", - "Slovakia", - "Slovenia", - "Somalia", - "South Africa", - "South Korea", - "Spain", - "Sri Lanka", - "Sudan", - "Sweden", - "Switzerland", - "Syria", - "Taiwan", - "Tajikistan", - "Tanzania", - "Thailand", - "Tunisia", - "Turkey", - "Uganda", - "Ukraine", - "United Arab Emirates", - "United Kingdom", - "United States", - "Uruguay", - "Uzbekistan", - "Venezuela", - "Vietnam", - "Yemen", - "Zambia", - "Zimbabwe" - ]; + "Afghanistan", + "Albania", + "Algeria", + "Andorra", + "Angola", + "Argentina", + "Armenia", + "Australia", + "Austria", + "Azerbaijan", + "Bahrain", + "Bangladesh", + "Belarus", + "Belgium", + "Belize", + "Benin", + "Bhutan", + "Bolivia", + "Bosnia and Herzegovina", + "Botswana", + "Brazil", + "Brunei", + "Bulgaria", + "Burkina Faso", + "Burundi", + "Cambodia", + "Cameroon", + "Canada", + "Chad", + "Chile", + "China", + "Colombia", + "Comoros", + "Congo", + "Costa Rica", + "Croatia", + "Cuba", + "Cyprus", + "Czech Republic", + "Denmark", + "Djibouti", + "Dominican Republic", + "Ecuador", + "Egypt", + "El Salvador", + "Eritrea", + "Estonia", + "Eswatini", + "Ethiopia", + "Finland", + "France", + "Gabon", + "Gambia", + "Georgia", + "Germany", + "Ghana", + "Greece", + "Guatemala", + "Guinea", + "Haiti", + "Honduras", + "Hungary", + "Iceland", + "India", + "Indonesia", + "Iran", + "Iraq", + "Ireland", + "Israel", + "Italy", + "Jamaica", + "Japan", + "Jordan", + "Kazakhstan", + "Kenya", + "Kuwait", + "Kyrgyzstan", + "Laos", + "Latvia", + "Lebanon", + "Liberia", + "Libya", + "Lithuania", + "Luxembourg", + "Madagascar", + "Malawi", + "Malaysia", + "Maldives", + "Mali", + "Malta", + "Mexico", + "Moldova", + "Monaco", + "Mongolia", + "Morocco", + "Mozambique", + "Myanmar", + "Namibia", + "Nepal", + "Netherlands", + "New Zealand", + "Nicaragua", + "Niger", + "Nigeria", + "North Korea", + "Norway", + "Oman", + "Pakistan", + "Panama", + "Paraguay", + "Peru", + "Philippines", + "Poland", + "Portugal", + "Qatar", + "Romania", + "Russia", + "Rwanda", + "Saudi Arabia", + "Senegal", + "Serbia", + "Singapore", + "Slovakia", + "Slovenia", + "Somalia", + "South Africa", + "South Korea", + "Spain", + "Sri Lanka", + "Sudan", + "Sweden", + "Switzerland", + "Syria", + "Taiwan", + "Tajikistan", + "Tanzania", + "Thailand", + "Tunisia", + "Turkey", + "Uganda", + "Ukraine", + "United Arab Emirates", + "United Kingdom", + "United States", + "Uruguay", + "Uzbekistan", + "Venezuela", + "Vietnam", + "Yemen", + "Zambia", + "Zimbabwe" + ]; void setSelectedCountry(String value) { _selectedCountry = value; @@ -305,24 +301,24 @@ class ProfileDetailViewModel extends ReactiveViewModel // Region List getRegions() => [ - 'Addis Ababa', - 'Afar', - 'Amhara', - 'Benishangul-Gumuz', - 'Central Ethiopia', - 'Dire Dawa', - 'Gambela', - 'Harari', - 'Oromia', - 'Sidama', - 'Somali', - 'South Ethiopia', - 'South West Ethiopia Peoples', - 'Tigray', - ]; + 'Addis Ababa', + 'Afar', + 'Amhara', + 'Benishangul-Gumuz', + 'Central Ethiopia', + 'Dire Dawa', + 'Gambela', + 'Harari', + 'Oromia', + 'Sidama', + 'Somali', + 'South Ethiopia', + 'South West Ethiopia Peoples', + 'Tigray', + ]; - bool checkRegion({required String region,required String country}){ - if(country == 'Ethiopia'){ + bool checkRegion({required String region, required String country}) { + if (country == 'Ethiopia') { return getRegions().contains(region); } return false; diff --git a/lib/ui/views/register/register_viewmodel.dart b/lib/ui/views/register/register_viewmodel.dart index e258e0d..344e9be 100644 --- a/lib/ui/views/register/register_viewmodel.dart +++ b/lib/ui/views/register/register_viewmodel.dart @@ -130,7 +130,6 @@ class RegisterViewModel extends ReactiveViewModel _length = false; } - if (password == confirmPassword) { _passwordMatch = true; } else { @@ -237,6 +236,7 @@ class RegisterViewModel extends ReactiveViewModel } void goBack() { + print('HERE'); if (_currentPage == 1) { _currentPage = 0; rebuildUi(); diff --git a/lib/ui/views/register/screens/create_password_screen.dart b/lib/ui/views/register/screens/create_password_screen.dart index 14a606e..ab8c9f0 100644 --- a/lib/ui/views/register/screens/create_password_screen.dart +++ b/lib/ui/views/register/screens/create_password_screen.dart @@ -213,7 +213,6 @@ class CreatePasswordScreen extends ViewModelWidget { backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey, label: '8 characters minimum'); - Widget _buildPasswordMatchValidator(RegisterViewModel viewModel) => ValidatorListTile( backgroundColor: diff --git a/lib/ui/views/telegram_support/telegram_support_view.dart b/lib/ui/views/telegram_support/telegram_support_view.dart index ffe63b7..1b3e518 100644 --- a/lib/ui/views/telegram_support/telegram_support_view.dart +++ b/lib/ui/views/telegram_support/telegram_support_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_constants.dart'; import 'package:yimaru_app/ui/widgets/circular_icon.dart'; import '../../common/app_colors.dart'; @@ -91,20 +92,16 @@ class TelegramSupportView extends StackedView { Widget _buildIcon() => const CircularIcon(icon: Icons.telegram, size: 50, color: kcSkyBlue); - Widget _buildTitle() => const Text( + Widget _buildTitle() => Text( 'Join Yimaru Academy on Telegram', + style: style25DG600, textAlign: TextAlign.center, - style: TextStyle( - fontSize: 25, - color: kcDarkGrey, - fontWeight: FontWeight.w600, - ), ); - Widget _buildSubtitle() => const Text( + Widget _buildSubtitle() => Text( 'Connect with our support team instantly on Telegram for quick assistance and community updates', + style: style14MG400, textAlign: TextAlign.center, - style: TextStyle(color: kcMediumGrey), ); Widget _buildLowerColumn(TelegramSupportViewModel viewModel) => Column( @@ -123,31 +120,24 @@ class TelegramSupportView extends StackedView { ]; Widget _buildContinueButton(TelegramSupportViewModel viewModel) => - const CustomElevatedButton( + CustomElevatedButton( height: 55, borderRadius: 12, - leadingIcon: Icons.telegram, text: 'Open in Telegram', foregroundColor: kcWhite, + leadingIcon: Icons.telegram, backgroundColor: kcPrimaryColor, + onTap: () async => await viewModel.launchTelegram(), ); Widget _buildOptionTextDivider() => const OptionTextDivider(); - Widget _buildSearchText() => const Text.rich( - TextSpan( - text: 'Search for', - style: TextStyle( - color: kcDarkGrey, - ), - children: [ - TextSpan( - text: ' @YimaruSupport', - style: TextStyle( - color: kcPrimaryColor, - fontWeight: FontWeight.w600, - ), - ) - ]), + Widget _buildSearchText() => Text.rich( + TextSpan(text: 'Search for', style: style14DG500, children: [ + TextSpan( + style: style14P600, + text: ' $kTelegramSupport', + ) + ]), ); } diff --git a/lib/ui/views/telegram_support/telegram_support_viewmodel.dart b/lib/ui/views/telegram_support/telegram_support_viewmodel.dart index c9f6120..34fc720 100644 --- a/lib/ui/views/telegram_support/telegram_support_viewmodel.dart +++ b/lib/ui/views/telegram_support/telegram_support_viewmodel.dart @@ -2,8 +2,19 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import '../../../app/app.locator.dart'; +import '../../../services/url_launcher_service.dart'; +import '../../common/app_constants.dart'; class TelegramSupportViewModel extends BaseViewModel { + // Dependency injection final _navigationService = locator(); + + final _urlLauncherService = locator(); + + // Launch telegram + Future launchTelegram() => + _urlLauncherService.launchUri(kTelegramSupport); + + // Navigation void pop() => _navigationService.back(); } diff --git a/lib/ui/widgets/app_bar_pattern.dart b/lib/ui/widgets/app_bar_pattern.dart new file mode 100644 index 0000000..c6506b1 --- /dev/null +++ b/lib/ui/widgets/app_bar_pattern.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; + +class AppBarPattern extends StatelessWidget { + const AppBarPattern({ + super.key, + }); + + @override + Widget build(BuildContext context) => _buildDecorationImageWrapper(); + + Widget _buildDecorationImageWrapper() => ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + child: _buildDecorationImage(), + ); + + Widget _buildDecorationImage() => SizedBox( + width: double.maxFinite, + height: double.maxFinite, + child: _buildPatternWrapper(), + ); + + Widget _buildPatternWrapper() => SizedBox( + width: double.maxFinite, + height: double.maxFinite, + child: _buildPatternMask(), + ); + + Widget _buildPatternMask() => ShaderMask( + shaderCallback: (Rect bounds) => const LinearGradient( + colors: [kcWhite, kcWhite], + ).createShader(bounds), + blendMode: BlendMode.modulate, + child: _buildPattern(), + ); + + Widget _buildPattern() => Image.asset( + 'assets/images/pattern.png', + fit: BoxFit.cover, + ); +} diff --git a/lib/ui/widgets/large_app_bar.dart b/lib/ui/widgets/large_app_bar.dart index c395baf..3886135 100644 --- a/lib/ui/widgets/large_app_bar.dart +++ b/lib/ui/widgets/large_app_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/widgets/app_bar_pattern.dart'; import 'package:yimaru_app/ui/widgets/language_button.dart'; class LargeAppBar extends StatelessWidget { @@ -18,12 +19,11 @@ class LargeAppBar extends StatelessWidget { required this.showLanguageSelection}); @override - Widget build(BuildContext context) => _buildAppBarWrapper(); + Widget build(BuildContext context) => _buildStackWrapper(); - Widget _buildAppBarWrapper() => Container( + Widget _buildStackWrapper() => Container( height: 125, width: double.maxFinite, - alignment: Alignment.bottomCenter, decoration: const BoxDecoration( color: kcPrimaryColor, borderRadius: BorderRadius.only( @@ -31,6 +31,18 @@ class LargeAppBar extends StatelessWidget { bottomRight: Radius.circular(24), ), ), + child: _buildStack(), + ); + + Widget _buildStack() => Stack( + children: [ _buildPattern(),_buildAppBarWrapper()], + ); + + Widget _buildAppBarWrapper() => Container( + color: kcTransparent, + width: double.maxFinite, + height: double.maxFinite, + alignment: Alignment.bottomCenter, padding: const EdgeInsets.only(bottom: 25, right: 15), child: _buildAppBarItems(), ); @@ -74,4 +86,6 @@ class LargeAppBar extends StatelessWidget { Icons.close, color: kcWhite, ); + + Widget _buildPattern() => const AppBarPattern(); } diff --git a/lib/ui/widgets/learn_course_tile.dart b/lib/ui/widgets/learn_course_tile.dart index 4c05990..493465f 100644 --- a/lib/ui/widgets/learn_course_tile.dart +++ b/lib/ui/widgets/learn_course_tile.dart @@ -143,7 +143,7 @@ class LearnCourseTile extends ViewModelWidget { height: 15, borderRadius: 12, onTap: onViewTap, - text: 'View Courses', + text: 'View Course', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, ); @@ -158,7 +158,7 @@ class LearnCourseTile extends ViewModelWidget { height: 15, borderRadius: 12, onTap: onPracticeTap, - text: 'Take Practices', + text: 'Take Practice', backgroundColor: kcWhite, borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, diff --git a/lib/ui/widgets/learn_lesson_tile.dart b/lib/ui/widgets/learn_lesson_tile.dart index c296116..d710db6 100644 --- a/lib/ui/widgets/learn_lesson_tile.dart +++ b/lib/ui/widgets/learn_lesson_tile.dart @@ -12,12 +12,16 @@ import 'custom_elevated_button.dart'; import 'custom_linear_progress_indicator.dart'; class LearnLessonTile extends ViewModelWidget { + final int index; final LearnLesson lesson; final GestureTapCallback? onLessonTap; final GestureTapCallback? onPracticeTap; - const LearnLessonTile( - {super.key, this.onLessonTap, this.onPracticeTap, required this.lesson}); + const LearnLessonTile({super.key, + this.onLessonTap, + this.onPracticeTap, + required this.index, + required this.lesson}); @override Widget build(BuildContext context, LearnLessonViewModel viewModel) => @@ -52,8 +56,8 @@ class LearnLessonTile extends ViewModelWidget { expandedAlignment: Alignment.centerLeft, backgroundColor: kcPrimaryColor.withOpacity(0.1), controlAffinity: ListTileControlAffinity.trailing, - tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 0), expandedCrossAxisAlignment: CrossAxisAlignment.start, + tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 15), collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1), childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15), // enabled: (lesson.access?.isAccessible ?? false), @@ -130,11 +134,12 @@ class LearnLessonTile extends ViewModelWidget { Widget _buildActionButtons(LearnLessonViewModel viewModel) => Row( mainAxisAlignment: MainAxisAlignment.end, - children: __buildActionButtonChildren(viewModel), + children: _buildActionButtonChildren(viewModel), ); - List __buildActionButtonChildren(LearnLessonViewModel viewModel) => [ - _buildPracticeButtonWrapper(viewModel), + List _buildActionButtonChildren(LearnLessonViewModel viewModel) => [ + if (index != viewModel.lessons.length - 1) + _buildPracticeButtonWrapper(viewModel), horizontalSpaceSmall, _buildLessonButtonWrapper(viewModel) ]; diff --git a/lib/ui/widgets/learn_practice_result_card.dart b/lib/ui/widgets/learn_practice_result_card.dart index b2fba0a..7a0a6a7 100644 --- a/lib/ui/widgets/learn_practice_result_card.dart +++ b/lib/ui/widgets/learn_practice_result_card.dart @@ -15,7 +15,7 @@ class LearnPracticeResultCard extends ViewModelWidget { _buildColumnWrapper(viewModel); Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => SizedBox( - height: 100, + height: 125, width: double.maxFinite, child: _buildColumn(viewModel), ); @@ -30,7 +30,8 @@ class LearnPracticeResultCard extends ViewModelWidget { [_buildQuestion(viewModel), verticalSpaceSmall, _buildRow()]; Widget _buildQuestion(LearnPracticeViewModel viewModel) => Text( - answer['sample_text_answer'], + answer['question_text'], + maxLines: 2, style: style14DG400, ); diff --git a/lib/ui/widgets/mini_thumbnail.dart b/lib/ui/widgets/mini_thumbnail.dart index 8aecf3b..63bb4a0 100644 --- a/lib/ui/widgets/mini_thumbnail.dart +++ b/lib/ui/widgets/mini_thumbnail.dart @@ -14,8 +14,7 @@ class MiniThumbnail extends StatelessWidget { Widget build(BuildContext context) => _buildWrapper(); Widget _buildWrapper() => SizedBox( - width: 75, - height: double.maxFinite, + width: 80, child: _buildLeadingClipper(), ); @@ -40,15 +39,17 @@ class MiniThumbnail extends StatelessWidget { : _buildNetworkImage(); Widget _buildNetworkImage() => CachedNetworkImage( + fit: BoxFit.cover, imageUrl: thumbnail, - fit: BoxFit.fill, width: double.maxFinite, + height: double.maxFinite, ); Widget _buildLocalImage() => Image.asset( thumbnail, - fit: BoxFit.fill, + fit: BoxFit.cover, width: double.maxFinite, + height: double.maxFinite, ); Widget _buildPlayButtonWrapper() => Align( diff --git a/pubspec.yaml b/pubspec.yaml index 0d06ac0..56b6d86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: yimaru_app -version: 0.1.13+15 +version: 0.1.14+16 publish_to: 'none' description: A new Flutter project.