fix(onboarding): Remove birthday and fix other fields

This commit is contained in:
BisratHailu 2026-04-25 11:41:31 +03:00
parent 36360ea7ca
commit 74ffabd917
23 changed files with 663 additions and 590 deletions

View File

@ -14,11 +14,9 @@ class VoiceRecorderService with ListenableServiceMixin {
WaveformRecorderController get waveController => _waveController; WaveformRecorderController get waveController => _waveController;
bool _isRecording = false; bool _isRecording = false;
bool get isRecording => _isRecording; bool get isRecording => _isRecording;
// Start voice recording // Start voice recording
Future<void> startRecording() async { Future<void> startRecording() async {
@ -41,5 +39,4 @@ class VoiceRecorderService with ListenableServiceMixin {
if (file == null) return null; if (file == null) return null;
return file.path; return file.path;
} }
} }

View File

@ -60,8 +60,8 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
required LearnPracticeViewModel viewModel}) => required LearnPracticeViewModel viewModel}) =>
PopScope( PopScope(
canPop: false, canPop: false,
onPopInvokedWithResult: (value, data) onPopInvokedWithResult: (value, data) async =>
async=>await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
child: _buildScaffoldWrapper(viewModel)); child: _buildScaffoldWrapper(viewModel));
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) =>

View File

