From 2eb7e7f0313d55108febf60dbf353a3129a85392 Mon Sep 17 00:00:00 2001 From: BisratHailu Date: Thu, 7 May 2026 16:30:16 +0300 Subject: [PATCH 1/3] fix: Apply UAT fixes --- lib/app/app.dart | 4 + lib/app/app.locator.dart | 4 + lib/services/api_service.dart | 14 +- lib/services/phone_caller_service.dart | 8 + lib/services/url_launcher_service.dart | 12 + lib/ui/common/app_constants.dart | 6 + lib/ui/common/app_strings.dart | 1 + .../assessment/assessment_viewmodel.dart | 10 +- .../screens/assessment_questions_screen.dart | 71 ++-- .../learn_practice/learn_practice_view.dart | 5 +- .../screens/finish_learn_practice_screen.dart | 5 +- .../learn_practice_description_screen.dart | 6 +- .../screens/learn_practice_result_screen.dart | 3 +- .../screens/start_learn_practice_screen.dart | 25 +- lib/ui/views/onboarding/onboarding_view.dart | 6 +- .../onboarding/onboarding_view.form.dart | 33 -- .../onboarding/onboarding_viewmodel.dart | 64 ++- .../screens/country_region_form_screen.dart | 55 ++- .../screens/occupation_form_screen.dart | 62 +-- lib/ui/views/profile/profile_viewmodel.dart | 14 + .../profile_detail/profile_detail_view.dart | 111 ++--- .../profile_detail_viewmodel.dart | 385 ++++++++++-------- lib/ui/views/register/register_viewmodel.dart | 27 +- .../screens/create_password_screen.dart | 17 - lib/ui/views/welcome/welcome_viewmodel.dart | 4 +- lib/ui/widgets/learning_progress_card.dart | 2 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 72 ++++ pubspec.yaml | 2 + test/helpers/test_helpers.dart | 22 + test/services/phone_caller_service_test.dart | 11 + test/services/url_launcher_service_test.dart | 11 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 36 files changed, 664 insertions(+), 419 deletions(-) create mode 100644 lib/services/phone_caller_service.dart create mode 100644 lib/services/url_launcher_service.dart create mode 100644 test/services/phone_caller_service_test.dart create mode 100644 test/services/url_launcher_service_test.dart diff --git a/lib/app/app.dart b/lib/app/app.dart index 6cd9164..6db28e1 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -53,6 +53,8 @@ import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart'; 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'; // @stacked-import @StackedApp( @@ -114,6 +116,8 @@ import 'package:yimaru_app/services/vimeo_service.dart'; LazySingleton(classType: VoiceRecorderService), LazySingleton(classType: InAppUpdateService), LazySingleton(classType: VimeoService), + LazySingleton(classType: UrlLauncherService), + LazySingleton(classType: PhoneCallerService), // @stacked-service ], bottomsheets: [ diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart index 1f65354..d770977 100644 --- a/lib/app/app.locator.dart +++ b/lib/app/app.locator.dart @@ -23,9 +23,11 @@ import '../services/image_picker_service.dart'; import '../services/in_app_update_service.dart'; import '../services/notification_service.dart'; import '../services/permission_handler_service.dart'; +import '../services/phone_caller_service.dart'; import '../services/secure_storage_service.dart'; import '../services/smart_auth_service.dart'; import '../services/status_checker_service.dart'; +import '../services/url_launcher_service.dart'; import '../services/vimeo_service.dart'; import '../services/voice_recorder_service.dart'; @@ -57,4 +59,6 @@ Future setupLocator( locator.registerLazySingleton(() => VoiceRecorderService()); locator.registerLazySingleton(() => InAppUpdateService()); locator.registerLazySingleton(() => VimeoService()); + locator.registerLazySingleton(() => UrlLauncherService()); + locator.registerLazySingleton(() => PhoneCallerService()); } diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 6316a15..374d71f 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -366,11 +366,15 @@ class ApiService { if (response.statusCode == 200) { var data = response.data; var decodedData = data['data']['question_sets'] as List; - assessments = decodedData.map( - (e) { - return Assessment.fromJson(e); - }, - ).toList(); + assessments = decodedData + .map( + (e) { + return Assessment.fromJson(e); + }, + ) + .toList() + .reversed + .toList(); return assessments; } return []; diff --git a/lib/services/phone_caller_service.dart b/lib/services/phone_caller_service.dart new file mode 100644 index 0000000..0db16b0 --- /dev/null +++ b/lib/services/phone_caller_service.dart @@ -0,0 +1,8 @@ +import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart'; + +class PhoneCallerService { + Future call(String phone) async => + await FlutterPhoneDirectCaller.callNumber(phone); + + +} diff --git a/lib/services/url_launcher_service.dart b/lib/services/url_launcher_service.dart new file mode 100644 index 0000000..7447528 --- /dev/null +++ b/lib/services/url_launcher_service.dart @@ -0,0 +1,12 @@ +import 'package:url_launcher/url_launcher.dart'; + +class UrlLauncherService { + Future launchUri(String url) async { + Uri uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + launchUrl(uri); + } + } +} diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 3b58b84..1f07f4a 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -1,3 +1,4 @@ +// Endpoints String kBaseUrl = 'https://api.yimaruacademy.com'; String kApiUrl = 'api'; @@ -83,3 +84,8 @@ String kSampleVideoUrl = String kServerClientId = '900714037062-24ria5fcfet71o3vde8f6gsvsj1n68ec.apps.googleusercontent.com'; + +// Other +String kPhoneSupport = '+251946396655'; + +String kTelegramSupport = '@yimaruacademy2026'; \ No newline at end of file diff --git a/lib/ui/common/app_strings.dart b/lib/ui/common/app_strings.dart index 0aee3bb..995114f 100644 --- a/lib/ui/common/app_strings.dart +++ b/lib/ui/common/app_strings.dart @@ -1,3 +1,4 @@ + const String ksHomeBottomSheetTitle = 'Build Great Apps!'; const String ksSuggestion = diff --git a/lib/ui/views/assessment/assessment_viewmodel.dart b/lib/ui/views/assessment/assessment_viewmodel.dart index 98b99d6..3802f6f 100644 --- a/lib/ui/views/assessment/assessment_viewmodel.dart +++ b/lib/ui/views/assessment/assessment_viewmodel.dart @@ -99,8 +99,7 @@ class AssessmentViewModel extends BaseViewModel { count++; } } - print('COUNT: $count'); - print('ASSESSMENT: ${_currentAssessment?.passingScore}'); + if (count >= (_currentAssessment?.passingScore ?? 0)) { return true; } @@ -152,9 +151,7 @@ class AssessmentViewModel extends BaseViewModel { Future nextQuestion() async { _currentQuestionIndex++; Map response = evaluateAssessment(); - print('LEVEL: $response'); - print('LENGTH: ${_assessmentQuestions.length}'); - print('INDEX: $_currentQuestionIndex'); + if (_currentQuestionIndex == _assessmentQuestions.length) { _currentAssessmentIndex = _currentAssessmentIndex + 1; if (_currentAssessmentIndex == _assessments.length) { @@ -279,7 +276,6 @@ class AssessmentViewModel extends BaseViewModel { Future _getAssessments() async { if (await _statusChecker.checkConnection()) { _assessments = await _apiService.getAssessments(); - _assessments.reversed; } } @@ -290,6 +286,8 @@ class AssessmentViewModel extends BaseViewModel { Future _getAssessmentQuestions(int id) async { if (await _statusChecker.checkConnection()) { _assessmentQuestions = await _apiService.getAssessmentQuestions(id); + _assessmentQuestions + .removeWhere((question) => question.questionType != 'MCQ'); } } } diff --git a/lib/ui/views/assessment/screens/assessment_questions_screen.dart b/lib/ui/views/assessment/screens/assessment_questions_screen.dart index c89c26c..2cb28bb 100644 --- a/lib/ui/views/assessment/screens/assessment_questions_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_questions_screen.dart @@ -54,57 +54,61 @@ class AssessmentQuestionsScreen extends ViewModelWidget { itemCount: viewModel.assessmentQuestions.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (cotext, index) => - _buildBodyScroller(index: index, viewModel: viewModel), + _buildBodyScroller( viewModel), ); Widget _buildBodyScroller( - {required int index, required AssessmentViewModel viewModel}) => + AssessmentViewModel viewModel) => SingleChildScrollView( - child: _buildBody(index: index, viewModel: viewModel), + child: _buildBody( viewModel), ); Widget _buildBody( - {required int index, required AssessmentViewModel viewModel}) => + AssessmentViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: _buildBodyChildren(viewModel: viewModel, index: index), + children: _buildBodyChildren( viewModel), ); List _buildBodyChildren( - {required int index, required AssessmentViewModel viewModel}) => - [ - verticalSpaceMedium, - _buildTitle(index: index, viewModel: viewModel), - verticalSpaceMedium, - _buildAnswers(index: index, viewModel: viewModel), - _buildContinueButtonWrapper(viewModel: viewModel, question: index + 1) - ]; + AssessmentViewModel viewModel) =>[ + verticalSpaceMedium, + _buildTitleState( viewModel), + verticalSpaceMedium, + _buildAnswersState( viewModel), + _buildContinueButtonWrapper( viewModel) + ]; + Widget _buildTitleState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex == + viewModel.assessmentQuestions.length ?Container(): _buildTitle(viewModel); Widget _buildTitle( - {required int index, required AssessmentViewModel viewModel}) => + AssessmentViewModel viewModel) => Text( - 'Q${index + 1}. ${viewModel.assessmentQuestions[index].questionText} ', + 'Q${viewModel.currentQuestionIndex + 1}. ${viewModel.assessmentQuestions[viewModel.currentQuestionIndex].questionText} ', style: style16DG600, ); + Widget _buildAnswersState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex == + viewModel.assessmentQuestions.length ?Container(): _buildAnswers(viewModel); + Widget _buildAnswers( - {required int index, required AssessmentViewModel viewModel}) => + AssessmentViewModel viewModel) => ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: viewModel.assessmentQuestions[index].options?.length, + itemCount: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?.length, itemBuilder: (context, inner) => _buildAnswer( onTap: () => viewModel.setSelectedAnswer( - question: index + 1, - option: viewModel.assessmentQuestions[index].options?[inner]), + question: viewModel.currentQuestionIndex + 1, + option: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner]), title: - viewModel.assessmentQuestions[index].options?[inner].optionText ?? + viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner].optionText ?? '', selected: viewModel.isSelectedAnswer( - question: index + 1, + question: viewModel.currentQuestionIndex + 1, answer: viewModel - .assessmentQuestions[index].options?[inner].optionText ?? + .assessmentQuestions[viewModel.currentQuestionIndex ].options?[inner].optionText ?? ''), ), ); @@ -120,28 +124,29 @@ class AssessmentQuestionsScreen extends ViewModelWidget { ); Widget _buildContinueButtonWrapper( - {required int question, required AssessmentViewModel viewModel}) => + AssessmentViewModel viewModel) => Padding( padding: const EdgeInsets.only(bottom: 50), - child: _buildContinueButton(viewModel: viewModel, question: question), + child: _buildContinueButton( viewModel), ); Widget _buildContinueButton( - {required int question, required AssessmentViewModel viewModel}) => + AssessmentViewModel viewModel) => CustomElevatedButton( height: 55, borderRadius: 12, foregroundColor: kcWhite, - backgroundColor: - viewModel.selectedAnswers.containsKey(question.toString()) - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1), - onTap: viewModel.selectedAnswers.containsKey(question.toString()) - ? () => viewModel.nextQuestion() - : null, 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}') + ? () => viewModel.nextQuestion() + : null, + ); } diff --git a/lib/ui/views/learn_practice/learn_practice_view.dart b/lib/ui/views/learn_practice/learn_practice_view.dart index 2c2e366..02b4629 100644 --- a/lib/ui/views/learn_practice/learn_practice_view.dart +++ b/lib/ui/views/learn_practice/learn_practice_view.dart @@ -104,15 +104,14 @@ class LearnPracticeView extends StackedView { List _buildScreens(LearnPracticeViewModel viewModel) => [ _buildLearnPracticeIntroScreen(), - _buildLearnPracticeElementsScreen(), + _buildLearnPracticeElementsScreen(), _buildLearnPracticeQuestionsScreen(), _buildFinishLearnPracticeScreen(), _buildLearnPracticeResultScreen(), _buildLearnPracticeCompletionScreen() ]; - Widget _buildLearnPracticeIntroScreen() => - const LearnPracticeIntroScreen(); + Widget _buildLearnPracticeIntroScreen() => const LearnPracticeIntroScreen(); Widget _buildLearnPracticeElementsScreen() => const LearnPracticeDescriptionScreen(); diff --git a/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart b/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart index e2ab92f..3c59be7 100644 --- a/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart +++ b/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart @@ -12,7 +12,8 @@ class FinishLearnPracticeScreen extends ViewModelWidget { const FinishLearnPracticeScreen({super.key}); - Future _reset(LearnPracticeViewModel viewModel)async =>await viewModel.reset(); + Future _reset(LearnPracticeViewModel viewModel) async => + await viewModel.reset(); @override Widget build(BuildContext context, LearnPracticeViewModel viewModel) => @@ -123,6 +124,6 @@ class FinishLearnPracticeScreen backgroundColor: kcWhite, borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, - onTap: ()async => await _reset(viewModel) , + onTap: () async => await _reset(viewModel), ); } 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 586ab31..fdb338c 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 @@ -153,9 +153,9 @@ class LearnPracticeDescriptionScreen Widget _buildImage(LearnPracticeViewModel viewModel) => CachedNetworkImage( fit: BoxFit.cover, width: double.maxFinite, - imageUrl: getReadableUrl(viewModel.practices.first.storyImage ?? '') ?? '', - - ); + imageUrl: + getReadableUrl(viewModel.practices.first.storyImage ?? '') ?? '', + ); Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) => Padding( 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 bb2c4f2..b4d8060 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 @@ -14,7 +14,8 @@ class LearnPracticeResultScreen extends ViewModelWidget { const LearnPracticeResultScreen({super.key}); - void _navigate(LearnPracticeViewModel viewModel) async=>await viewModel.reset(); + void _navigate(LearnPracticeViewModel viewModel) async => + await viewModel.reset(); Future _cancel(LearnPracticeViewModel viewModel) async { await viewModel.stopRecording(); diff --git a/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart b/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart index dae11c5..70f6b6c 100644 --- a/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart +++ b/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart @@ -22,9 +22,8 @@ class StartLearnPracticeScreen extends ViewModelWidget { viewModel.pop(); } - Future _start(LearnPracticeViewModel viewModel)async => - await viewModel.playVoicePrompt(question); - + Future _start(LearnPracticeViewModel viewModel) async => + await viewModel.playVoicePrompt(question); Future _showSheet( {required BuildContext context, @@ -115,7 +114,7 @@ class StartLearnPracticeScreen extends ViewModelWidget { Widget _buildStartButtonContainer(LearnPracticeViewModel viewModel) => GestureDetector( - onTap: () async=>await _start(viewModel), + onTap: () async => await _start(viewModel), child: _buildStartButton(), ); @@ -142,16 +141,16 @@ class StartLearnPracticeScreen extends ViewModelWidget { startAngle: 0.0, center: Alignment.center, colors: [ - kcPrimaryColor.withValues(alpha:0.3), - kcIndigo.withValues(alpha:0.2), - kcIndigo.withValues(alpha:0.3), - kcIndigo.withValues(alpha:0.4), - kcIndigo.withValues(alpha:0.5), - kcPrimaryColor.withValues(alpha:0.5), - kcPrimaryColor.withValues(alpha:0.4), - kcPrimaryColor.withValues(alpha:0.3), + kcPrimaryColor.withValues(alpha: 0.3), + kcIndigo.withValues(alpha: 0.2), + kcIndigo.withValues(alpha: 0.3), + kcIndigo.withValues(alpha: 0.4), + kcIndigo.withValues(alpha: 0.5), + kcPrimaryColor.withValues(alpha: 0.5), + kcPrimaryColor.withValues(alpha: 0.4), + kcPrimaryColor.withValues(alpha: 0.3), kcPrimaryColor.withValues(alpha: 0.2), - kcPrimaryColor.withValues(alpha:0.5), + kcPrimaryColor.withValues(alpha: 0.5), ], // quarterly spread ), diff --git a/lib/ui/views/onboarding/onboarding_view.dart b/lib/ui/views/onboarding/onboarding_view.dart index cd30cf0..623ac2d 100644 --- a/lib/ui/views/onboarding/onboarding_view.dart +++ b/lib/ui/views/onboarding/onboarding_view.dart @@ -23,7 +23,6 @@ import 'onboarding_view.form.dart'; name: 'fullName', validator: FormValidator.validateFullNameForm), FormTextField(name: 'region', validator: FormValidator.validateForm), FormTextField(name: 'challenge', validator: FormValidator.validateForm), - FormTextField(name: 'occupation', validator: FormValidator.validateForm), FormTextField(name: 'languageGoal', validator: FormValidator.validateForm), ]) class OnboardingView extends StackedView @@ -39,7 +38,6 @@ class OnboardingView extends StackedView regionController.clear(); fullNameController.clear(); challengeController.clear(); - occupationController.clear(); languageGoalController.clear(); } @@ -54,7 +52,6 @@ class OnboardingView extends StackedView } else if (viewModel.currentPage == 3) { viewModel.resetEducationalBackgroundFormScreen(); } else if (viewModel.currentPage == 4) { - occupationController.clear(); viewModel.resetOccupationFormScreen(); } else if (viewModel.currentPage == 5) { viewModel.resetCountryRegionFormScreen(); @@ -135,8 +132,7 @@ class OnboardingView extends StackedView Widget _buildEducationalBackgroundForm() => const EducationalBackgroundFormScreen(); - Widget _buildOccupationForm() => - OccupationFormScreen(occupationController: occupationController); + Widget _buildOccupationForm() => const OccupationFormScreen(); Widget _buildCountryRegionForm() => CountryRegionFormScreen( regionController: regionController, diff --git a/lib/ui/views/onboarding/onboarding_view.form.dart b/lib/ui/views/onboarding/onboarding_view.form.dart index 6ae05a8..453c5c7 100644 --- a/lib/ui/views/onboarding/onboarding_view.form.dart +++ b/lib/ui/views/onboarding/onboarding_view.form.dart @@ -17,7 +17,6 @@ const String TopicValueKey = 'topic'; const String FullNameValueKey = 'fullName'; const String RegionValueKey = 'region'; const String ChallengeValueKey = 'challenge'; -const String OccupationValueKey = 'occupation'; const String LanguageGoalValueKey = 'languageGoal'; final Map _OnboardingViewTextEditingControllers = @@ -30,7 +29,6 @@ final Map _OnboardingViewTextValidations = { FullNameValueKey: FormValidator.validateFullNameForm, RegionValueKey: FormValidator.validateForm, ChallengeValueKey: FormValidator.validateForm, - OccupationValueKey: FormValidator.validateForm, LanguageGoalValueKey: FormValidator.validateForm, }; @@ -43,8 +41,6 @@ mixin $OnboardingView { _getFormTextEditingController(RegionValueKey); TextEditingController get challengeController => _getFormTextEditingController(ChallengeValueKey); - TextEditingController get occupationController => - _getFormTextEditingController(OccupationValueKey); TextEditingController get languageGoalController => _getFormTextEditingController(LanguageGoalValueKey); @@ -52,7 +48,6 @@ mixin $OnboardingView { FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey); FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey); FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey); - FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey); FocusNode get languageGoalFocusNode => _getFormFocusNode(LanguageGoalValueKey); @@ -84,7 +79,6 @@ mixin $OnboardingView { fullNameController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model)); challengeController.addListener(() => _updateFormData(model)); - occupationController.addListener(() => _updateFormData(model)); languageGoalController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); @@ -101,7 +95,6 @@ mixin $OnboardingView { fullNameController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model)); challengeController.addListener(() => _updateFormData(model)); - occupationController.addListener(() => _updateFormData(model)); languageGoalController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); @@ -116,7 +109,6 @@ mixin $OnboardingView { FullNameValueKey: fullNameController.text, RegionValueKey: regionController.text, ChallengeValueKey: challengeController.text, - OccupationValueKey: occupationController.text, LanguageGoalValueKey: languageGoalController.text, }), ); @@ -163,8 +155,6 @@ extension ValueProperties on FormStateHelper { String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?; String? get regionValue => this.formValueMap[RegionValueKey] as String?; String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?; - String? get occupationValue => - this.formValueMap[OccupationValueKey] as String?; String? get languageGoalValue => this.formValueMap[LanguageGoalValueKey] as String?; @@ -210,17 +200,6 @@ extension ValueProperties on FormStateHelper { } } - set occupationValue(String? value) { - this.setData( - this.formValueMap..addAll({OccupationValueKey: value}), - ); - - if (_OnboardingViewTextEditingControllers.containsKey(OccupationValueKey)) { - _OnboardingViewTextEditingControllers[OccupationValueKey]?.text = - value ?? ''; - } - } - set languageGoalValue(String? value) { this.setData( this.formValueMap..addAll({LanguageGoalValueKey: value}), @@ -245,9 +224,6 @@ extension ValueProperties on FormStateHelper { bool get hasChallenge => this.formValueMap.containsKey(ChallengeValueKey) && (challengeValue?.isNotEmpty ?? false); - bool get hasOccupation => - this.formValueMap.containsKey(OccupationValueKey) && - (occupationValue?.isNotEmpty ?? false); bool get hasLanguageGoal => this.formValueMap.containsKey(LanguageGoalValueKey) && (languageGoalValue?.isNotEmpty ?? false); @@ -260,8 +236,6 @@ extension ValueProperties on FormStateHelper { this.fieldsValidationMessages[RegionValueKey]?.isNotEmpty ?? false; bool get hasChallengeValidationMessage => this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false; - bool get hasOccupationValidationMessage => - this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false; bool get hasLanguageGoalValidationMessage => this.fieldsValidationMessages[LanguageGoalValueKey]?.isNotEmpty ?? false; @@ -273,8 +247,6 @@ extension ValueProperties on FormStateHelper { this.fieldsValidationMessages[RegionValueKey]; String? get challengeValidationMessage => this.fieldsValidationMessages[ChallengeValueKey]; - String? get occupationValidationMessage => - this.fieldsValidationMessages[OccupationValueKey]; String? get languageGoalValidationMessage => this.fieldsValidationMessages[LanguageGoalValueKey]; } @@ -288,8 +260,6 @@ extension Methods on FormStateHelper { this.fieldsValidationMessages[RegionValueKey] = validationMessage; void setChallengeValidationMessage(String? validationMessage) => this.fieldsValidationMessages[ChallengeValueKey] = validationMessage; - void setOccupationValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[OccupationValueKey] = validationMessage; void setLanguageGoalValidationMessage(String? validationMessage) => this.fieldsValidationMessages[LanguageGoalValueKey] = validationMessage; @@ -299,7 +269,6 @@ extension Methods on FormStateHelper { fullNameValue = ''; regionValue = ''; challengeValue = ''; - occupationValue = ''; languageGoalValue = ''; } @@ -310,7 +279,6 @@ extension Methods on FormStateHelper { FullNameValueKey: getValidationMessage(FullNameValueKey), RegionValueKey: getValidationMessage(RegionValueKey), ChallengeValueKey: getValidationMessage(ChallengeValueKey), - OccupationValueKey: getValidationMessage(OccupationValueKey), LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey), }); } @@ -335,6 +303,5 @@ void updateValidationData(FormStateHelper model) => FullNameValueKey: getValidationMessage(FullNameValueKey), RegionValueKey: getValidationMessage(RegionValueKey), ChallengeValueKey: getValidationMessage(ChallengeValueKey), - OccupationValueKey: getValidationMessage(OccupationValueKey), LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey), }); diff --git a/lib/ui/views/onboarding/onboarding_viewmodel.dart b/lib/ui/views/onboarding/onboarding_viewmodel.dart index 6b753d5..b80877d 100644 --- a/lib/ui/views/onboarding/onboarding_viewmodel.dart +++ b/lib/ui/views/onboarding/onboarding_viewmodel.dart @@ -91,9 +91,9 @@ class OnboardingViewModel extends ReactiveViewModel Map? get selectedAgeGroup => _selectedAgeGroup; // Occupation - bool _focusOccupation = false; + String _selectedOccupation = 'Students (High school & University)'; - bool get focusOccupation => _focusOccupation; + String get selectedOccupation => _selectedOccupation; // Country String _selectedCountry = 'Ethiopia'; @@ -105,6 +105,14 @@ class OnboardingViewModel extends ReactiveViewModel bool get focusRegion => _focusRegion; + bool _dropdownRegion = true; + + bool get dropdownRegion => _dropdownRegion; + + String _selectedRegion = 'Addis Ababa'; + + String get selectedRegion => _selectedRegion; + // Learning goal String? _selectedLearningGoal; @@ -253,8 +261,18 @@ class OnboardingViewModel extends ReactiveViewModel _selectedAgeGroup == value; // Occupation - void setOccupationFocus() { - _focusOccupation = true; + 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)' + ]; + + void setSelectedOccupation(String value) { + _selectedOccupation = value; rebuildUi(); } @@ -415,15 +433,50 @@ class OnboardingViewModel extends ReactiveViewModel void setSelectedCountry(String value) { _selectedCountry = value; + + if (value == 'Ethiopia') { + _dropdownRegion = true; + _selectedRegion = 'Addis Ababa'; + } else { + _dropdownRegion = false; + } + rebuildUi(); } // 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', + ]; + + void setSelectedRegion(String value) { + _selectedRegion = value; + rebuildUi(); + } + void setRegionFocus() { _focusRegion = true; rebuildUi(); } + void unsetRegionFocus() { + _focusRegion = false; + rebuildUi(); + } + // Learning goal void setSelectedLearningGoal(String value) { _selectedLearningGoal = value; @@ -541,7 +594,7 @@ class OnboardingViewModel extends ReactiveViewModel // Reset occupation form screen void resetOccupationFormScreen() { - _focusOccupation = false; + _selectedOccupation = 'Students (High school & University)'; rebuildUi(); } @@ -549,6 +602,7 @@ class OnboardingViewModel extends ReactiveViewModel void resetCountryRegionFormScreen() { _focusRegion = false; _selectedCountry = 'Ethiopia'; + _selectedRegion = 'Addis Ababa'; rebuildUi(); } 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 4a08c34..5c322e9 100644 --- a/lib/ui/views/onboarding/screens/country_region_form_screen.dart +++ b/lib/ui/views/onboarding/screens/country_region_form_screen.dart @@ -12,6 +12,16 @@ class CountryRegionFormScreen extends ViewModelWidget { final TextEditingController regionController; const CountryRegionFormScreen({super.key, required this.regionController}); + void _setSelectedCountry( + {String? value, required OnboardingViewModel viewModel}) { + viewModel.setSelectedCountry(value ?? 'Ethiopia'); + + if (viewModel.selectedCountry != 'Ethiopia') { + regionController.clear(); + viewModel.unsetRegionFocus(); + } + } + void _pop(OnboardingViewModel viewModel) { viewModel.resetCountryRegionFormScreen(); viewModel.goBack(); @@ -21,7 +31,9 @@ class CountryRegionFormScreen extends ViewModelWidget { FocusManager.instance.primaryFocus?.unfocus(); Map data = { - 'region': regionController.text, + 'region': viewModel.dropdownRegion + ? viewModel.selectedRegion + : regionController.text, 'country': viewModel.selectedCountry, }; viewModel.addUserData(data); @@ -85,10 +97,14 @@ class CountryRegionFormScreen extends ViewModelWidget { verticalSpaceMedium, _buildCountryDropDown(viewModel), verticalSpaceMedium, - _buildRegionFormField(viewModel), - if (viewModel.hasRegionValidationMessage && viewModel.focusRegion) + _buildRegionFormState(viewModel), + if (viewModel.hasRegionValidationMessage && + !viewModel.dropdownRegion && + viewModel.focusRegion) verticalSpaceTiny, - if (viewModel.hasRegionValidationMessage && viewModel.focusRegion) + if (viewModel.hasRegionValidationMessage && + !viewModel.dropdownRegion && + viewModel.focusRegion) _buildRegionValidatorWrapper(viewModel) ]; @@ -116,7 +132,22 @@ class CountryRegionFormScreen extends ViewModelWidget { selectedItem: viewModel.selectedCountry, items: (value, props) => viewModel.getCountries(), onChanged: (value) => - viewModel.setSelectedCountry(value ?? 'Ethiopia')); + _setSelectedCountry(value: value, viewModel: viewModel)); + + Widget _buildRegionFormState(OnboardingViewModel viewModel) => + viewModel.dropdownRegion + ? _buildRegionDropDown(viewModel) + : _buildRegionFormField(viewModel); + + Widget _buildRegionDropDown(OnboardingViewModel viewModel) => + CustomDropdownPicker( + hint: 'Select region', + icon: _buildSearchIcon(), + selectedItem: viewModel.selectedRegion, + items: (value, props) => viewModel.getRegions(), + onChanged: (value) => + viewModel.setSelectedRegion(value ?? 'Addis Ababa')); + Widget _buildRegionFormField(OnboardingViewModel viewModel) => TextFormField( controller: regionController, onTap: viewModel.setRegionFocus, @@ -152,9 +183,15 @@ class CountryRegionFormScreen extends ViewModelWidget { text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, - onTap: regionController.text.isNotEmpty ? () => _next(viewModel) : null, - backgroundColor: regionController.text.isNotEmpty - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1), + onTap: !viewModel.dropdownRegion + ? regionController.text.isNotEmpty + ? () => _next(viewModel) + : null + : () => _next(viewModel), + backgroundColor: !viewModel.dropdownRegion + ? regionController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.1) + : kcPrimaryColor, ); } diff --git a/lib/ui/views/onboarding/screens/occupation_form_screen.dart b/lib/ui/views/onboarding/screens/occupation_form_screen.dart index cbedf0b..aed9707 100644 --- a/lib/ui/views/onboarding/screens/occupation_form_screen.dart +++ b/lib/ui/views/onboarding/screens/occupation_form_screen.dart @@ -6,15 +6,13 @@ import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; 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 { - final TextEditingController occupationController; - - const OccupationFormScreen({super.key, required this.occupationController}); + const OccupationFormScreen({super.key}); void _pop(OnboardingViewModel viewModel) { - occupationController.clear(); viewModel.resetOccupationFormScreen(); viewModel.goBack(); } @@ -22,7 +20,7 @@ class OccupationFormScreen extends ViewModelWidget { Future _next(OnboardingViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); - Map data = {'occupation': occupationController.text}; + Map data = {'occupation': viewModel.selectedOccupation}; viewModel.addUserData(data); viewModel.next(); @@ -82,13 +80,7 @@ class OccupationFormScreen extends ViewModelWidget { verticalSpaceSmall, _buildSubtitle(), verticalSpaceLarge, - _buildOccupationFormField(viewModel), - if (viewModel.hasOccupationValidationMessage && - viewModel.focusOccupation) - verticalSpaceTiny, - if (viewModel.hasOccupationValidationMessage && - viewModel.focusOccupation) - _buildOccupationValidatorWrapper(viewModel) + _buildOccupationDropdown(viewModel), ]; Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( @@ -108,24 +100,17 @@ class OccupationFormScreen extends ViewModelWidget { style: style14MG400, ); - Widget _buildOccupationFormField(OnboardingViewModel viewModel) => - TextFormField( - controller: occupationController, - onTap: viewModel.setOccupationFocus, - decoration: inputDecoration( - hint: 'Enter Your Occupation', - focus: viewModel.focusOccupation, - filled: occupationController.text.isNotEmpty), - ); - - Widget _buildOccupationValidatorWrapper(OnboardingViewModel viewModel) => - viewModel.hasOccupationValidationMessage - ? _buildOccupationValidator(viewModel) - : Container(); - - Widget _buildOccupationValidator(OnboardingViewModel viewModel) => Text( - viewModel.occupationValidationMessage!, - style: style12R700, + Widget _buildOccupationDropdown(OnboardingViewModel viewModel) => + CustomDropdownPicker( + hint: 'Select occupation', + icon: _buildSearchIcon(), + selectedItem: viewModel.selectedOccupation, + items: (value, props) => viewModel.getOccupations(), + onChanged: (value) => viewModel.setSelectedOccupation( + value ?? 'Students (High school & University)')); + Icon _buildSearchIcon() => const Icon( + Icons.search, + color: kcPrimaryColor, ); Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( @@ -135,15 +120,10 @@ class OccupationFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( - height: 55, - text: 'Continue', - borderRadius: 12, - foregroundColor: kcWhite, - onTap: occupationController.text.isNotEmpty - ? () => _next(viewModel) - : null, - backgroundColor: occupationController.text.isNotEmpty - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1), - ); + height: 55, + text: 'Continue', + borderRadius: 12, + foregroundColor: kcWhite, + onTap: () => _next(viewModel), + backgroundColor: kcPrimaryColor); } diff --git a/lib/ui/views/profile/profile_viewmodel.dart b/lib/ui/views/profile/profile_viewmodel.dart index 083e9e4..e16caac 100644 --- a/lib/ui/views/profile/profile_viewmodel.dart +++ b/lib/ui/views/profile/profile_viewmodel.dart @@ -2,6 +2,8 @@ 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'; @@ -10,6 +12,7 @@ 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 { @@ -23,6 +26,10 @@ class ProfileViewModel extends ReactiveViewModel { final _googleAuthService = locator(); + final _phoneCallerService = locator(); + + final _urlLauncherService = locator(); + final _imagePickerService = locator(); final _authenticationService = locator(); @@ -61,6 +68,13 @@ 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 2ae98fe..1583713 100644 --- a/lib/ui/views/profile_detail/profile_detail_view.dart +++ b/lib/ui/views/profile_detail/profile_detail_view.dart @@ -26,7 +26,6 @@ import 'profile_detail_view.form.dart'; name: 'phoneNumber', validator: FormValidator.validatePhoneNumberForm), FormTextField(name: 'lastName', validator: FormValidator.validateForm), FormTextField(name: 'firstName', validator: FormValidator.validateForm), - FormTextField(name: 'occupation', validator: FormValidator.validateForm), ]) class ProfileDetailView extends StackedView with $ProfileDetailView { @@ -34,7 +33,9 @@ class ProfileDetailView extends StackedView Future _update(ProfileDetailViewModel viewModel) async { Map data = { - 'region': regionController.text, + 'region': viewModel.dropdownRegion + ? viewModel.selectedRegion + : regionController.text, 'gender': viewModel.selectedGender, 'last_name': lastNameController.text, 'country': viewModel.selectedCountry, @@ -65,13 +66,23 @@ class ProfileDetailView extends StackedView content: _buildImagePicker(context: context, viewModel: viewModel), ); + void _checkRegion(ProfileDetailViewModel viewModel){ + if(viewModel.checkRegion(viewModel.user?.region ?? '')){ + viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa'); + }else{ + regionController.text = viewModel.user?.region ?? ''; + + } + } + void _onModelReady(ProfileDetailViewModel viewModel) { phoneNumberController.text = '251900000000'; emailController.text = viewModel.user?.email ?? ''; - regionController.text = viewModel.user?.region ?? ''; + lastNameController.text = viewModel.user?.lastName ?? ''; firstNameController.text = viewModel.user?.firstName ?? ''; occupationController.text = viewModel.user?.occupation ?? ''; + _checkRegion(viewModel); viewModel.clearUserData(); viewModel.setGender(viewModel.user?.gender ?? ''); viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia'); @@ -535,11 +546,13 @@ class ProfileDetailView extends StackedView [ _buildRegionFormFieldLabel(), verticalSpaceSmall, - _buildRegionFormField(viewModel), - if (viewModel.hasRegionValidationMessage && viewModel.focusRegion) - verticalSpaceTiny, - if (viewModel.hasRegionValidationMessage && viewModel.focusRegion) - _buildRegionValidatorWrapper(viewModel), + _buildRegionFormState(viewModel), + if (viewModel.hasRegionValidationMessage && + !viewModel.dropdownRegion && + viewModel.focusRegion) verticalSpaceTiny, + if (viewModel.hasRegionValidationMessage && + !viewModel.dropdownRegion && + viewModel.focusRegion) _buildRegionValidatorWrapper(viewModel), ]; Widget _buildRegionFormFieldLabel() => CustomFormLabel( @@ -547,15 +560,28 @@ class ProfileDetailView extends StackedView style: style16DG600, ); - Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => - TextFormField( - controller: regionController, - onTap: viewModel.setRegionFocus, - decoration: inputDecoration( - hint: 'Enter Your City', - focus: viewModel.focusRegion, - filled: regionController.text.isNotEmpty), - ); + Widget _buildRegionFormState(ProfileDetailViewModel viewModel) => + viewModel.dropdownRegion + ? _buildRegionDropDown(viewModel) + : _buildRegionFormField(viewModel); + + Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) => + CustomDropdownPicker( + hint: 'Select region', + icon: _buildSearchIcon(), + selectedItem: viewModel.selectedRegion, + items: (value, props) => viewModel.getRegions(), + 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 _buildRegionValidatorWrapper(ProfileDetailViewModel viewModel) => viewModel.hasRegionValidationMessage @@ -563,9 +589,10 @@ class ProfileDetailView extends StackedView : Container(); Widget _buildRegionValidator(ProfileDetailViewModel viewModel) => Text( - viewModel.regionValidationMessage!, - style: style12R700, - ); + viewModel.regionValidationMessage!, + style: style12R700, + ); + Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) => Column( @@ -580,13 +607,8 @@ class ProfileDetailView extends StackedView [ _buildOccupationDropdownLabel(), verticalSpaceSmall, - _buildOccupationFormField(viewModel), - if (viewModel.hasOccupationValidationMessage && - viewModel.focusOccupation) - verticalSpaceTiny, - if (viewModel.hasOccupationValidationMessage && - viewModel.focusOccupation) - _buildOccupationValidatorWrapper(viewModel) + _buildOccupationDropdown(viewModel) + ]; Widget _buildOccupationDropdownLabel() => CustomFormLabel( @@ -594,29 +616,18 @@ class ProfileDetailView extends StackedView style: style16DG600, ); - Widget _buildOccupationFormField(ProfileDetailViewModel viewModel) => - TextFormField( - controller: occupationController, - onTap: viewModel.setOccupationFocus, - decoration: inputDecoration( - hint: 'Enter Your Occupation', - focus: viewModel.focusOccupation, - filled: occupationController.text.isNotEmpty), - ); - - Widget _buildOccupationValidatorWrapper(ProfileDetailViewModel viewModel) => - viewModel.hasOccupationValidationMessage - ? _buildOccupationValidator(viewModel) - : Container(); - - Widget _buildOccupationValidator(ProfileDetailViewModel viewModel) => Text( - viewModel.occupationValidationMessage!, - style: const TextStyle( - fontSize: 12, - color: Colors.red, - fontWeight: FontWeight.w700, - ), - ); + Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) => + CustomDropdownPicker( + hint: 'Select occupation', + icon: _buildSearchIcon(), + selectedItem: viewModel.selectedOccupation, + items: (value, props) => viewModel.getOccupations(), + onChanged: (value) => viewModel.setSelectedOccupation( + value ?? 'Students (High school & University)')); + Icon _buildSearchIcon() => const Icon( + 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_viewmodel.dart b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart index 67e9ffc..c5d43f6 100644 --- a/lib/ui/views/profile_detail/profile_detail_viewmodel.dart +++ b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart @@ -57,21 +57,31 @@ class ProfileDetailViewModel extends ReactiveViewModel bool get focusEmail => _focusEmail; + + // Occupation + String _selectedOccupation = 'Students (High school & University)'; + + String get selectedOccupation => _selectedOccupation; + + // Country String _selectedCountry = 'Ethiopia'; String get selectedCountry => _selectedCountry; - // Occupation - bool _focusOccupation = false; - - bool get focusOccupation => _focusOccupation; - // Region bool _focusRegion = false; bool get focusRegion => _focusRegion; + bool _dropdownRegion = true; + + bool get dropdownRegion => _dropdownRegion; + + String _selectedRegion = 'Addis Ababa'; + + String get selectedRegion => _selectedRegion; + // User data final Map _userData = {}; @@ -107,180 +117,229 @@ class ProfileDetailViewModel extends ReactiveViewModel rebuildUi(); } - // Country + + + // 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)' + ]; + + void setSelectedOccupation(String value) { + _selectedOccupation = value; + rebuildUi(); + } + // 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; - rebuildUi(); - } + if (value == 'Ethiopia') { + _dropdownRegion = true; + _selectedRegion = 'Addis Ababa'; + } else { + _dropdownRegion = false; + } - // Occupation - void setOccupationFocus() { - _focusOccupation = true; rebuildUi(); } // 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', + ]; + + bool checkRegion(String value){ + return getRegions().contains(value); + } + + void setSelectedRegion(String value) { + _selectedRegion = value; + rebuildUi(); + } + void setRegionFocus() { _focusRegion = true; rebuildUi(); } + void unsetRegionFocus() { + _focusRegion = false; + rebuildUi(); + } + // User data void addUserData(Map data) { _userData.addAll(data); diff --git a/lib/ui/views/register/register_viewmodel.dart b/lib/ui/views/register/register_viewmodel.dart index 07a50d0..e258e0d 100644 --- a/lib/ui/views/register/register_viewmodel.dart +++ b/lib/ui/views/register/register_viewmodel.dart @@ -49,14 +49,6 @@ class RegisterViewModel extends ReactiveViewModel 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; @@ -138,17 +130,6 @@ class RegisterViewModel extends ReactiveViewModel _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; @@ -160,13 +141,9 @@ class RegisterViewModel extends ReactiveViewModel double validationProgress() { 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 setObscurePassword() { @@ -238,8 +215,6 @@ class RegisterViewModel extends ReactiveViewModel void resetCreatePasswordScreen() { _agree = false; _length = false; - _number = false; - _specialChar = false; _passwordMatch = false; _focusPassword = false; _focusConfirmPassword = false; diff --git a/lib/ui/views/register/screens/create_password_screen.dart b/lib/ui/views/register/screens/create_password_screen.dart index 19bd52a..14a606e 100644 --- a/lib/ui/views/register/screens/create_password_screen.dart +++ b/lib/ui/views/register/screens/create_password_screen.dart @@ -114,8 +114,6 @@ class CreatePasswordScreen extends ViewModelWidget { _buildLinearProgressIndicator(viewModel), verticalSpaceSmall, _buildCharLengthValidator(viewModel), - _buildNumberValidator(viewModel), - _buildSymbolValidator(viewModel), _buildPasswordMatchValidator(viewModel), _buildCheckBox(viewModel), verticalSpaceSmall, @@ -215,15 +213,6 @@ class CreatePasswordScreen extends ViewModelWidget { backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey, label: '8 characters minimum'); - Widget _buildNumberValidator(RegisterViewModel viewModel) => - ValidatorListTile( - backgroundColor: viewModel.number ? kcPrimaryColor : kcLightGrey, - label: 'a number'); - - Widget _buildSymbolValidator(RegisterViewModel viewModel) => - ValidatorListTile( - backgroundColor: viewModel.specialChar ? kcPrimaryColor : kcLightGrey, - label: 'one symbol minimum'); Widget _buildPasswordMatchValidator(RegisterViewModel viewModel) => ValidatorListTile( @@ -265,20 +254,14 @@ class CreatePasswordScreen extends ViewModelWidget { foregroundColor: kcWhite, onTap: passwordController.text.isNotEmpty && confirmPasswordController.text.isNotEmpty && - viewModel.number && viewModel.length && - viewModel.specialChar && - viewModel.specialChar && viewModel.passwordMatch && viewModel.agree ? () async => await _signUp(viewModel) : null, backgroundColor: passwordController.text.isNotEmpty && confirmPasswordController.text.isNotEmpty && - viewModel.number && viewModel.length && - viewModel.specialChar && - viewModel.specialChar && viewModel.passwordMatch && viewModel.agree ? kcPrimaryColor diff --git a/lib/ui/views/welcome/welcome_viewmodel.dart b/lib/ui/views/welcome/welcome_viewmodel.dart index c8dfc1b..1f41aa7 100644 --- a/lib/ui/views/welcome/welcome_viewmodel.dart +++ b/lib/ui/views/welcome/welcome_viewmodel.dart @@ -10,13 +10,11 @@ class WelcomeViewModel extends BaseViewModel { // Dependency Injection final _navigationService = locator(); - final _statusChecker = locator(); - final _authenticationService = locator(); // Navigation Future navigateToLogin() async => - await _navigationService.navigateToLoginView(); + await _navigationService.replaceWithLoginView(); // Remote api call diff --git a/lib/ui/widgets/learning_progress_card.dart b/lib/ui/widgets/learning_progress_card.dart index c9e7ac9..f00fcdc 100644 --- a/lib/ui/widgets/learning_progress_card.dart +++ b/lib/ui/widgets/learning_progress_card.dart @@ -55,7 +55,7 @@ class LearningProgressCard extends StatelessWidget { color: kcPrimaryColor, ); - Widget _buildTitle() => Text( + Widget _buildTitle() => Text( 'Learn English', style: style16DG600, ); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 3038d01..024f277 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = @@ -28,4 +29,7 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) record_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); record_linux_plugin_register_with_registrar(record_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f1eba05..118a6eb 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_inappwebview_linux flutter_secure_storage_linux record_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 99c8352..80a3442 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -18,6 +18,7 @@ import google_sign_in_ios import package_info_plus import record_macos import sqflite_darwin +import url_launcher_macos import video_player_avfoundation import wakelock_plus @@ -35,6 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 5a0b585..e5946e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -662,6 +662,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.7" + flutter_phone_direct_caller: + dependency: "direct main" + description: + name: flutter_phone_direct_caller + sha256: "8f166b12391572ce5872feeac3f4b47f455ee314ef221d98ba160c296ae2fad3" + url: "https://pub.dev" + source: hosted + version: "2.2.1" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -1693,6 +1701,70 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.dev" + source: hosted + version: "6.3.29" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cd2e91b..f53d9b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: flutter_html: ^3.0.0 email_validator: any audioplayers: ^6.6.0 + url_launcher: ^6.3.2 video_player: ^2.10.1 firebase_core: ^4.4.0 in_app_update: ^4.2.5 @@ -49,6 +50,7 @@ dependencies: flutter_timer_countdown: ^1.0.7 flutter_carousel_widget: ^3.1.0 flutter_inappwebview: ^6.2.0-beta.3 + flutter_phone_direct_caller: ^2.2.1 flutter_local_notifications: ^20.1.0 internet_connection_checker_plus: ^2.9.1+2 diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index 3e7bfad..c0c44d5 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -18,6 +18,8 @@ import 'package:yimaru_app/services/audio_player_service.dart'; import 'package:yimaru_app/services/voice_recorder_service.dart'; 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'; // @stacked-import import 'test_helpers.mocks.dart'; @@ -47,6 +49,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 ], ) @@ -71,6 +76,9 @@ void registerServices() { getAndRegisterInAppUpdateService(); getAndRegisterVimeoService(); getAndRegisterVimeoService(); + getAndRegisterUrlLauncherService(); + getAndRegisterUrlLauncherService(); + getAndRegisterPhoneCallerService(); // @stacked-mock-register } @@ -239,6 +247,20 @@ MockVimeoService getAndRegisterVimeoService() { locator.registerSingleton(service); return service; } + +MockUrlLauncherService getAndRegisterUrlLauncherService() { + _removeRegistrationIfExists(); + final service = MockUrlLauncherService(); + locator.registerSingleton(service); + return service; +} + +MockPhoneCallerService getAndRegisterPhoneCallerService() { + _removeRegistrationIfExists(); + final service = MockPhoneCallerService(); + locator.registerSingleton(service); + return service; +} // @stacked-mock-create void _removeRegistrationIfExists() { diff --git a/test/services/phone_caller_service_test.dart b/test/services/phone_caller_service_test.dart new file mode 100644 index 0000000..d699fae --- /dev/null +++ b/test/services/phone_caller_service_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('PhoneCallerServiceTest -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/services/url_launcher_service_test.dart b/test/services/url_launcher_service_test.dart new file mode 100644 index 0000000..8f81a9f --- /dev/null +++ b/test/services/url_launcher_service_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('UrlLauncherServiceTest -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 18488b6..051255a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -15,6 +15,7 @@ #include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( @@ -35,4 +36,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); RecordWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b0a64d4..749dce4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows permission_handler_windows record_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 343626242e82e00356a0de4efe99a295097c794c Mon Sep 17 00:00:00 2001 From: BisratHailu Date: Thu, 7 May 2026 16:30:57 +0300 Subject: [PATCH 2/3] fix: Apply UAT fixes --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f53d9b5..0d06ac0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: yimaru_app -version: 0.1.12+14 +version: 0.1.13+15 publish_to: 'none' description: A new Flutter project. From 0f1b2fbfc20058d906334d39995fbd16db169d99 Mon Sep 17 00:00:00 2001 From: BisratHailu Date: Thu, 7 May 2026 16:57:19 +0300 Subject: [PATCH 3/3] fix: Apply UAT fixes --- .../views/profile_detail/profile_detail_view.dart | 14 +++++++------- .../profile_detail/profile_detail_viewmodel.dart | 9 ++++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/ui/views/profile_detail/profile_detail_view.dart b/lib/ui/views/profile_detail/profile_detail_view.dart index 1583713..780a963 100644 --- a/lib/ui/views/profile_detail/profile_detail_view.dart +++ b/lib/ui/views/profile_detail/profile_detail_view.dart @@ -40,7 +40,7 @@ class ProfileDetailView extends StackedView 'last_name': lastNameController.text, 'country': viewModel.selectedCountry, 'first_name': firstNameController.text, - 'occupation': occupationController.text, + 'occupation': viewModel.selectedOccupation, 'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()), }; @@ -67,7 +67,8 @@ class ProfileDetailView extends StackedView ); void _checkRegion(ProfileDetailViewModel viewModel){ - if(viewModel.checkRegion(viewModel.user?.region ?? '')){ + 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{ regionController.text = viewModel.user?.region ?? ''; @@ -78,13 +79,12 @@ class ProfileDetailView extends StackedView void _onModelReady(ProfileDetailViewModel viewModel) { phoneNumberController.text = '251900000000'; emailController.text = viewModel.user?.email ?? ''; - lastNameController.text = viewModel.user?.lastName ?? ''; firstNameController.text = viewModel.user?.firstName ?? ''; - occupationController.text = viewModel.user?.occupation ?? ''; _checkRegion(viewModel); viewModel.clearUserData(); - viewModel.setGender(viewModel.user?.gender ?? ''); + viewModel.setSelectedGender(viewModel.user?.gender ?? ''); + viewModel.setSelectedOccupation(viewModel.user?.occupation ?? ''); viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia'); } @@ -370,7 +370,7 @@ class ProfileDetailView extends StackedView Widget _buildMaleRadioButton(ProfileDetailViewModel viewModel) => RadioGroup( groupValue: viewModel.selectedGender, - onChanged: (value) => viewModel.setGender(value ?? ''), + onChanged: (value) => viewModel.setSelectedGender(value ?? ''), child: _buildMaleRadioTileWrapper(viewModel)); Widget _buildMaleRadioTileWrapper(ProfileDetailViewModel viewModel) => @@ -399,7 +399,7 @@ class ProfileDetailView extends StackedView Widget _buildFemaleRadioButton(ProfileDetailViewModel viewModel) => RadioGroup( groupValue: viewModel.selectedGender, - onChanged: (value) => viewModel.setGender(value ?? ''), + onChanged: (value) => viewModel.setSelectedGender(value ?? ''), child: _buildFemaleRadioTileWrapper(viewModel)); Widget _buildFemaleRadioTileWrapper(ProfileDetailViewModel viewModel) => diff --git a/lib/ui/views/profile_detail/profile_detail_viewmodel.dart b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart index c5d43f6..9fc1cd0 100644 --- a/lib/ui/views/profile_detail/profile_detail_viewmodel.dart +++ b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart @@ -100,7 +100,7 @@ class ProfileDetailViewModel extends ReactiveViewModel } // Gender - void setGender(String value) { + void setSelectedGender(String value) { _selectedGender = value; rebuildUi(); } @@ -321,8 +321,11 @@ class ProfileDetailViewModel extends ReactiveViewModel 'Tigray', ]; - bool checkRegion(String value){ - return getRegions().contains(value); + bool checkRegion({required String region,required String country}){ + if(country == 'Ethiopia'){ + return getRegions().contains(region); + } + return false; } void setSelectedRegion(String value) {