@ -83,7 +83,7 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Voice? _playing; Voice? _playing;
Voice? get playing =>_playing; Voice? get playing => _playing;
// Learn practices // Learn practices
List<LearnPractice> _practices = []; List<LearnPractice> _practices = [];
@ -126,7 +126,6 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Future<void> _startRecording() async => Future<void> _startRecording() async =>
await _voiceRecorderService.startRecording(); await _voiceRecorderService.startRecording();
// Play practice audio // Play practice audio
void _listenToAudio() { void _listenToAudio() {
_audioPlayerService.durationStream.listen((dur) { _audioPlayerService.durationStream.listen((dur) {
@ -151,45 +150,37 @@ class LearnPracticeViewModel extends ReactiveViewModel {
await _audioPlayerService.playUrl(question.voicePrompt ?? ''); await _audioPlayerService.playUrl(question.voicePrompt ?? '');
} }
Future<void> playResult({required Map<String,dynamic> answer,required Voice voice})async{ Future<void> playResult(
setBusyObject( {required Map<String, dynamic> answer, required Voice voice}) async {
playing: voice, setBusyObject(playing: voice, object: answer['busy_object']);
object: answer['busy_object']); await playAudio(voice: voice, answer: answer);
await playAudio(voice: voice,answer: answer);
} }
Future<void> playAudio({required Map<String,dynamic> answer,required Voice voice}) async => Future<void> playAudio(
await runBusyFuture(_playAudio(voice:voice,answer:answer), {required Map<String, dynamic> answer, required Voice voice}) async =>
await runBusyFuture(_playAudio(voice: voice, answer: answer),
busyObject: answer['busy_object']); busyObject: answer['busy_object']);
Future<void> _playAudio({required Map<String,dynamic> answer,required Voice voice}) async { Future<void> _playAudio(
{required Map<String, dynamic> answer, required Voice voice}) async {
if (voice == Voice.recorded) {
if(voice == Voice.recorded){ await _audioPlayerService.playLocal(answer['recorded_voice_answer']);
await _audioPlayerService } else {
.playLocal(answer['recorded_voice_answer']);
}else{
await _audioPlayerService.playUrl(answer['sample_voice_answer']); await _audioPlayerService.playUrl(answer['sample_voice_answer']);
} }
} }
Future<void> pauseAudio() async { Future<void> pauseAudio() async {
await _audioPlayerService.pause(); await _audioPlayerService.pause();
} }
// Set busy object // Set busy object
void setBusyObject({required String object,required Voice playing}) { void setBusyObject({required String object, required Voice playing}) {
_playing = playing; _playing = playing;
_busyObject = object; _busyObject = object;
rebuildUi(); rebuildUi();
} }
// Dialogue // Dialogue
Future<bool?> showAbortDialog() async { Future<bool?> showAbortDialog() async {
DialogResponse? response = await _dialogService.showDialog( DialogResponse? response = await _dialogService.showDialog(
@ -219,13 +210,14 @@ class LearnPracticeViewModel extends ReactiveViewModel {
} }
} }
Future<void> nextQuestion({required int index,required LearnQuestion question}) async { Future<void> nextQuestion(
{required int index, required LearnQuestion question}) async {
await stopRecording(); await stopRecording();
_answers.add({ _answers.add({
'busy_object': question.id.toString(), 'busy_object': question.id.toString(),
'sample_text_answer': question.audioCorrectAnswerText, 'sample_text_answer': question.audioCorrectAnswerText,
'sample_voice_answer': question.sampleAnswerVoicePrompt, 'sample_voice_answer': question.sampleAnswerVoicePrompt,
'recorded_voice_answer' : _voiceRecorderService.getRecordedAudio() , 'recorded_voice_answer': _voiceRecorderService.getRecordedAudio(),
}); });
if (index != _questions.length) { if (index != _questions.length) {
_questionSetController.nextPage( _questionSetController.nextPage(
@ -266,6 +258,5 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Future<void> _getLearnPracticeQuestions(int id) async { Future<void> _getLearnPracticeQuestions(int id) async {
_questions = await _apiService.getLearnQuestions(id); _questions = await _apiService.getLearnQuestions(id);
} }
} }

View File

@ -31,11 +31,10 @@ class InteractLearnPracticeScreen
} }
void _start(LearnPracticeViewModel viewModel) => void _start(LearnPracticeViewModel viewModel) =>
viewModel.playVoicePrompt(question); viewModel.playVoicePrompt(question);
Future<void> _stop(LearnPracticeViewModel viewModel) async => Future<void> _stop(LearnPracticeViewModel viewModel) async =>
await viewModel.nextQuestion(index: index,question: question); await viewModel.nextQuestion(index: index, question: question);
Future<void> _showSheet( Future<void> _showSheet(
{required BuildContext context, {required BuildContext context,
@ -96,25 +95,30 @@ class InteractLearnPracticeScreen
{required BuildContext context, {required BuildContext context,
required LearnPracticeViewModel viewModel}) => required LearnPracticeViewModel viewModel}) =>
[ [
_buildAppBarWrapper(context: context,viewModel: viewModel), _buildAppBarWrapper(context: context, viewModel: viewModel),
_buildSpeakingIndicatorWrapper(viewModel), _buildSpeakingIndicatorWrapper(viewModel),
_buildLowerButtonsSectionWrapper(context: context, viewModel: viewModel) _buildLowerButtonsSectionWrapper(context: context, viewModel: viewModel)
]; ];
Widget _buildAppBarWrapper( {required BuildContext context, Widget _buildAppBarWrapper(
required LearnPracticeViewModel viewModel}) => Column( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(context: context,viewModel: viewModel), _buildAppBar(context: context, viewModel: viewModel),
verticalSpaceMedium, verticalSpaceMedium,
], ],
); );
Widget _buildAppBar( {required BuildContext context, Widget _buildAppBar(
required LearnPracticeViewModel viewModel}) => SmallAppBar( {required BuildContext context,
showBackButton: true, required LearnPracticeViewModel viewModel}) =>
onTap: () async => await _showSheet(context: context,viewModel: viewModel), SmallAppBar(
title: 'Practice Speaking ($index/${viewModel.questions.length})'); showBackButton: true,
onTap: () async =>
await _showSheet(context: context, viewModel: viewModel),
title: 'Practice Speaking ($index/${viewModel.questions.length})');
Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) =>
Column( Column(

View File

@ -17,12 +17,11 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
viewModel.pop(); viewModel.pop();
viewModel.pop(); viewModel.pop();
viewModel.stopRecording(); viewModel.stopRecording();
} }
Future<void> _showSheet( Future<void> _showSheet(
{required BuildContext context, {required BuildContext context,
required LearnPracticeViewModel viewModel}) async => required LearnPracticeViewModel viewModel}) async =>
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@ -32,41 +31,51 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
@override @override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) => Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context,viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper( {required BuildContext context, Widget _buildScaffoldWrapper(
required LearnPracticeViewModel viewModel}) => Scaffold( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(context: context,viewModel: viewModel), body: _buildScaffold(context: context, viewModel: viewModel),
); );
Widget _buildScaffold( {required BuildContext context, Widget _buildScaffold(
required LearnPracticeViewModel viewModel}) => {required BuildContext context,
SafeArea(child: _buildColumnWrapper(context: context,viewModel: viewModel)); required LearnPracticeViewModel viewModel}) =>
SafeArea(
child: _buildColumnWrapper(context: context, viewModel: viewModel));
Widget _buildColumnWrapper( {required BuildContext context, Widget _buildColumnWrapper(
required LearnPracticeViewModel viewModel}) => Padding( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(context: context,viewModel: viewModel), child: _buildColumn(context: context, viewModel: viewModel),
); );
Widget _buildColumn( {required BuildContext context, Widget _buildColumn(
required LearnPracticeViewModel viewModel}) => Column( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(context: context,viewModel: viewModel), _buildAppBar(context: context, viewModel: viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildBodyColumnWrapper(viewModel), _buildBodyColumnWrapper(viewModel),
], ],
); );
Widget _buildAppBar( {required BuildContext context, Widget _buildAppBar(
required LearnPracticeViewModel viewModel}) => SmallAppBar( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
SmallAppBar(
showBackButton: true, showBackButton: true,
title: 'Practice Speaking', title: 'Practice Speaking',
onTap: () async => await _showSheet(context: context,viewModel: viewModel), onTap: () async =>
await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet( CancelLearnPracticeSheet(

View File

@ -38,7 +38,7 @@ class LearnPracticeQuestionsScreen
PageView( PageView(
controller: viewModel.questionController, controller: viewModel.questionController,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
children: _buildScreens(index:index,question: question), children: _buildScreens(index: index, question: question),
); );
List<Widget> _buildScreens({ List<Widget> _buildScreens({
@ -46,20 +46,25 @@ class LearnPracticeQuestionsScreen
required LearnQuestion question, required LearnQuestion question,
}) => }) =>
[ [
_buildStartLearnPracticeScreen(index:index,question: question), _buildStartLearnPracticeScreen(index: index, question: question),
_buildInteractLearnPracticeScreen(index:index,question: question) _buildInteractLearnPracticeScreen(index: index, question: question)
]; ];
Widget _buildStartLearnPracticeScreen({ Widget _buildStartLearnPracticeScreen({
required int index, required int index,
required LearnQuestion question,} required LearnQuestion question,
) => StartLearnPracticeScreen( }) =>
StartLearnPracticeScreen(
index: index, index: index,
question: question, question: question,
); );
Widget _buildInteractLearnPracticeScreen({ Widget _buildInteractLearnPracticeScreen({
required int index, required int index,
required LearnQuestion question,}) => required LearnQuestion question,
InteractLearnPracticeScreen(index: index,question: question,); }) =>
InteractLearnPracticeScreen(
index: index,
question: question,
);
} }

View File

@ -12,11 +12,9 @@ import '../../../widgets/small_app_bar.dart';
class LearnPracticeResultScreen class LearnPracticeResultScreen
extends ViewModelWidget<LearnPracticeViewModel> { extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeResultScreen({super.key}); const LearnPracticeResultScreen({super.key});
void _navigate(LearnPracticeViewModel viewModel) {
void _navigate(LearnPracticeViewModel viewModel){
viewModel.questionSetController.jumpToPage(0); viewModel.questionSetController.jumpToPage(0);
viewModel.goTo(0); viewModel.goTo(0);
} }
@ -25,12 +23,11 @@ class LearnPracticeResultScreen
viewModel.pop(); viewModel.pop();
viewModel.pop(); viewModel.pop();
viewModel.stopRecording(); viewModel.stopRecording();
} }
Future<void> _showSheet( Future<void> _showSheet(
{required BuildContext context, {required BuildContext context,
required LearnPracticeViewModel viewModel}) async => required LearnPracticeViewModel viewModel}) async =>
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@ -40,42 +37,57 @@ class LearnPracticeResultScreen
@override @override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) => Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context,viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper({required BuildContext context, Widget _buildScaffoldWrapper(
required LearnPracticeViewModel viewModel}) => Scaffold( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(context: context,viewModel: viewModel), body: _buildScaffold(context: context, viewModel: viewModel),
); );
Widget _buildScaffold({required BuildContext context, Widget _buildScaffold(
required LearnPracticeViewModel viewModel}) => {required BuildContext context,
SafeArea(child: _buildBodyColumnWrapper(context: context,viewModel: viewModel)); required LearnPracticeViewModel viewModel}) =>
SafeArea(
child:
_buildBodyColumnWrapper(context: context, viewModel: viewModel));
Widget _buildBodyColumnWrapper({required BuildContext context, Widget _buildBodyColumnWrapper(
required LearnPracticeViewModel viewModel}) => Padding( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(context: context,viewModel: viewModel), child: _buildBodyColumn(context: context, viewModel: viewModel),
); );
Widget _buildBodyColumn({required BuildContext context, Widget _buildBodyColumn(
required LearnPracticeViewModel viewModel}) => Column( {required BuildContext context,
children: _buildBodyColumnChildren(context: context,viewModel: viewModel), required LearnPracticeViewModel viewModel}) =>
Column(
children:
_buildBodyColumnChildren(context: context, viewModel: viewModel),
); );
List<Widget> _buildBodyColumnChildren({required BuildContext context, List<Widget> _buildBodyColumnChildren(
required LearnPracticeViewModel viewModel}) => [ {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
[
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(context: context,viewModel: viewModel), _buildAppBar(context: context, viewModel: viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildBodyWrapper(viewModel) _buildBodyWrapper(viewModel)
]; ];
Widget _buildAppBar({required BuildContext context, Widget _buildAppBar(
required LearnPracticeViewModel viewModel}) => SmallAppBar( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
SmallAppBar(
title: 'Result', title: 'Result',
showBackButton: true, showBackButton: true,
onTap: () async => await _showSheet(context: context,viewModel: viewModel), onTap: () async =>
await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) =>
@ -112,7 +124,7 @@ class LearnPracticeResultScreen
Widget _buildResultsSection(LearnPracticeViewModel viewModel) => Widget _buildResultsSection(LearnPracticeViewModel viewModel) =>
const LearnPracticeResultsWrapper(); const LearnPracticeResultsWrapper();
Widget _buildLearnPracticeTipSection() => const LearnPracticeTipSection(); Widget _buildLearnPracticeTipSection() => const LearnPracticeTipSection();
Widget _buildLowerButtonsSection(LearnPracticeViewModel viewModel) => Column( Widget _buildLowerButtonsSection(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -148,6 +160,5 @@ class LearnPracticeResultScreen
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () => _navigate(viewModel), onTap: () => _navigate(viewModel),
); );
} }

View File

@ -13,7 +13,8 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
final int index; final int index;
final LearnQuestion question; final LearnQuestion question;
const StartLearnPracticeScreen({super.key,required this.index,required this.question}); const StartLearnPracticeScreen(
{super.key, required this.index, required this.question});
Future<void> _cancel(LearnPracticeViewModel viewModel) async { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
viewModel.pop(); viewModel.pop();
@ -26,8 +27,8 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
} }
Future<void> _showSheet( Future<void> _showSheet(
{required BuildContext context, {required BuildContext context,
required LearnPracticeViewModel viewModel}) async => required LearnPracticeViewModel viewModel}) async =>
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@ -37,51 +38,68 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
@override @override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) => Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context,viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper( {required BuildContext context, Widget _buildScaffoldWrapper(
required LearnPracticeViewModel viewModel}) => Scaffold( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(context: context,viewModel: viewModel), body: _buildScaffold(context: context, viewModel: viewModel),
); );
Widget _buildScaffold( {required BuildContext context, Widget _buildScaffold(
required LearnPracticeViewModel viewModel}) => {required BuildContext context,
SafeArea(child: _buildBodyColumnWrapper(context: context,viewModel: viewModel)); required LearnPracticeViewModel viewModel}) =>
SafeArea(
child:
_buildBodyColumnWrapper(context: context, viewModel: viewModel));
Widget _buildBodyColumnWrapper( {required BuildContext context, Widget _buildBodyColumnWrapper(
required LearnPracticeViewModel viewModel}) => Padding( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(context: context,viewModel: viewModel), child: _buildBodyColumn(context: context, viewModel: viewModel),
); );
Widget _buildBodyColumn( {required BuildContext context, Widget _buildBodyColumn(
required LearnPracticeViewModel viewModel}) => Column( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(context: context,viewModel: viewModel), children:
_buildBodyColumnChildren(context: context, viewModel: viewModel),
); );
List<Widget> _buildBodyColumnChildren( {required BuildContext context, List<Widget> _buildBodyColumnChildren(
required LearnPracticeViewModel viewModel}) => [ {required BuildContext context,
_buildAppBarWrapper(context: context,viewModel: viewModel), required LearnPracticeViewModel viewModel}) =>
[
_buildAppBarWrapper(context: context, viewModel: viewModel),
_buildStartButtonWrapper(viewModel), _buildStartButtonWrapper(viewModel),
_buildLowerButtonsSectionWrapper(viewModel) _buildLowerButtonsSectionWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper( {required BuildContext context, Widget _buildAppBarWrapper(
required LearnPracticeViewModel viewModel}) => Column( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(context: context,viewModel: viewModel), _buildAppBar(context: context, viewModel: viewModel),
verticalSpaceMedium, verticalSpaceMedium,
], ],
); );
Widget _buildAppBar( {required BuildContext context, Widget _buildAppBar(
required LearnPracticeViewModel viewModel}) => SmallAppBar( {required BuildContext context,
showBackButton: true, required LearnPracticeViewModel viewModel}) =>
onTap: () async => await _showSheet(context: context,viewModel: viewModel), SmallAppBar(
title: 'Practice Speaking ($index/${viewModel.questions.length})'); showBackButton: true,
onTap: () async =>
await _showSheet(context: context, viewModel: viewModel),
title: 'Practice Speaking ($index/${viewModel.questions.length})');
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet( CancelLearnPracticeSheet(

View File

@ -3,7 +3,6 @@ import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart'; import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/age_group_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/age_group_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/birthday_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/challenge_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/challenge_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/country_region_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/country_region_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/educational_background_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/educational_background_form_screen.dart';
@ -22,6 +21,7 @@ import 'onboarding_view.form.dart';
FormTextField(name: 'topic', validator: FormValidator.validateForm), FormTextField(name: 'topic', validator: FormValidator.validateForm),
FormTextField( FormTextField(
name: 'fullName', validator: FormValidator.validateFullNameForm), name: 'fullName', validator: FormValidator.validateFullNameForm),
FormTextField(name: 'region', validator: FormValidator.validateForm),
FormTextField(name: 'challenge', validator: FormValidator.validateForm), FormTextField(name: 'challenge', validator: FormValidator.validateForm),
FormTextField(name: 'occupation', validator: FormValidator.validateForm), FormTextField(name: 'occupation', validator: FormValidator.validateForm),
FormTextField(name: 'languageGoal', validator: FormValidator.validateForm), FormTextField(name: 'languageGoal', validator: FormValidator.validateForm),
@ -50,25 +50,23 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} else if (viewModel.currentPage == 1) { } else if (viewModel.currentPage == 1) {
viewModel.resetGenderFormScreen(); viewModel.resetGenderFormScreen();
} else if (viewModel.currentPage == 2) { } else if (viewModel.currentPage == 2) {
viewModel.resetBirthdayFormScreen();
} else if (viewModel.currentPage == 3) {
viewModel.resetAgeGroupFormScreen(); viewModel.resetAgeGroupFormScreen();
} else if (viewModel.currentPage == 4) { } else if (viewModel.currentPage == 3) {
viewModel.resetEducationalBackgroundFormScreen(); viewModel.resetEducationalBackgroundFormScreen();
} else if (viewModel.currentPage == 5) { } else if (viewModel.currentPage == 4) {
occupationController.clear(); occupationController.clear();
viewModel.resetOccupationFormScreen(); viewModel.resetOccupationFormScreen();
} else if (viewModel.currentPage == 6) { } else if (viewModel.currentPage == 5) {
viewModel.resetCountryRegionFormScreen(); viewModel.resetCountryRegionFormScreen();
} else if (viewModel.currentPage == 7) { } else if (viewModel.currentPage == 6) {
viewModel.resetLearningGoalFormScreen(); viewModel.resetLearningGoalFormScreen();
} else if (viewModel.currentPage == 8) { } else if (viewModel.currentPage == 7) {
languageGoalController.clear(); languageGoalController.clear();
viewModel.resetLanguageGoalFormScreen(); viewModel.resetLanguageGoalFormScreen();
} else if (viewModel.currentPage == 9) { } else if (viewModel.currentPage == 8) {
challengeController.clear(); challengeController.clear();
viewModel.resetChallengeFormScreen(); viewModel.resetChallengeFormScreen();
} else if (viewModel.currentPage == 10) { } else if (viewModel.currentPage == 9) {
topicController.clear(); topicController.clear();
viewModel.resetTopicFormScreen(); viewModel.resetTopicFormScreen();
} }
@ -117,7 +115,6 @@ class OnboardingView extends StackedView<OnboardingViewModel>
List<Widget> _buildScreens() => [ List<Widget> _buildScreens() => [
_buildFullNameForm(), _buildFullNameForm(),
_buildGenderForm(), _buildGenderForm(),
_buildBirthdayForm(),
_buildAgeGroupForm(), _buildAgeGroupForm(),
_buildEducationalBackgroundForm(), _buildEducationalBackgroundForm(),
_buildOccupationForm(), _buildOccupationForm(),
@ -133,8 +130,6 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildGenderForm() => const GenderFormScreen(); Widget _buildGenderForm() => const GenderFormScreen();
Widget _buildBirthdayForm() => const BirthdayFormScreen();
Widget _buildAgeGroupForm() => const AgeGroupFormScreen(); Widget _buildAgeGroupForm() => const AgeGroupFormScreen();
Widget _buildEducationalBackgroundForm() => Widget _buildEducationalBackgroundForm() =>
@ -143,7 +138,9 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildOccupationForm() => Widget _buildOccupationForm() =>
OccupationFormScreen(occupationController: occupationController); OccupationFormScreen(occupationController: occupationController);
Widget _buildCountryRegionForm() => const CountryRegionFormScreen(); Widget _buildCountryRegionForm() => CountryRegionFormScreen(
regionController: regionController,
);
Widget _buildLearningGoalForm() => const LearningGoalFormScreen(); Widget _buildLearningGoalForm() => const LearningGoalFormScreen();

View File

@ -15,6 +15,7 @@ const bool _autoTextFieldValidation = true;
const String TopicValueKey = 'topic'; const String TopicValueKey = 'topic';
const String FullNameValueKey = 'fullName'; const String FullNameValueKey = 'fullName';
const String RegionValueKey = 'region';
const String ChallengeValueKey = 'challenge'; const String ChallengeValueKey = 'challenge';
const String OccupationValueKey = 'occupation'; const String OccupationValueKey = 'occupation';
const String LanguageGoalValueKey = 'languageGoal'; const String LanguageGoalValueKey = 'languageGoal';
@ -27,6 +28,7 @@ final Map<String, FocusNode> _OnboardingViewFocusNodes = {};
final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = { final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
TopicValueKey: FormValidator.validateForm, TopicValueKey: FormValidator.validateForm,
FullNameValueKey: FormValidator.validateFullNameForm, FullNameValueKey: FormValidator.validateFullNameForm,
RegionValueKey: FormValidator.validateForm,
ChallengeValueKey: FormValidator.validateForm, ChallengeValueKey: FormValidator.validateForm,
OccupationValueKey: FormValidator.validateForm, OccupationValueKey: FormValidator.validateForm,
LanguageGoalValueKey: FormValidator.validateForm, LanguageGoalValueKey: FormValidator.validateForm,
@ -37,6 +39,8 @@ mixin $OnboardingView {
_getFormTextEditingController(TopicValueKey); _getFormTextEditingController(TopicValueKey);
TextEditingController get fullNameController => TextEditingController get fullNameController =>
_getFormTextEditingController(FullNameValueKey); _getFormTextEditingController(FullNameValueKey);
TextEditingController get regionController =>
_getFormTextEditingController(RegionValueKey);
TextEditingController get challengeController => TextEditingController get challengeController =>
_getFormTextEditingController(ChallengeValueKey); _getFormTextEditingController(ChallengeValueKey);
TextEditingController get occupationController => TextEditingController get occupationController =>
@ -46,6 +50,7 @@ mixin $OnboardingView {
FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey); FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey);
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey); FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey);
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey); FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey); FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
FocusNode get languageGoalFocusNode => FocusNode get languageGoalFocusNode =>
@ -77,6 +82,7 @@ mixin $OnboardingView {
void syncFormWithViewModel(FormStateHelper model) { void syncFormWithViewModel(FormStateHelper model) {
topicController.addListener(() => _updateFormData(model)); topicController.addListener(() => _updateFormData(model));
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model)); challengeController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model)); occupationController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model)); languageGoalController.addListener(() => _updateFormData(model));
@ -93,6 +99,7 @@ mixin $OnboardingView {
void listenToFormUpdated(FormViewModel model) { void listenToFormUpdated(FormViewModel model) {
topicController.addListener(() => _updateFormData(model)); topicController.addListener(() => _updateFormData(model));
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model)); challengeController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model)); occupationController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model)); languageGoalController.addListener(() => _updateFormData(model));
@ -107,6 +114,7 @@ mixin $OnboardingView {
..addAll({ ..addAll({
TopicValueKey: topicController.text, TopicValueKey: topicController.text,
FullNameValueKey: fullNameController.text, FullNameValueKey: fullNameController.text,
RegionValueKey: regionController.text,
ChallengeValueKey: challengeController.text, ChallengeValueKey: challengeController.text,
OccupationValueKey: occupationController.text, OccupationValueKey: occupationController.text,
LanguageGoalValueKey: languageGoalController.text, LanguageGoalValueKey: languageGoalController.text,
@ -153,6 +161,7 @@ extension ValueProperties on FormStateHelper {
String? get topicValue => this.formValueMap[TopicValueKey] as String?; String? get topicValue => this.formValueMap[TopicValueKey] as String?;
String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?; 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 challengeValue => this.formValueMap[ChallengeValueKey] as String?;
String? get occupationValue => String? get occupationValue =>
this.formValueMap[OccupationValueKey] as String?; this.formValueMap[OccupationValueKey] as String?;
@ -180,6 +189,16 @@ extension ValueProperties on FormStateHelper {
} }
} }
set regionValue(String? value) {
this.setData(
this.formValueMap..addAll({RegionValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(RegionValueKey)) {
_OnboardingViewTextEditingControllers[RegionValueKey]?.text = value ?? '';
}
}
set challengeValue(String? value) { set challengeValue(String? value) {
this.setData( this.setData(
this.formValueMap..addAll({ChallengeValueKey: value}), this.formValueMap..addAll({ChallengeValueKey: value}),
@ -220,6 +239,9 @@ extension ValueProperties on FormStateHelper {
bool get hasFullName => bool get hasFullName =>
this.formValueMap.containsKey(FullNameValueKey) && this.formValueMap.containsKey(FullNameValueKey) &&
(fullNameValue?.isNotEmpty ?? false); (fullNameValue?.isNotEmpty ?? false);
bool get hasRegion =>
this.formValueMap.containsKey(RegionValueKey) &&
(regionValue?.isNotEmpty ?? false);
bool get hasChallenge => bool get hasChallenge =>
this.formValueMap.containsKey(ChallengeValueKey) && this.formValueMap.containsKey(ChallengeValueKey) &&
(challengeValue?.isNotEmpty ?? false); (challengeValue?.isNotEmpty ?? false);
@ -234,6 +256,8 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
bool get hasFullNameValidationMessage => bool get hasFullNameValidationMessage =>
this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false;
bool get hasRegionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey]?.isNotEmpty ?? false;
bool get hasChallengeValidationMessage => bool get hasChallengeValidationMessage =>
this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false;
bool get hasOccupationValidationMessage => bool get hasOccupationValidationMessage =>
@ -245,6 +269,8 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[TopicValueKey]; this.fieldsValidationMessages[TopicValueKey];
String? get fullNameValidationMessage => String? get fullNameValidationMessage =>
this.fieldsValidationMessages[FullNameValueKey]; this.fieldsValidationMessages[FullNameValueKey];
String? get regionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey];
String? get challengeValidationMessage => String? get challengeValidationMessage =>
this.fieldsValidationMessages[ChallengeValueKey]; this.fieldsValidationMessages[ChallengeValueKey];
String? get occupationValidationMessage => String? get occupationValidationMessage =>
@ -258,6 +284,8 @@ extension Methods on FormStateHelper {
this.fieldsValidationMessages[TopicValueKey] = validationMessage; this.fieldsValidationMessages[TopicValueKey] = validationMessage;
void setFullNameValidationMessage(String? validationMessage) => void setFullNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[FullNameValueKey] = validationMessage; this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
void setRegionValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[RegionValueKey] = validationMessage;
void setChallengeValidationMessage(String? validationMessage) => void setChallengeValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[ChallengeValueKey] = validationMessage; this.fieldsValidationMessages[ChallengeValueKey] = validationMessage;
void setOccupationValidationMessage(String? validationMessage) => void setOccupationValidationMessage(String? validationMessage) =>
@ -269,6 +297,7 @@ extension Methods on FormStateHelper {
void clearForm() { void clearForm() {
topicValue = ''; topicValue = '';
fullNameValue = ''; fullNameValue = '';
regionValue = '';
challengeValue = ''; challengeValue = '';
occupationValue = ''; occupationValue = '';
languageGoalValue = ''; languageGoalValue = '';
@ -279,6 +308,7 @@ extension Methods on FormStateHelper {
this.setValidationMessages({ this.setValidationMessages({
TopicValueKey: getValidationMessage(TopicValueKey), TopicValueKey: getValidationMessage(TopicValueKey),
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
RegionValueKey: getValidationMessage(RegionValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey), ChallengeValueKey: getValidationMessage(ChallengeValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey), OccupationValueKey: getValidationMessage(OccupationValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey), LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
@ -303,6 +333,7 @@ void updateValidationData(FormStateHelper model) =>
model.setValidationMessages({ model.setValidationMessages({
TopicValueKey: getValidationMessage(TopicValueKey), TopicValueKey: getValidationMessage(TopicValueKey),
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
RegionValueKey: getValidationMessage(RegionValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey), ChallengeValueKey: getValidationMessage(ChallengeValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey), OccupationValueKey: getValidationMessage(OccupationValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey), LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),

View File

@ -63,11 +63,6 @@ class OnboardingViewModel extends ReactiveViewModel
String? get selectedGender => _selectedGender; String? get selectedGender => _selectedGender;
// Birthday
String? _selectedBirthday;
String? get selectedBirthday => _selectedBirthday;
// Age group // Age group
final List<Map<String, dynamic>> _ageGroups = [ final List<Map<String, dynamic>> _ageGroups = [
{ {
@ -105,10 +100,10 @@ class OnboardingViewModel extends ReactiveViewModel
String get selectedCountry => _selectedCountry; String get selectedCountry => _selectedCountry;
// Country // Region
String _selectedRegion = 'Addis Ababa'; bool _focusRegion = false;
String get selectedRegion => _selectedRegion; bool get focusRegion => _focusRegion;
// Learning goal // Learning goal
String? _selectedLearningGoal; String? _selectedLearningGoal;
@ -247,12 +242,6 @@ class OnboardingViewModel extends ReactiveViewModel
bool isSelectedGender(String value) => _selectedGender == value; bool isSelectedGender(String value) => _selectedGender == value;
// Birthday
void setBirthday(String value) {
_selectedBirthday = value;
rebuildUi();
}
// Age group // Age group
void setSelectedAgeGroup(Map<String, dynamic> value) { void setSelectedAgeGroup(Map<String, dynamic> value) {
_selectedAgeGroup = value; _selectedAgeGroup = value;
@ -270,44 +259,168 @@ class OnboardingViewModel extends ReactiveViewModel
} }
// Country // Country
List<String> getCountries() => ['Ethiopia', 'Other']; List<String> 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"
];
void setSelectedCountry(String value) { void setSelectedCountry(String value) {
_selectedCountry = value; _selectedCountry = value;
if (selectedCountry != 'Ethiopia') {
_selectedRegion = 'Other';
} else {
_selectedRegion = 'Addis Ababa';
}
rebuildUi(); rebuildUi();
} }
// Region // Region
List<String> getRegions(String country) { void setRegionFocus() {
if (country == 'Ethiopia') { _focusRegion = true;
return [
'Afar',
'SNNPR',
'Amhara',
'Harari',
'Oromia',
'Sidama',
'Somali',
'Tigray',
'Gambela',
'Dire Dawa',
'Addis Ababa',
'Central Ethiopia',
'Benishangul-Gumuz',
'South West Ethiopia',
];
} else {
return ['Other'];
}
}
void setSelectedRegion(String value) {
_selectedRegion = value;
rebuildUi(); rebuildUi();
} }
@ -414,12 +527,6 @@ class OnboardingViewModel extends ReactiveViewModel
rebuildUi(); rebuildUi();
} }
// Reset birthday form screen
void resetBirthdayFormScreen() {
_selectedBirthday = null;
rebuildUi();
}
// Reset age group form screen // Reset age group form screen
void resetAgeGroupFormScreen() { void resetAgeGroupFormScreen() {
_selectedAgeGroup = null; _selectedAgeGroup = null;
@ -441,7 +548,6 @@ class OnboardingViewModel extends ReactiveViewModel
// Reset country region form screen // Reset country region form screen
void resetCountryRegionFormScreen() { void resetCountryRegionFormScreen() {
_selectedCountry = 'Ethiopia'; _selectedCountry = 'Ethiopia';
_selectedRegion = 'Addis Ababa';
rebuildUi(); rebuildUi();
} }

View File

@ -1,122 +0,0 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
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/birthday_selector.dart';
class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
const BirthdayFormScreen({super.key});
void _pop(OnboardingViewModel viewModel) {
viewModel.resetBirthdayFormScreen();
viewModel.goBack();
}
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {'birth_day': viewModel.selectedBirthday};
viewModel.addUserData(data);
viewModel.next();
}
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubtitle(),
verticalSpaceMedium,
_buildBirthdayFormField(viewModel)
];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: true,
showLanguageSelection: true,
onPop: () => _pop(viewModel),
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => Text(
'Pick your birthday?',
style: style25DG600,
);
Widget _buildSubtitle() => Text(
'Well personalize your learning experience based on your birthday.',
style: style14MG400,
);
Widget _buildBirthdayFormField(OnboardingViewModel viewModel) =>
BirthdaySelector(
birthday: viewModel.selectedBirthday,
onSelected: (value) =>
viewModel.setBirthday(DateFormat('yyyy-MM-dd').format(value)),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
backgroundColor: viewModel.selectedBirthday != null
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap:
viewModel.selectedBirthday != null ? () => _next(viewModel) : null,
);
}

View File

@ -6,9 +6,11 @@ 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/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_dropdown.dart'; import 'package:yimaru_app/ui/widgets/custom_dropdown.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../onboarding_view.form.dart';
class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> { class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
const CountryRegionFormScreen({super.key}); final TextEditingController regionController;
const CountryRegionFormScreen({super.key, required this.regionController});
void _pop(OnboardingViewModel viewModel) { void _pop(OnboardingViewModel viewModel) {
viewModel.resetCountryRegionFormScreen(); viewModel.resetCountryRegionFormScreen();
@ -19,8 +21,8 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {
'region': regionController.text,
'country': viewModel.selectedCountry, 'country': viewModel.selectedCountry,
'region': viewModel.selectedRegion
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -83,8 +85,11 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
verticalSpaceMedium, verticalSpaceMedium,
_buildCountryDropDown(viewModel), _buildCountryDropDown(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildRegionDropDown(viewModel), _buildRegionFormField(viewModel),
verticalSpaceMedium, if (viewModel.hasRegionValidationMessage && viewModel.focusRegion)
verticalSpaceTiny,
if (viewModel.hasRegionValidationMessage && viewModel.focusRegion)
_buildRegionValidatorWrapper(viewModel)
]; ];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
@ -112,16 +117,23 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
items: (value, props) => viewModel.getCountries(), items: (value, props) => viewModel.getCountries(),
onChanged: (value) => onChanged: (value) =>
viewModel.setSelectedCountry(value ?? 'Ethiopia')); viewModel.setSelectedCountry(value ?? 'Ethiopia'));
Widget _buildRegionFormField(OnboardingViewModel viewModel) => TextFormField(
controller: regionController,
onTap: viewModel.setRegionFocus,
decoration: inputDecoration(
hint: 'Enter Your City',
focus: viewModel.focusRegion,
filled: regionController.text.isNotEmpty),
);
Widget _buildRegionDropDown(OnboardingViewModel viewModel) => Widget _buildRegionValidatorWrapper(OnboardingViewModel viewModel) =>
CustomDropdownPicker( viewModel.hasRegionValidationMessage
hint: 'Select region', ? _buildRegionValidator(viewModel)
icon: _buildSearchIcon(), : Container();
selectedItem: viewModel.selectedRegion,
items: (value, props) => Widget _buildRegionValidator(OnboardingViewModel viewModel) => Text(
viewModel.getRegions(viewModel.selectedCountry), viewModel.regionValidationMessage!,
onChanged: (value) => style: style12R700,
viewModel.setSelectedRegion(value ?? 'Addis Ababa'),
); );
Icon _buildSearchIcon() => const Icon( Icon _buildSearchIcon() => const Icon(
@ -140,7 +152,9 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => _next(viewModel), onTap: regionController.text.isNotEmpty ? () => _next(viewModel) : null,
backgroundColor: kcPrimaryColor, backgroundColor: regionController.text.isNotEmpty
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
); );
} }

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
@ -25,6 +26,7 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'profile_completed': true, 'profile_completed': true,
'preferred_language': 'en', 'preferred_language': 'en',
'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()),
'favoutite_topic': viewModel.selectedTopic ?? topicController.text, 'favoutite_topic': viewModel.selectedTopic ?? topicController.text,
}; };
viewModel.addUserData(data); viewModel.addUserData(data);

View File

@ -3,7 +3,6 @@ import 'package:flutter/services.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart'; import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/widgets/birthday_selector.dart';
import 'package:yimaru_app/ui/widgets/custom_form_label.dart'; import 'package:yimaru_app/ui/widgets/custom_form_label.dart';
import 'package:yimaru_app/ui/widgets/small_app_bar.dart'; import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
@ -22,6 +21,7 @@ import 'profile_detail_view.form.dart';
@FormView(fields: [ @FormView(fields: [
FormTextField(name: 'email', validator: FormValidator.validateForm), FormTextField(name: 'email', validator: FormValidator.validateForm),
FormTextField(name: 'region', validator: FormValidator.validateForm),
FormTextField( FormTextField(
name: 'phoneNumber', validator: FormValidator.validatePhoneNumberForm), name: 'phoneNumber', validator: FormValidator.validatePhoneNumberForm),
FormTextField(name: 'lastName', validator: FormValidator.validateForm), FormTextField(name: 'lastName', validator: FormValidator.validateForm),
@ -34,13 +34,13 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
Future<void> _update(ProfileDetailViewModel viewModel) async { Future<void> _update(ProfileDetailViewModel viewModel) async {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'region': viewModel.selectedRegion, 'region':regionController.text,
'gender': viewModel.selectedGender, 'gender': viewModel.selectedGender,
'last_name': lastNameController.text, 'last_name': lastNameController.text,
'country': viewModel.selectedCountry, 'country': viewModel.selectedCountry,
'first_name': firstNameController.text, 'first_name': firstNameController.text,
'occupation': occupationController.text, 'occupation': occupationController.text,
'birth_day': viewModel.selectedBirthday, 'birth_day': DateFormat('d MMM, yyyy').format(DateTime.now()),
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -68,15 +68,14 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
void _onModelReady(ProfileDetailViewModel viewModel) { void _onModelReady(ProfileDetailViewModel viewModel) {
phoneNumberController.text = '251900000000'; phoneNumberController.text = '251900000000';
emailController.text = viewModel.user?.email ?? ''; emailController.text = viewModel.user?.email ?? '';
regionController.text = viewModel.user?.region ?? '';
lastNameController.text = viewModel.user?.lastName ?? ''; lastNameController.text = viewModel.user?.lastName ?? '';
firstNameController.text = viewModel.user?.firstName ?? ''; firstNameController.text = viewModel.user?.firstName ?? '';
occupationController.text = viewModel.user?.occupation ?? ''; occupationController.text = viewModel.user?.occupation ?? '';
viewModel.clearUserData(); viewModel.clearUserData();
viewModel.setGender(viewModel.user?.gender ?? ''); viewModel.setGender(viewModel.user?.gender ?? '');
viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia'); viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia');
viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa');
viewModel.setBirthday(viewModel.user?.birthday ??
DateFormat('d MMM, yyyy').format(DateTime.now()));
} }
@override @override
@ -185,14 +184,17 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
verticalSpaceMedium, verticalSpaceMedium,
_buildGenderFormFieldWrapper(viewModel), _buildGenderFormFieldWrapper(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildBirthdayColumn(viewModel),
verticalSpaceSmall,
_buildPhoneNumberFormFieldSection(viewModel), _buildPhoneNumberFormFieldSection(viewModel),
verticalSpaceTiny, verticalSpaceTiny,
_buildEmailFormFieldSection(viewModel), _buildEmailFormFieldSection(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildCountryRegionSection(viewModel), _buildCountryDropdownLabel(),
verticalSpaceSmall,
_buildCountryDropdown(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildRegionFormFieldWrapper(viewModel),
verticalSpaceMedium,
_buildOccupationDropdownWrapper(viewModel), _buildOccupationDropdownWrapper(viewModel),
verticalSpaceLarge, verticalSpaceLarge,
_buildLowerColumn(viewModel) _buildLowerColumn(viewModel)
@ -415,30 +417,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
), ),
); );
Widget _buildBirthdayColumn(ProfileDetailViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildBirthdayChildren(viewModel),
);
List<Widget> _buildBirthdayChildren(ProfileDetailViewModel viewModel) => [
_buildBirthdayLabel(),
verticalSpaceSmall,
_buildBirthdayFormField(viewModel),
];
Widget _buildBirthdayLabel() => CustomFormLabel(
label: 'Birthday',
style: style16DG600,
);
Widget _buildBirthdayFormField(ProfileDetailViewModel viewModel) =>
BirthdaySelector(
birthday: viewModel.selectedBirthday,
onSelected: (value) =>
viewModel.setBirthday(DateFormat('d MMM, yyyy').format(value)),
);
Widget _buildPhoneNumberFormFieldSection(ProfileDetailViewModel viewModel) => Widget _buildPhoneNumberFormFieldSection(ProfileDetailViewModel viewModel) =>
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -533,39 +511,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
style: validationStyle, style: validationStyle,
); );
Widget _buildCountryRegionSection(ProfileDetailViewModel viewModel) => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildCountryRegionChildren(viewModel),
);
List<Widget> _buildCountryRegionChildren(ProfileDetailViewModel viewModel) =>
[
_buildCountryDropdownColumnWrapper(viewModel),
const SizedBox(width: 20),
_buildRegionDropdownColumnWrapper(viewModel)
];
Widget _buildCountryDropdownColumnWrapper(ProfileDetailViewModel viewModel) =>
Expanded(
child: _buildCountryDropdownColumn(viewModel),
);
Widget _buildCountryDropdownColumn(ProfileDetailViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildCountryDropdownChildren(viewModel),
);
List<Widget> _buildCountryDropdownChildren(
ProfileDetailViewModel viewModel) =>
[
_buildCountryDropdownLabel(),
verticalSpaceSmall,
_buildCountryDropdown(viewModel)
];
Widget _buildCountryDropdownLabel() => CustomFormLabel( Widget _buildCountryDropdownLabel() => CustomFormLabel(
label: 'Country', label: 'Country',
style: style16DG600, style: style16DG600,
@ -579,38 +524,48 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
onChanged: (value) => viewModel.setSelectedCountry(value ?? 'Ethiopia'), onChanged: (value) => viewModel.setSelectedCountry(value ?? 'Ethiopia'),
); );
Widget _buildRegionDropdownColumnWrapper(ProfileDetailViewModel viewModel) => Widget _buildRegionFormFieldWrapper(ProfileDetailViewModel viewModel) =>
Expanded( Column(
child: _buildRegionDropdownColumn(viewModel),
);
Widget _buildRegionDropdownColumn(ProfileDetailViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildRegionDropdownChildren(viewModel), children: _buildRegionFormFieldChildren(viewModel),
); );
List<Widget> _buildRegionDropdownChildren(ProfileDetailViewModel viewModel) => List<Widget> _buildRegionFormFieldChildren(
ProfileDetailViewModel viewModel) =>
[ [
_buildRegionDropdownLabel(), _buildRegionFormFieldLabel(),verticalSpaceSmall,
verticalSpaceSmall, _buildRegionFormField(viewModel),
_buildRegionDropdown(viewModel) if (viewModel.hasRegionValidationMessage && viewModel.focusRegion)
verticalSpaceTiny,
if (viewModel.hasRegionValidationMessage && viewModel.focusRegion)
_buildRegionValidatorWrapper(viewModel),
]; ];
Widget _buildRegionDropdownLabel() => CustomFormLabel( Widget _buildRegionFormFieldLabel() => CustomFormLabel(
label: 'Region', label: 'Region',
style: style16DG600, style: style16DG600,
); );
Widget _buildRegionDropdown(ProfileDetailViewModel viewModel) => Widget _buildRegionFormField(ProfileDetailViewModel viewModel) =>
CustomDropdownPicker( TextFormField(
hint: 'Select region', controller: regionController,
selectedItem: viewModel.selectedRegion, onTap: viewModel.setRegionFocus,
items: (value, props) => decoration: inputDecoration(
viewModel.getRegions(viewModel.selectedCountry), hint: 'Enter Your City',
onChanged: (value) => focus: viewModel.focusRegion,
viewModel.setSelectedRegion(value ?? 'Addis Ababa'), filled: regionController.text.isNotEmpty),
);
Widget _buildRegionValidatorWrapper(ProfileDetailViewModel viewModel) =>
viewModel.hasRegionValidationMessage
? _buildRegionValidator(viewModel)
: Container();
Widget _buildRegionValidator(ProfileDetailViewModel viewModel) => Text(
viewModel.regionValidationMessage!,
style: style12R700,
); );
Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) => Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) =>

View File

@ -14,6 +14,7 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart';
const bool _autoTextFieldValidation = true; const bool _autoTextFieldValidation = true;
const String EmailValueKey = 'email'; const String EmailValueKey = 'email';
const String RegionValueKey = 'region';
const String PhoneNumberValueKey = 'phoneNumber'; const String PhoneNumberValueKey = 'phoneNumber';
const String LastNameValueKey = 'lastName'; const String LastNameValueKey = 'lastName';
const String FirstNameValueKey = 'firstName'; const String FirstNameValueKey = 'firstName';
@ -27,6 +28,7 @@ final Map<String, FocusNode> _ProfileDetailViewFocusNodes = {};
final Map<String, String? Function(String?)?> final Map<String, String? Function(String?)?>
_ProfileDetailViewTextValidations = { _ProfileDetailViewTextValidations = {
EmailValueKey: FormValidator.validateForm, EmailValueKey: FormValidator.validateForm,
RegionValueKey: FormValidator.validateForm,
PhoneNumberValueKey: FormValidator.validatePhoneNumberForm, PhoneNumberValueKey: FormValidator.validatePhoneNumberForm,
LastNameValueKey: FormValidator.validateForm, LastNameValueKey: FormValidator.validateForm,
FirstNameValueKey: FormValidator.validateForm, FirstNameValueKey: FormValidator.validateForm,
@ -36,6 +38,8 @@ final Map<String, String? Function(String?)?>
mixin $ProfileDetailView { mixin $ProfileDetailView {
TextEditingController get emailController => TextEditingController get emailController =>
_getFormTextEditingController(EmailValueKey); _getFormTextEditingController(EmailValueKey);
TextEditingController get regionController =>
_getFormTextEditingController(RegionValueKey);
TextEditingController get phoneNumberController => TextEditingController get phoneNumberController =>
_getFormTextEditingController(PhoneNumberValueKey); _getFormTextEditingController(PhoneNumberValueKey);
TextEditingController get lastNameController => TextEditingController get lastNameController =>
@ -46,6 +50,7 @@ mixin $ProfileDetailView {
_getFormTextEditingController(OccupationValueKey); _getFormTextEditingController(OccupationValueKey);
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey); FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey);
FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey); FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey);
FocusNode get lastNameFocusNode => _getFormFocusNode(LastNameValueKey); FocusNode get lastNameFocusNode => _getFormFocusNode(LastNameValueKey);
FocusNode get firstNameFocusNode => _getFormFocusNode(FirstNameValueKey); FocusNode get firstNameFocusNode => _getFormFocusNode(FirstNameValueKey);
@ -76,6 +81,7 @@ mixin $ProfileDetailView {
/// with the latest textController values /// with the latest textController values
void syncFormWithViewModel(FormStateHelper model) { void syncFormWithViewModel(FormStateHelper model) {
emailController.addListener(() => _updateFormData(model)); emailController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model));
phoneNumberController.addListener(() => _updateFormData(model)); phoneNumberController.addListener(() => _updateFormData(model));
lastNameController.addListener(() => _updateFormData(model)); lastNameController.addListener(() => _updateFormData(model));
firstNameController.addListener(() => _updateFormData(model)); firstNameController.addListener(() => _updateFormData(model));
@ -92,6 +98,7 @@ mixin $ProfileDetailView {
) )
void listenToFormUpdated(FormViewModel model) { void listenToFormUpdated(FormViewModel model) {
emailController.addListener(() => _updateFormData(model)); emailController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model));
phoneNumberController.addListener(() => _updateFormData(model)); phoneNumberController.addListener(() => _updateFormData(model));
lastNameController.addListener(() => _updateFormData(model)); lastNameController.addListener(() => _updateFormData(model));
firstNameController.addListener(() => _updateFormData(model)); firstNameController.addListener(() => _updateFormData(model));
@ -106,6 +113,7 @@ mixin $ProfileDetailView {
model.formValueMap model.formValueMap
..addAll({ ..addAll({
EmailValueKey: emailController.text, EmailValueKey: emailController.text,
RegionValueKey: regionController.text,
PhoneNumberValueKey: phoneNumberController.text, PhoneNumberValueKey: phoneNumberController.text,
LastNameValueKey: lastNameController.text, LastNameValueKey: lastNameController.text,
FirstNameValueKey: firstNameController.text, FirstNameValueKey: firstNameController.text,
@ -152,6 +160,7 @@ extension ValueProperties on FormStateHelper {
} }
String? get emailValue => this.formValueMap[EmailValueKey] as String?; String? get emailValue => this.formValueMap[EmailValueKey] as String?;
String? get regionValue => this.formValueMap[RegionValueKey] as String?;
String? get phoneNumberValue => String? get phoneNumberValue =>
this.formValueMap[PhoneNumberValueKey] as String?; this.formValueMap[PhoneNumberValueKey] as String?;
String? get lastNameValue => this.formValueMap[LastNameValueKey] as String?; String? get lastNameValue => this.formValueMap[LastNameValueKey] as String?;
@ -170,6 +179,17 @@ extension ValueProperties on FormStateHelper {
} }
} }
set regionValue(String? value) {
this.setData(
this.formValueMap..addAll({RegionValueKey: value}),
);
if (_ProfileDetailViewTextEditingControllers.containsKey(RegionValueKey)) {
_ProfileDetailViewTextEditingControllers[RegionValueKey]?.text =
value ?? '';
}
}
set phoneNumberValue(String? value) { set phoneNumberValue(String? value) {
this.setData( this.setData(
this.formValueMap..addAll({PhoneNumberValueKey: value}), this.formValueMap..addAll({PhoneNumberValueKey: value}),
@ -221,6 +241,9 @@ extension ValueProperties on FormStateHelper {
bool get hasEmail => bool get hasEmail =>
this.formValueMap.containsKey(EmailValueKey) && this.formValueMap.containsKey(EmailValueKey) &&
(emailValue?.isNotEmpty ?? false); (emailValue?.isNotEmpty ?? false);
bool get hasRegion =>
this.formValueMap.containsKey(RegionValueKey) &&
(regionValue?.isNotEmpty ?? false);
bool get hasPhoneNumber => bool get hasPhoneNumber =>
this.formValueMap.containsKey(PhoneNumberValueKey) && this.formValueMap.containsKey(PhoneNumberValueKey) &&
(phoneNumberValue?.isNotEmpty ?? false); (phoneNumberValue?.isNotEmpty ?? false);
@ -236,6 +259,8 @@ extension ValueProperties on FormStateHelper {
bool get hasEmailValidationMessage => bool get hasEmailValidationMessage =>
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
bool get hasRegionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey]?.isNotEmpty ?? false;
bool get hasPhoneNumberValidationMessage => bool get hasPhoneNumberValidationMessage =>
this.fieldsValidationMessages[PhoneNumberValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[PhoneNumberValueKey]?.isNotEmpty ?? false;
bool get hasLastNameValidationMessage => bool get hasLastNameValidationMessage =>
@ -247,6 +272,8 @@ extension ValueProperties on FormStateHelper {
String? get emailValidationMessage => String? get emailValidationMessage =>
this.fieldsValidationMessages[EmailValueKey]; this.fieldsValidationMessages[EmailValueKey];
String? get regionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey];
String? get phoneNumberValidationMessage => String? get phoneNumberValidationMessage =>
this.fieldsValidationMessages[PhoneNumberValueKey]; this.fieldsValidationMessages[PhoneNumberValueKey];
String? get lastNameValidationMessage => String? get lastNameValidationMessage =>
@ -260,6 +287,8 @@ extension ValueProperties on FormStateHelper {
extension Methods on FormStateHelper { extension Methods on FormStateHelper {
void setEmailValidationMessage(String? validationMessage) => void setEmailValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[EmailValueKey] = validationMessage; this.fieldsValidationMessages[EmailValueKey] = validationMessage;
void setRegionValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[RegionValueKey] = validationMessage;
void setPhoneNumberValidationMessage(String? validationMessage) => void setPhoneNumberValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage; this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage;
void setLastNameValidationMessage(String? validationMessage) => void setLastNameValidationMessage(String? validationMessage) =>
@ -272,6 +301,7 @@ extension Methods on FormStateHelper {
/// Clears text input fields on the Form /// Clears text input fields on the Form
void clearForm() { void clearForm() {
emailValue = ''; emailValue = '';
regionValue = '';
phoneNumberValue = ''; phoneNumberValue = '';
lastNameValue = ''; lastNameValue = '';
firstNameValue = ''; firstNameValue = '';
@ -282,6 +312,7 @@ extension Methods on FormStateHelper {
void validateForm() { void validateForm() {
this.setValidationMessages({ this.setValidationMessages({
EmailValueKey: getValidationMessage(EmailValueKey), EmailValueKey: getValidationMessage(EmailValueKey),
RegionValueKey: getValidationMessage(RegionValueKey),
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
LastNameValueKey: getValidationMessage(LastNameValueKey), LastNameValueKey: getValidationMessage(LastNameValueKey),
FirstNameValueKey: getValidationMessage(FirstNameValueKey), FirstNameValueKey: getValidationMessage(FirstNameValueKey),
@ -306,6 +337,7 @@ String? getValidationMessage(String key) {
void updateValidationData(FormStateHelper model) => void updateValidationData(FormStateHelper model) =>
model.setValidationMessages({ model.setValidationMessages({
EmailValueKey: getValidationMessage(EmailValueKey), EmailValueKey: getValidationMessage(EmailValueKey),
RegionValueKey: getValidationMessage(RegionValueKey),
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
LastNameValueKey: getValidationMessage(LastNameValueKey), LastNameValueKey: getValidationMessage(LastNameValueKey),
FirstNameValueKey: getValidationMessage(FirstNameValueKey), FirstNameValueKey: getValidationMessage(FirstNameValueKey),

View File

@ -47,10 +47,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
String? get selectedGender => _selectedGender; String? get selectedGender => _selectedGender;
// Birthday
String? _selectedBirthday;
String? get selectedBirthday => _selectedBirthday;
// First name // First name
bool _focusPhoneNumber = false; bool _focusPhoneNumber = false;
@ -67,16 +63,17 @@ class ProfileDetailViewModel extends ReactiveViewModel
String get selectedCountry => _selectedCountry; String get selectedCountry => _selectedCountry;
// Region
String _selectedRegion = 'Addis Ababa';
String get selectedRegion => _selectedRegion;
// Occupation // Occupation
bool _focusOccupation = false; bool _focusOccupation = false;
bool get focusOccupation => _focusOccupation; bool get focusOccupation => _focusOccupation;
// Region
bool _focusRegion = false;
bool get focusRegion => _focusRegion;
// User data // User data
final Map<String, dynamic> _userData = {}; final Map<String, dynamic> _userData = {};
@ -100,11 +97,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
rebuildUi(); rebuildUi();
} }
// Birthday
void setBirthday(String value) {
_selectedBirthday = value;
rebuildUi();
}
// Phone number // Phone number
void setPhoneNumberFocus() { void setPhoneNumberFocus() {
@ -119,47 +111,168 @@ class ProfileDetailViewModel extends ReactiveViewModel
} }
// Country // Country
List<String> getCountries() => ['Ethiopia', 'Other']; // Country
List<String> 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"
];
void setSelectedCountry(String value) { void setSelectedCountry(String value) {
_selectedCountry = value; _selectedCountry = value;
if (selectedCountry != 'Ethiopia') {
_selectedRegion = 'Other';
} else {
_selectedRegion = 'Addis Ababa';
}
rebuildUi(); rebuildUi();
} }
// Region
List<String> getRegions(String country) {
if (country == 'Ethiopia') {
return [
'Afar',
'SNNPR',
'Amhara',
'Harari',
'Oromia',
'Sidama',
'Somali',
'Tigray',
'Gambela',
'Dire Dawa',
'Addis Ababa',
'Central Ethiopia',
'Benishangul-Gumuz',
'South West Ethiopia',
];
} else {
return ['Other'];
}
}
void setSelectedRegion(String value) {
_selectedRegion = value;
rebuildUi();
}
// Occupation // Occupation
void setOccupationFocus() { void setOccupationFocus() {
@ -167,6 +280,12 @@ class ProfileDetailViewModel extends ReactiveViewModel
rebuildUi(); rebuildUi();
} }
// Region
void setRegionFocus() {
_focusRegion = true;
rebuildUi();
}
// User data // User data
void addUserData(Map<String, dynamic> data) { void addUserData(Map<String, dynamic> data) {
_userData.addAll(data); _userData.addAll(data);

View File

@ -1,103 +0,0 @@
import 'package:flutter/material.dart';
import '../common/app_colors.dart';
import '../common/ui_helpers.dart';
import 'package:omni_datetime_picker/omni_datetime_picker.dart';
class BirthdaySelector extends StatelessWidget {
final String? birthday;
final void Function(DateTime)? onSelected;
const BirthdaySelector({super.key, this.birthday, this.onSelected});
DateTime _initialDate() {
try {
final parsedDate = format.parse(birthday ?? '');
return parsedDate.isAfter(DateTime.now()) ? DateTime.now() : parsedDate;
} catch (_) {
return DateTime.now();
}
}
Future<void> _pickDateTime(
BuildContext context,
) async {
DateTime? dateTime = await showOmniDateTimePicker(
context: context,
is24HourMode: false,
isShowSeconds: false,
title: _buildTitle(),
lastDate: DateTime.now(),
firstDate: DateTime(1900),
barrierDismissible: true,
initialDate: _initialDate(),
titleSeparator: const Divider(),
padding: const EdgeInsets.all(16),
type: OmniDateTimePickerType.date,
borderRadius: const BorderRadius.all(Radius.circular(15)),
insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
theme: ThemeData(
colorScheme:
const ColorScheme.light().copyWith(primary: kcPrimaryColor),
),
);
if (dateTime != null) {
// String formattedDateTime = DateFormat('d MMM, yyyy').format(dateTime);
if (onSelected != null) {
onSelected!(dateTime);
}
}
}
@override
Widget build(
BuildContext context,
) =>
_buildButtonWrapper(
context,
);
Widget _buildButtonWrapper(BuildContext context) => Container(
height: 50,
width: double.maxFinite,
margin: const EdgeInsets.only(bottom: 15),
child: _buildContainerWrapper(context),
);
Widget _buildContainerWrapper(BuildContext context) => GestureDetector(
onTap: () async => await _pickDateTime(
context,
),
child: _buildContainer(),
);
Widget _buildTitle() => Text('Birthday', style: style16DG600);
Widget _buildContainer() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: kcPrimaryColor.withOpacity(0.1),
border: Border.all(color: kcPrimaryColor),
),
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildButtonRowWrapper(),
);
Widget _buildButtonRowWrapper() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildButtonRowChildren(),
);
List<Widget> _buildButtonRowChildren() => [_buildText(), _buildIcon()];
Widget _buildText() => Text(
birthday ?? 'Pick birthday',
style: const TextStyle(color: kcDarkGrey),
);
Widget _buildIcon() => const Icon(
Icons.calendar_month,
color: kcPrimaryColor,
);
}

View File

@ -15,15 +15,17 @@ class CancelLearnPracticeSheet extends StatelessWidget {
final GestureTapCallback? onContinue; final GestureTapCallback? onContinue;
const CancelLearnPracticeSheet( const CancelLearnPracticeSheet(
{super.key, this.onClose, this.onCancel, this.onContinue,required this.user}); {super.key,
this.onClose,
this.onCancel,
this.onContinue,
required this.user});
@override @override
Widget build(BuildContext context) => Widget build(BuildContext context) => _buildSheetWrapper();
_buildSheetWrapper();
Widget _buildSheetWrapper() => Widget _buildSheetWrapper() => CustomBottomSheet(
CustomBottomSheet( height: 500, onTap: onClose, child: _buildColumnWrapper());
height: 500, onTap: onClose, child: _buildColumnWrapper());
Widget _buildColumnWrapper() => Padding( Widget _buildColumnWrapper() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),

View File

@ -29,11 +29,8 @@ class LearnPracticeResultsWrapper
children: _buildColumnChildren(viewModel), children: _buildColumnChildren(viewModel),
); );
List<Widget> _buildColumnChildren(LearnPracticeViewModel viewModel) => [ List<Widget> _buildColumnChildren(LearnPracticeViewModel viewModel) =>
_buildTitle(), [_buildTitle(), verticalSpaceSmall, _buildResults(viewModel)];
verticalSpaceSmall,
_buildResults(viewModel)
];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Conversation Review', 'Conversation Review',
@ -42,14 +39,14 @@ class LearnPracticeResultsWrapper
); );
Widget _buildResults(LearnPracticeViewModel viewModel) => ListView.separated( Widget _buildResults(LearnPracticeViewModel viewModel) => ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.answers.length, itemCount: viewModel.answers.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall, separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildResult( viewModel.answers[index]), itemBuilder: (context, index) => _buildResult(viewModel.answers[index]),
); );
Widget _buildResult(Map<String, dynamic> answer) => LearnPracticeResultCard(
answer: answer,
Widget _buildResult(Map<String,dynamic> answer) => LearnPracticeResultCard(answer: answer,); );
} }

View File

@ -10,7 +10,8 @@ class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeTipSection({super.key}); const LearnPracticeTipSection({super.key});
@override @override
Widget build(BuildContext context,LearnPracticeViewModel viewModel) => _buildContainer(viewModel); Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildContainer(viewModel);
Widget _buildContainer(LearnPracticeViewModel viewModel) => Container( Widget _buildContainer(LearnPracticeViewModel viewModel) => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25), padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
@ -24,7 +25,7 @@ class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column( Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren( viewModel), children: _buildColumnChildren(viewModel),
); );
List<Widget> _buildColumnChildren(LearnPracticeViewModel viewModel) => List<Widget> _buildColumnChildren(LearnPracticeViewModel viewModel) =>

View File

@ -1,5 +1,5 @@
name: yimaru_app name: yimaru_app
version: 0.1.4+6 version: 0.1.5+7
publish_to: 'none' publish_to: 'none'
description: A new Flutter project. description: A new Flutter project.

View File

@ -1992,6 +1992,13 @@ class MockVoiceRecorderService extends _i1.Mock
), ),
) as _i5.WaveformRecorderController); ) as _i5.WaveformRecorderController);
@override
bool get isRecording => (super.noSuchMethod(
Invocation.getter(#isRecording),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override @override
int get listenersCount => (super.noSuchMethod( int get listenersCount => (super.noSuchMethod(
Invocation.getter(#listenersCount), Invocation.getter(#listenersCount),