diff --git a/StudioProjects/yimaru_app/assets/fonts/Aeonik-Regular.ttf b/StudioProjects/yimaru_app/assets/fonts/Aeonik-Regular.ttf new file mode 100644 index 0000000..4d6eaf5 Binary files /dev/null and b/StudioProjects/yimaru_app/assets/fonts/Aeonik-Regular.ttf differ diff --git a/StudioProjects/yimaru_app/assets/icons/alert.svg b/StudioProjects/yimaru_app/assets/icons/alert.svg new file mode 100644 index 0000000..0cac7fa --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/alert.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StudioProjects/yimaru_app/assets/icons/b1.svg b/StudioProjects/yimaru_app/assets/icons/b1.svg new file mode 100644 index 0000000..4a75083 --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/b1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/StudioProjects/yimaru_app/assets/icons/complete.svg b/StudioProjects/yimaru_app/assets/icons/complete.svg new file mode 100644 index 0000000..4bf1bc3 --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/complete.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StudioProjects/yimaru_app/assets/icons/logo.svg b/StudioProjects/yimaru_app/assets/icons/logo.svg new file mode 100644 index 0000000..d6ffb10 --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/StudioProjects/yimaru_app/assets/icons/mascot.svg b/StudioProjects/yimaru_app/assets/icons/mascot.svg new file mode 100644 index 0000000..4641e42 --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/mascot.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/StudioProjects/yimaru_app/assets/icons/progress_indicator.svg b/StudioProjects/yimaru_app/assets/icons/progress_indicator.svg new file mode 100644 index 0000000..ad20a93 --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/progress_indicator.svg @@ -0,0 +1,4 @@ + + + + diff --git a/StudioProjects/yimaru_app/assets/icons/question.svg b/StudioProjects/yimaru_app/assets/icons/question.svg new file mode 100644 index 0000000..cb9a01d --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/question.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/StudioProjects/yimaru_app/assets/icons/success.svg b/StudioProjects/yimaru_app/assets/icons/success.svg new file mode 100644 index 0000000..4bf1bc3 --- /dev/null +++ b/StudioProjects/yimaru_app/assets/icons/success.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StudioProjects/yimaru_app/assets/images/onboarding_1.png b/StudioProjects/yimaru_app/assets/images/onboarding_1.png new file mode 100644 index 0000000..8650c79 Binary files /dev/null and b/StudioProjects/yimaru_app/assets/images/onboarding_1.png differ diff --git a/StudioProjects/yimaru_app/assets/images/onboarding_2.png b/StudioProjects/yimaru_app/assets/images/onboarding_2.png new file mode 100644 index 0000000..a53af54 Binary files /dev/null and b/StudioProjects/yimaru_app/assets/images/onboarding_2.png differ diff --git a/StudioProjects/yimaru_app/assets/images/onboarding_3.png b/StudioProjects/yimaru_app/assets/images/onboarding_3.png new file mode 100644 index 0000000..4d300f4 Binary files /dev/null and b/StudioProjects/yimaru_app/assets/images/onboarding_3.png differ diff --git a/StudioProjects/yimaru_app/lib/app/app.dart b/StudioProjects/yimaru_app/lib/app/app.dart index 4e96242..c6a337a 100644 --- a/StudioProjects/yimaru_app/lib/app/app.dart +++ b/StudioProjects/yimaru_app/lib/app/app.dart @@ -1,17 +1,18 @@ import 'package:yimaru_app/ui/bottom_sheets/notice/notice_sheet.dart'; import 'package:yimaru_app/ui/dialogs/info_alert/info_alert_dialog.dart'; import 'package:yimaru_app/ui/views/home/home_view.dart'; -import 'package:yimaru_app/ui/views/startup/startup_view.dart'; import 'package:stacked/stacked_annotations.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart'; +import 'package:yimaru_app/ui/views/startup/startup_view.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/language_selector.dart'; // @stacked-import @StackedApp( routes: [ MaterialRoute(page: HomeView), - MaterialRoute(page: StartupView), MaterialRoute(page: OnboardingView), + MaterialRoute(page: StartupView), // @stacked-route ], dependencies: [ diff --git a/StudioProjects/yimaru_app/lib/app/app.router.dart b/StudioProjects/yimaru_app/lib/app/app.router.dart index b85ee7b..b3725f5 100644 --- a/StudioProjects/yimaru_app/lib/app/app.router.dart +++ b/StudioProjects/yimaru_app/lib/app/app.router.dart @@ -5,25 +5,28 @@ // ************************************************************************** // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:flutter/material.dart' as _i5; +import 'package:flutter/material.dart' as _i6; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart' as _i1; -import 'package:stacked_services/stacked_services.dart' as _i6; +import 'package:stacked_services/stacked_services.dart' as _i7; import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2; -import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i4; -import 'package:yimaru_app/ui/views/startup/startup_view.dart' as _i3; +import 'package:yimaru_app/ui/views/onboarding/screens/language_selector.dart' as _i5; +import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3; +import 'package:yimaru_app/ui/views/startup/startup_view.dart' as _i4; class Routes { static const homeView = '/home-view'; + static const onboardingView = '/onboarding-view'; + static const startupView = '/startup-view'; - static const onboardingView = '/onboarding-view'; + static const all = { homeView, - startupView, onboardingView, + startupView, }; } @@ -34,31 +37,38 @@ class StackedRouter extends _i1.RouterBase { page: _i2.HomeView, ), _i1.RouteDef( - Routes.startupView, - page: _i3.StartupView, + Routes.onboardingView, + page: _i3.OnboardingView, ), _i1.RouteDef( - Routes.onboardingView, - page: _i4.OnboardingView, + Routes.startupView, + page: _i4.StartupView, ), + ]; final _pagesMap = { _i2.HomeView: (data) { - return _i5.MaterialPageRoute( + return _i6.MaterialPageRoute( builder: (context) => const _i2.HomeView(), settings: data, ); }, - _i3.StartupView: (data) { - return _i5.MaterialPageRoute( - builder: (context) => const _i3.StartupView(), + _i3.OnboardingView: (data) { + return _i6.MaterialPageRoute( + builder: (context) => const _i3.OnboardingView(), settings: data, ); }, - _i4.OnboardingView: (data) { - return _i5.MaterialPageRoute( - builder: (context) => const _i4.OnboardingView(), + _i4.StartupView: (data) { + return _i6.MaterialPageRoute( + builder: (context) => const _i4.StartupView(), + settings: data, + ); + }, + _i5.LanguageSelector: (data) { + return _i6.MaterialPageRoute( + builder: (context) => const _i5.LanguageSelector(), settings: data, ); }, @@ -71,7 +81,7 @@ class StackedRouter extends _i1.RouterBase { Map get pagesMap => _pagesMap; } -extension NavigatorStateExtension on _i6.NavigationService { +extension NavigatorStateExtension on _i7.NavigationService { Future navigateToHomeView([ int? routerId, bool preventDuplicates = true, @@ -86,20 +96,6 @@ extension NavigatorStateExtension on _i6.NavigationService { transition: transition); } - Future navigateToStartupView([ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - ]) async { - return navigateTo(Routes.startupView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToOnboardingView([ int? routerId, bool preventDuplicates = true, @@ -114,6 +110,22 @@ extension NavigatorStateExtension on _i6.NavigationService { transition: transition); } + Future navigateToStartupView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.startupView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + + Future replaceWithHomeView([ int? routerId, bool preventDuplicates = true, @@ -128,6 +140,20 @@ extension NavigatorStateExtension on _i6.NavigationService { transition: transition); } + Future replaceWithOnboardingView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.onboardingView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + Future replaceWithStartupView([ int? routerId, bool preventDuplicates = true, @@ -142,17 +168,5 @@ extension NavigatorStateExtension on _i6.NavigationService { transition: transition); } - Future replaceWithOnboardingView([ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - ]) async { - return replaceWith(Routes.onboardingView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } + } diff --git a/StudioProjects/yimaru_app/lib/main.dart b/StudioProjects/yimaru_app/lib/main.dart index 9d7a4de..d23ff42 100644 --- a/StudioProjects/yimaru_app/lib/main.dart +++ b/StudioProjects/yimaru_app/lib/main.dart @@ -20,6 +20,7 @@ class MainApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( initialRoute: Routes.startupView, + theme: ThemeData(fontFamily: 'Aeonik'), onGenerateRoute: StackedRouter().onGenerateRoute, navigatorKey: StackedService.navigatorKey, navigatorObservers: [StackedService.routeObserver], diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.dart index e4200db..4206bcc 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.dart @@ -1,25 +1,47 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked/stacked_annotations.dart'; -import 'package:yimaru_app/ui/views/onboarding/forms/age_group_form.dart'; -import 'package:yimaru_app/ui/views/onboarding/forms/country_region_form.dart'; -import 'package:yimaru_app/ui/views/onboarding/forms/educational_background_form.dart'; -import 'package:yimaru_app/ui/views/onboarding/forms/full_name_form.dart'; -import 'package:yimaru_app/ui/views/onboarding/forms/learning_goal_form.dart'; -import 'package:yimaru_app/ui/views/onboarding/forms/learning_reason_form.dart'; -import 'package:yimaru_app/ui/views/onboarding/forms/occupation_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_completion.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_failure.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_intro.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_result.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/first_assessment_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/fourth_assessment_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/result_analysis.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/retake_assessment.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/second_assessment_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/start_lesson.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/assessment/third_assessment_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/age_group_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/challenge_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/country_region_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/educational_background_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/full_name_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/learning_goal_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/learning_reason_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/occupation_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/forms/topic_form.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/language_selector.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/welcome/first_welcome.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/welcome/second_welcome.dart'; +import 'package:yimaru_app/ui/views/onboarding/screens/welcome/third_welcome.dart'; import '../../common/validators/onboarding_form_validator.dart'; import 'onboarding_viewmodel.dart'; import 'onboarding_view.form.dart'; @FormView(fields: [ + FormTextField( + name: 'answer', validator: OnboardingFormValidator.validateForm), FormTextField( name: 'fullName', validator: OnboardingFormValidator.validateForm), + FormTextField( + name: 'challenge', validator: OnboardingFormValidator.validateForm), FormTextField( name: 'occupation', validator: OnboardingFormValidator.validateForm), FormTextField( - name: 'learningReason', validator: OnboardingFormValidator.validateForm) + name: 'learningReason', validator: OnboardingFormValidator.validateForm), + FormTextField(name: 'topic', validator: OnboardingFormValidator.validateForm), ]) class OnboardingView extends StackedView with $OnboardingView { @@ -31,42 +53,99 @@ class OnboardingView extends StackedView OnboardingViewModel viewModel, Widget? child, ) => - _buildOnboardingScreens(viewModel); + _buildOnboardingScreensWrapper(viewModel); + + Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) => + PopScope( + canPop: false, + onPopInvokedWithResult: (value, data) => viewModel.pop( + language: viewModel.currentPage == 23 ? true : false), + child: _buildOnboardingScreens(viewModel)); Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack( - index: viewModel.currentStep, + index: viewModel.currentPage, children: _buildScreens(), ); List _buildScreens() => [ + _buildFirstWelcome(), + _buildSecondWelcome(), + _buildThirdWelcome(), _buildFullNameForm(), _buildEducationalBackgroundForm(), _buildAgeGroupForm(), _buildOccupationForm(), _buildCountryRegionForm(), _buildLearningGoalForm(), - _buildLearningReasonForm() + _buildLearningReasonForm(), + _buildChallengeForm(), + _buildTopicForm(), + _buildAssessmentIntro(), + _buildFirstAssessmentForm(), + _buildSecondAssessment(), + _buildThirdAssessment(), + _buildFourthAssessment(), + _buildAssessmentFailure(), + _buildRetakeAssessment(), + _buildResultAnalysis(), + _buildAssessmentCompletion(), + _buildAssessmentResult(), + _buildStartLesson(), + _buildLanguageSelector() ]; - Widget _buildFullNameForm() => FullNameForm( - fullNameController: fullNameController, - ); + Widget _buildFirstWelcome() => const FirstWelcome(); + + Widget _buildSecondWelcome() => const SecondWelcome(); + + Widget _buildThirdWelcome() => const ThirdWelcome(); + + Widget _buildFullNameForm() => + FullNameForm(fullNameController: fullNameController); Widget _buildEducationalBackgroundForm() => const EducationalBackgroundForm(); Widget _buildAgeGroupForm() => const AgeGroupForm(); - Widget _buildOccupationForm() => OccupationForm( - occupationController: occupationController, - ); + Widget _buildOccupationForm() => + OccupationForm(occupationController: occupationController); Widget _buildCountryRegionForm() => const CountryRegionForm(); Widget _buildLearningGoalForm() => const LearningGoalForm(); - Widget _buildLearningReasonForm() => LearningReasonForm( - learningReasonController: learningReasonController, - ); + Widget _buildLearningReasonForm() => + LearningReasonForm(learningReasonController: learningReasonController); + + Widget _buildChallengeForm() => + ChallengeForm(challengeController: challengeController); + + Widget _buildTopicForm() => TopicForm(topicController: topicController); + + Widget _buildAssessmentIntro() => const AssessmentIntro(); + + Widget _buildFirstAssessmentForm() => + FirstAssessmentForm(answerController: answerController); + + Widget _buildSecondAssessment() => const SecondAssessmentForm(); + + Widget _buildThirdAssessment() => const ThirdAssessmentForm(); + + Widget _buildFourthAssessment() => const FourthAssessmentForm(); + + Widget _buildAssessmentFailure() => const AssessmentFailure(); + + Widget _buildRetakeAssessment() => const RetakeAssessment(); + + Widget _buildResultAnalysis() => const ResultAnalysis(); + + Widget _buildAssessmentCompletion() => const AssessmentCompletion(); + + Widget _buildAssessmentResult() => const AssessmentResult(); + + Widget _buildStartLesson() => const StartLesson(); + + Widget _buildLanguageSelector() => const LanguageSelector(); @override void onViewModelReady(OnboardingViewModel viewModel) { diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.form.dart index 58d4ae0..c2c4d89 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_view.form.dart @@ -12,9 +12,12 @@ import 'package:yimaru_app/ui/common/validators/onboarding_form_validator.dart'; const bool _autoTextFieldValidation = true; +const String AnswerValueKey = 'answer'; const String FullNameValueKey = 'fullName'; +const String ChallengeValueKey = 'challenge'; const String OccupationValueKey = 'occupation'; const String LearningReasonValueKey = 'learningReason'; +const String TopicValueKey = 'topic'; final Map _OnboardingViewTextEditingControllers = {}; @@ -22,23 +25,35 @@ final Map _OnboardingViewTextEditingControllers = final Map _OnboardingViewFocusNodes = {}; final Map _OnboardingViewTextValidations = { + AnswerValueKey: OnboardingFormValidator.validateForm, FullNameValueKey: OnboardingFormValidator.validateForm, + ChallengeValueKey: OnboardingFormValidator.validateForm, OccupationValueKey: OnboardingFormValidator.validateForm, LearningReasonValueKey: OnboardingFormValidator.validateForm, + TopicValueKey: OnboardingFormValidator.validateForm, }; mixin $OnboardingView { + TextEditingController get answerController => + _getFormTextEditingController(AnswerValueKey); TextEditingController get fullNameController => _getFormTextEditingController(FullNameValueKey); + TextEditingController get challengeController => + _getFormTextEditingController(ChallengeValueKey); TextEditingController get occupationController => _getFormTextEditingController(OccupationValueKey); TextEditingController get learningReasonController => _getFormTextEditingController(LearningReasonValueKey); + TextEditingController get topicController => + _getFormTextEditingController(TopicValueKey); + FocusNode get answerFocusNode => _getFormFocusNode(AnswerValueKey); FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey); + FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey); FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey); FocusNode get learningReasonFocusNode => _getFormFocusNode(LearningReasonValueKey); + FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey); TextEditingController _getFormTextEditingController( String key, { @@ -64,9 +79,12 @@ mixin $OnboardingView { /// Registers a listener on every generated controller that calls [model.setData()] /// with the latest textController values void syncFormWithViewModel(FormStateHelper model) { + answerController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model)); + challengeController.addListener(() => _updateFormData(model)); occupationController.addListener(() => _updateFormData(model)); learningReasonController.addListener(() => _updateFormData(model)); + topicController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); } @@ -78,9 +96,12 @@ mixin $OnboardingView { 'This feature was deprecated after 3.1.0.', ) void listenToFormUpdated(FormViewModel model) { + answerController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model)); + challengeController.addListener(() => _updateFormData(model)); occupationController.addListener(() => _updateFormData(model)); learningReasonController.addListener(() => _updateFormData(model)); + topicController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); } @@ -90,9 +111,12 @@ mixin $OnboardingView { model.setData( model.formValueMap ..addAll({ + AnswerValueKey: answerController.text, FullNameValueKey: fullNameController.text, + ChallengeValueKey: challengeController.text, OccupationValueKey: occupationController.text, LearningReasonValueKey: learningReasonController.text, + TopicValueKey: topicController.text, }), ); @@ -134,11 +158,24 @@ extension ValueProperties on FormStateHelper { return !hasAnyValidationMessage; } + String? get answerValue => this.formValueMap[AnswerValueKey] as String?; String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?; + String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?; String? get occupationValue => this.formValueMap[OccupationValueKey] as String?; String? get learningReasonValue => this.formValueMap[LearningReasonValueKey] as String?; + String? get topicValue => this.formValueMap[TopicValueKey] as String?; + + set answerValue(String? value) { + this.setData( + this.formValueMap..addAll({AnswerValueKey: value}), + ); + + if (_OnboardingViewTextEditingControllers.containsKey(AnswerValueKey)) { + _OnboardingViewTextEditingControllers[AnswerValueKey]?.text = value ?? ''; + } + } set fullNameValue(String? value) { this.setData( @@ -151,6 +188,17 @@ extension ValueProperties on FormStateHelper { } } + set challengeValue(String? value) { + this.setData( + this.formValueMap..addAll({ChallengeValueKey: value}), + ); + + if (_OnboardingViewTextEditingControllers.containsKey(ChallengeValueKey)) { + _OnboardingViewTextEditingControllers[ChallengeValueKey]?.text = + value ?? ''; + } + } + set occupationValue(String? value) { this.setData( this.formValueMap..addAll({OccupationValueKey: value}), @@ -174,53 +222,96 @@ extension ValueProperties on FormStateHelper { } } + set topicValue(String? value) { + this.setData( + this.formValueMap..addAll({TopicValueKey: value}), + ); + + if (_OnboardingViewTextEditingControllers.containsKey(TopicValueKey)) { + _OnboardingViewTextEditingControllers[TopicValueKey]?.text = value ?? ''; + } + } + + bool get hasAnswer => + this.formValueMap.containsKey(AnswerValueKey) && + (answerValue?.isNotEmpty ?? false); bool get hasFullName => this.formValueMap.containsKey(FullNameValueKey) && (fullNameValue?.isNotEmpty ?? false); + bool get hasChallenge => + this.formValueMap.containsKey(ChallengeValueKey) && + (challengeValue?.isNotEmpty ?? false); bool get hasOccupation => this.formValueMap.containsKey(OccupationValueKey) && (occupationValue?.isNotEmpty ?? false); bool get hasLearningReason => this.formValueMap.containsKey(LearningReasonValueKey) && (learningReasonValue?.isNotEmpty ?? false); + bool get hasTopic => + this.formValueMap.containsKey(TopicValueKey) && + (topicValue?.isNotEmpty ?? false); + bool get hasAnswerValidationMessage => + this.fieldsValidationMessages[AnswerValueKey]?.isNotEmpty ?? false; bool get hasFullNameValidationMessage => this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false; + bool get hasChallengeValidationMessage => + this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false; bool get hasOccupationValidationMessage => this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false; bool get hasLearningReasonValidationMessage => this.fieldsValidationMessages[LearningReasonValueKey]?.isNotEmpty ?? false; + bool get hasTopicValidationMessage => + this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false; + String? get answerValidationMessage => + this.fieldsValidationMessages[AnswerValueKey]; String? get fullNameValidationMessage => this.fieldsValidationMessages[FullNameValueKey]; + String? get challengeValidationMessage => + this.fieldsValidationMessages[ChallengeValueKey]; String? get occupationValidationMessage => this.fieldsValidationMessages[OccupationValueKey]; String? get learningReasonValidationMessage => this.fieldsValidationMessages[LearningReasonValueKey]; + String? get topicValidationMessage => + this.fieldsValidationMessages[TopicValueKey]; } extension Methods on FormStateHelper { + setAnswerValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[AnswerValueKey] = validationMessage; setFullNameValidationMessage(String? validationMessage) => this.fieldsValidationMessages[FullNameValueKey] = validationMessage; + setChallengeValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[ChallengeValueKey] = validationMessage; setOccupationValidationMessage(String? validationMessage) => this.fieldsValidationMessages[OccupationValueKey] = validationMessage; setLearningReasonValidationMessage(String? validationMessage) => this.fieldsValidationMessages[LearningReasonValueKey] = validationMessage; + setTopicValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[TopicValueKey] = validationMessage; /// Clears text input fields on the Form void clearForm() { + answerValue = ''; fullNameValue = ''; + challengeValue = ''; occupationValue = ''; learningReasonValue = ''; + topicValue = ''; } /// Validates text input fields on the Form void validateForm() { this.setValidationMessages({ + AnswerValueKey: getValidationMessage(AnswerValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey), + ChallengeValueKey: getValidationMessage(ChallengeValueKey), OccupationValueKey: getValidationMessage(OccupationValueKey), LearningReasonValueKey: getValidationMessage(LearningReasonValueKey), + TopicValueKey: getValidationMessage(TopicValueKey), }); } } @@ -240,7 +331,10 @@ String? getValidationMessage(String key) { /// Updates the fieldsValidationMessages on the FormViewModel void updateValidationData(FormStateHelper model) => model.setValidationMessages({ + AnswerValueKey: getValidationMessage(AnswerValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey), + ChallengeValueKey: getValidationMessage(ChallengeValueKey), OccupationValueKey: getValidationMessage(OccupationValueKey), LearningReasonValueKey: getValidationMessage(LearningReasonValueKey), + TopicValueKey: getValidationMessage(TopicValueKey), }); diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_viewmodel.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_viewmodel.dart index f9944c0..2cf1fef 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_viewmodel.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/onboarding_viewmodel.dart @@ -1,14 +1,19 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; +import 'package:yimaru_app/app/app.router.dart'; import '../../../app/app.locator.dart'; import 'onboarding_view.form.dart'; class OnboardingViewModel extends FormViewModel { final _navigationService = locator(); - int _currentStep = 0; + int _currentPage = 0; - int get currentStep => _currentStep; + int get currentPage => _currentPage; + + int _previousPage = 0; + + int get previousPage => _previousPage; // Full name bool _focusFullName = false; @@ -75,9 +80,9 @@ class OnboardingViewModel extends FormViewModel { List> get learningGoals => _learningGoals; // Learning reason - bool _showTextBox = false; + bool _showReasonTextBox = false; - bool get showTextBox => _showTextBox; + bool get showReasonTextBox => _showReasonTextBox; bool _focusLearningReason = false; @@ -97,6 +102,114 @@ class OnboardingViewModel extends FormViewModel { List get learningReasons => _learningReasons; + // Challenges + bool _showChallengeTextBox = false; + + bool get showChallengeTextBox => _showChallengeTextBox; + + bool _focusChallenge = false; + + bool get focusChallenge => _focusChallenge; + + String? _selectedChallenge; + + String? get selectedChallenge => _selectedChallenge; + + final List _challenges = [ + 'Pronunciation', + 'Finding words or grammar quickly', + 'Feeling nervous or lacking confidence', + 'Understanding accents or fast speech', + 'Other' + ]; + + List get challenges => _challenges; + + // Topic + bool _showTopicTextBox = false; + + bool get showTopicTextBox => _showTopicTextBox; + + bool _focusTopic = false; + + bool get focusTopic => _focusTopic; + + String? _selectedTopic; + + String? get selectedTopic => _selectedTopic; + + final List _topics = [ + 'Food & Cooking', + ' Hobbies, Sports, Music', + 'Tech, News, Business', + 'Travel, Places, Culture', + 'Other' + ]; + + List get topics => _topics; + + // First assessment + bool _focusFirstAssessment = false; + + bool get focusFirstAssessment => _focusFirstAssessment; + + // Second assessment + final List _secondAnswers = [ + 'go', + 'goes', + 'went', + 'going', + ]; + + List get secondAnswers => _secondAnswers; + + String? _selectedA2Answer; + + String? get selectedA2Answer => _selectedA2Answer; + + // Third assessment + final List _thirdAnswers = [ + 'reduce', + 'grow', + 'stop', + 'hide', + ]; + + List get thirdAnswers => _thirdAnswers; + + String? _selectedA3Answer; + + String? get selectedA3Answer => _selectedA3Answer; + + // Third assessment + final List _fourthAnswers = [ + 'reduce', + 'grow', + 'stop', + 'hide', + ]; + + List get fourthAnswers => _fourthAnswers; + + String? _selectedA4Answer; + + String? get selectedA4Answer => _selectedA4Answer; + + // Languages + final List> _languages = [ + {'code': 'አማ', 'language': 'አማርኛ'}, + {'code': 'EN', 'language': 'English'}, + ]; + + List> get languages => _languages; + + Map _selectedLanguage = { + 'code': 'EN', + 'language': 'English' + }; + + Map get selectedLanguage => _selectedLanguage; + // Full name void setFullNameFocus() { _focusFullName = true; @@ -150,10 +263,10 @@ class OnboardingViewModel extends FormViewModel { void setSelectedLearningReason(String title) { _selectedLearningReason = title; if (title.toLowerCase() == 'other') { - _showTextBox = true; + _showReasonTextBox = true; } else { - if (_showTextBox) { - _showTextBox = false; + if (_showReasonTextBox) { + _showReasonTextBox = false; _focusLearningReason = false; } } @@ -163,13 +276,114 @@ class OnboardingViewModel extends FormViewModel { bool isSelectedLearningReason(String title) => _selectedLearningReason == title; - void next() { - _currentStep++; + // Challenges + void setChallengesFocus() { + _focusChallenge = true; rebuildUi(); } - void pop() { - _currentStep--; + void setSelectedChallenge(String title) { + _selectedChallenge = title; + if (title.toLowerCase() == 'other') { + _showChallengeTextBox = true; + } else { + if (_showChallengeTextBox) { + _showChallengeTextBox = false; + _focusChallenge = false; + } + } + rebuildUi(); + } + + bool isSelectedChallenge(String title) => _selectedChallenge == title; + + // Topics + void setTopicsFocus() { + _focusTopic = true; + rebuildUi(); + } + + void setSelectedTopic(String title) { + _selectedTopic = title; + if (title.toLowerCase() == 'other') { + _showTopicTextBox = true; + } else { + if (_showTopicTextBox) { + _showTopicTextBox = false; + _focusTopic = false; + } + } + rebuildUi(); + } + + bool isSelectedTopic(String title) => _selectedTopic == title; + + // First assessment + void setFirstAssessmentFocus() { + _focusFirstAssessment = true; + rebuildUi(); + } + + // Second assessment + void setSelectedA2Answer(String title) { + _selectedA2Answer = title; + rebuildUi(); + } + + bool isSelectedA2Answer(String title) => _selectedA2Answer == title; + + // Third assessment + void setSelectedA3Answer(String title) { + _selectedA3Answer = title; + rebuildUi(); + } + + bool isSelectedA3Answer(String title) => _selectedA3Answer == title; + + // Fourth assessment + void setSelectedA4Answer(String title) { + _selectedA4Answer = title; + rebuildUi(); + } + + bool isSelectedA4Answer(String title) => _selectedA4Answer == title; + + // Language + void setSelectedLanguage(Map title) { + _selectedLanguage = title; + rebuildUi(); + } + + bool isSelectedLanguage(String title) => + _selectedLanguage['language'] == title; + + void next({int? page}) async { + if (page == null) { + if (_previousPage != 0) { + _currentPage = _previousPage; + } else { + _currentPage++; + if (_currentPage == 19) { + rebuildUi(); + await Future.delayed(const Duration(seconds: 3)); + _currentPage++; + } + } + } else { + _previousPage = _currentPage; + _currentPage = page; + } + rebuildUi(); + } + + void pop({bool language = false}) { + if (!language) { + _currentPage--; + } else { + _currentPage = _previousPage; + _previousPage = 0; + } + rebuildUi(); } } diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_completion.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_completion.dart new file mode 100644 index 0000000..1b7641a --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_completion.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/onboarding_app_bar.dart'; + +class AssessmentCompletion extends ViewModelWidget { + const AssessmentCompletion({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _buildExpandedBody(viewModel)]; + + Widget _buildAppBar() => const OnboardingAppBar( + showBackButton: false, + showLanguageSelection: false, + ); + + 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.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyChildren(viewModel), + ); + + List _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceLarge, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + ]; + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/complete.svg', + ); + + Widget _buildTitle() => const Text( + 'Assessment complete!', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'We’re now analyzing your speaking skills', + textAlign: TextAlign.center, + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + text: 'View My Results', + onTap: () => viewModel.next(), + foregroundColor: kcWhiteColor, + backgroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_failure.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_failure.dart new file mode 100644 index 0000000..c2a5fc6 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_failure.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/onboarding_app_bar.dart'; + +class AssessmentFailure extends ViewModelWidget { + const AssessmentFailure({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _buildExpandedBody(viewModel)]; + + Widget _buildAppBar() => const OnboardingAppBar(showBackButton: false); + + 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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceLarge, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + ]; + + Widget _buildIcon() => SvgPicture.asset('assets/icons/alert.svg'); + + Widget _buildTitle() => const Text( + 'We didn’t get enough from your assessment', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'Your assessment wasn’t long enough for us to analyze your speaking level. You can retake the call to get accurate results ', + textAlign: TextAlign.center, + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + children: _buildLowerColumnChildren(viewModel), + ); + + List _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ + _buildContinueButton(viewModel), + verticalSpaceMedium, + _buildSkipButtonWrapper(viewModel) + ]; + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + text: 'Continue Assessment', + onTap: () => viewModel.next(), + foregroundColor: kcWhiteColor, + backgroundColor: kcPrimaryColor, + ); + + Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildSkipButton(viewModel), + ); + + Widget _buildSkipButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + text: 'Skip', + borderRadius: 12, + borderColor: kcPrimaryColor, + onTap: () => viewModel.next(), + backgroundColor: kcWhiteColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_intro.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_intro.dart new file mode 100644 index 0000000..fc376a0 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_intro.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.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/onboarding_app_bar.dart'; + +class AssessmentIntro extends ViewModelWidget { + const AssessmentIntro({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + ]; + + Widget _buildAppBar() => const OnboardingAppBar(showBackButton: false); + + Widget _buildTitle() => const Text( + 'Want a quick assessment to know your English level?', + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'Answer a few quick questions to help us understand your English proficiency.', + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + children: _buildLowerColumnChildren(viewModel), + ); + + List _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ + _buildContinueButton(viewModel), + verticalSpaceMedium, + _buildSkipButtonWrapper(viewModel) + ]; + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + text: 'Continue', + borderRadius: 12, + onTap: () => viewModel.next(), + foregroundColor: kcWhiteColor, + backgroundColor: kcPrimaryColor, + ); + + Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildSkipButton(viewModel), + ); + + Widget _buildSkipButton(OnboardingViewModel viewModel) => + const CustomElevatedButton( + height: 55, + text: 'Skip', + borderRadius: 12, + borderColor: kcPrimaryColor, + backgroundColor: kcWhiteColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_result.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_result.dart new file mode 100644 index 0000000..c7e2186 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/assessment_result.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/onboarding_app_bar.dart'; + +class AssessmentResult extends ViewModelWidget { + const AssessmentResult({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _buildExpandedBody(viewModel)]; + + Widget _buildAppBar() => const OnboardingAppBar(showBackButton: false); + + 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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceLarge, + _buildTitle(), + verticalSpaceSmall, + _buildPrimarySubTitle(), + verticalSpaceMedium, + _buildIcon(), + verticalSpaceMedium, + _buildSecondarySubTitle() + ]; + + Widget _buildTitle() => const Text( + 'You’re likely a B1 speaker!', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + color: kcPrimaryColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildPrimarySubTitle() => const Text( + 'Great Job! Here’s your next step to keep improving.', + textAlign: TextAlign.center, + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildIcon() => SvgPicture.asset('assets/icons/b1.svg'); + + Widget _buildSecondarySubTitle() => const Text( + 'Let\'s start your practice', + textAlign: TextAlign.center, + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + children: _buildLowerColumnChildren(viewModel), + ); + + List _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ + _buildContinueButton(viewModel), + verticalSpaceMedium, + _buildSkipButtonWrapper(viewModel) + ]; + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + text: 'Continue', + borderRadius: 12, + onTap: () => viewModel.next(), + foregroundColor: kcWhiteColor, + backgroundColor: kcPrimaryColor, + ); + + Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildSkipButton(viewModel), + ); + + Widget _buildSkipButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + text: 'Practice Speaking', + borderColor: kcPrimaryColor, + onTap: () => viewModel.next(), + backgroundColor: kcWhiteColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/first_assessment_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/first_assessment_form.dart new file mode 100644 index 0000000..5ccb94e --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/first_assessment_form.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.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/onboarding_app_bar.dart'; + +import '../../onboarding_view.form.dart'; + +class FirstAssessmentForm extends ViewModelWidget { + final TextEditingController answerController; + + const FirstAssessmentForm({super.key, required this.answerController}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceLarge, + _buildFirstAssessmentFormField(viewModel), + if (viewModel.hasAnswerValidationMessage && + viewModel.focusFirstAssessment) + verticalSpaceTiny, + if (viewModel.hasAnswerValidationMessage && + viewModel.focusFirstAssessment) + _buildFirstAssessmentValidatorWrapper(viewModel) + ]; + + Widget _buildAppBar() => const OnboardingAppBar( + showBackButton: false, + showLanguageSelection: false, + ); + + Widget _buildTitle() => const Text( + '1. What is the plural of “book”?', + style: TextStyle( + fontSize: 16, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildFirstAssessmentFormField(OnboardingViewModel viewModel) => + TextFormField( + controller: answerController, + onTap: viewModel.setFirstAssessmentFocus, + decoration: inputDecoration(focus: viewModel.focusFirstAssessment), + ); + + Widget _buildFirstAssessmentValidatorWrapper(OnboardingViewModel viewModel) => + viewModel.hasAnswerValidationMessage + ? _buildFirstAssessmentValidator(viewModel) + : Container(); + + Widget _buildFirstAssessmentValidator(OnboardingViewModel viewModel) => Text( + viewModel.answerValidationMessage!, + style: const TextStyle( + fontSize: 12, + color: Colors.red, + fontWeight: FontWeight.w700, + ), + ); + + 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: kcWhiteColor, + backgroundColor: + viewModel.focusFirstAssessment && answerController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), + onTap: + viewModel.focusFirstAssessment && answerController.text.isNotEmpty + ? () => viewModel.next() + : null, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/fourth_assessment_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/fourth_assessment_form.dart new file mode 100644 index 0000000..5fb4f43 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/fourth_assessment_form.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.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/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class FourthAssessmentForm extends ViewModelWidget { + const FourthAssessmentForm({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceMedium, + _buildAnswers(viewModel) + ]; + + Widget _buildAppBar() => const OnboardingAppBar( + showBackButton: false, + showLanguageSelection: false, + ); + + Widget _buildTitle() => const Text( + 'Q4.  Choose the word that best matches the meaning of ‘meticulous’:', + style: TextStyle( + fontSize: 16, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildAnswers(OnboardingViewModel viewModel) => ListView.builder( + shrinkWrap: true, + itemCount: viewModel.fourthAnswers.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildAnswer( + title: viewModel.fourthAnswers[index], + selected: + viewModel.isSelectedA4Answer(viewModel.fourthAnswers[index]), + onTap: () => + viewModel.setSelectedA4Answer(viewModel.fourthAnswers[index]), + ), + ); + + Widget _buildAnswer( + {required String title, + required bool selected, + required GestureTapCallback onTap}) => + CustomSmallRadioButton( + title: title, + onTap: onTap, + selected: selected, + ); + + 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: kcWhiteColor, + onTap: + viewModel.selectedA4Answer != null ? () => viewModel.next() : null, + backgroundColor: viewModel.selectedA4Answer != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/result_analysis.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/result_analysis.dart new file mode 100644 index 0000000..48399e5 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/result_analysis.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/onboarding_app_bar.dart'; + +class ResultAnalysis extends ViewModelWidget { + const ResultAnalysis({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMassive, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + ]; + + Widget _buildAppBar() => const OnboardingAppBar(showBackButton: false); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/progress_indicator.svg', + ); + + Widget _buildTitle() => const Text( + 'Analyzing your results…', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'We’re now analyzing your speaking skills', + textAlign: TextAlign.center, + style: TextStyle(color: kcMediumGrey), + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/retake_assessment.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/retake_assessment.dart new file mode 100644 index 0000000..f960b11 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/retake_assessment.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.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/onboarding_app_bar.dart'; + +class RetakeAssessment extends ViewModelWidget { + const RetakeAssessment({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceLarge, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + ]; + + Widget _buildAppBar() => const OnboardingAppBar(showBackButton: false); + + Widget _buildIcon() => const Icon( + Icons.warning_amber_rounded, + size: 100, + color: kcPrimaryColor, + ); + + Widget _buildTitle() => const Text( + 'We didn’t get enough from your assessment', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'Your assessment wasn’t long enough for us to analyze your speaking level. You can retake the call to get accurate results ', + textAlign: TextAlign.center, + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + children: _buildLowerColumnChildren(viewModel), + ); + + List _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ + _buildContinueButton(viewModel), + verticalSpaceMedium, + _buildSkipButtonWrapper(viewModel) + ]; + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + text: 'Retake Assessment', + onTap: () => viewModel.next(), + foregroundColor: kcWhiteColor, + backgroundColor: kcPrimaryColor, + ); + + Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildSkipButton(viewModel), + ); + + Widget _buildSkipButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + text: 'Skip', + borderRadius: 12, + borderColor: kcPrimaryColor, + onTap: () => viewModel.next(), + backgroundColor: kcWhiteColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/second_assessment_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/second_assessment_form.dart new file mode 100644 index 0000000..0869e2c --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/second_assessment_form.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.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/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class SecondAssessmentForm extends ViewModelWidget { + const SecondAssessmentForm({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceMedium, + _buildAnswers(viewModel) + ]; + + Widget _buildAppBar() => const OnboardingAppBar( + showBackButton: false, + showLanguageSelection: false, + ); + + Widget _buildTitle() => const Text( + 'Q2. Choose the correct word to complete the sentence:\nI ____ to school yesterday. ', + style: TextStyle( + fontSize: 16, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildAnswers(OnboardingViewModel viewModel) => ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: viewModel.secondAnswers.length, + itemBuilder: (context, index) => _buildAnswer( + title: viewModel.secondAnswers[index], + selected: + viewModel.isSelectedA2Answer(viewModel.secondAnswers[index]), + onTap: () => + viewModel.setSelectedA2Answer(viewModel.secondAnswers[index]), + ), + ); + + Widget _buildAnswer( + {required String title, + required bool selected, + required GestureTapCallback onTap}) => + CustomSmallRadioButton( + title: title, + onTap: onTap, + selected: selected, + ); + + 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: kcWhiteColor, + onTap: viewModel.selectedA2Answer != null + ? () => viewModel.next() + : null, + backgroundColor: viewModel.selectedA2Answer != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/start_lesson.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/start_lesson.dart new file mode 100644 index 0000000..d8de773 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/start_lesson.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/onboarding_app_bar.dart'; + +class StartLesson extends ViewModelWidget { + const StartLesson({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _buildExpandedBody(viewModel)]; + + Widget _buildAppBar() => const OnboardingAppBar( + showBackButton: false, + showLanguageSelection: false, + ); + + 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.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyChildren(viewModel), + ); + + List _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceLarge, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + ]; + + Widget _buildIcon() => SvgPicture.asset('assets/icons/mascot.svg'); + + Widget _buildTitle() => const Text.rich( + TextSpan( + text: 'Welcome aboard', + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + children: [ + TextSpan( + text: ', Bisrat!', + style: TextStyle( + fontSize: 25, + color: kcPrimaryColor, + fontWeight: FontWeight.w600, + ), + ) + ]), + ); + + Widget _buildSubTitle() => const Text( + 'You’re ready to explore your personalized lessons.', + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + const CustomElevatedButton( + height: 55, + borderRadius: 12, + text: 'Go to My Lessons', + foregroundColor: kcWhiteColor, + backgroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/third_assessment_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/third_assessment_form.dart new file mode 100644 index 0000000..2c7cd26 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/assessment/third_assessment_form.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.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/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class ThirdAssessmentForm extends ViewModelWidget { + const ThirdAssessmentForm({super.key}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceMedium, + _buildAnswers(viewModel) + ]; + + Widget _buildAppBar() => const OnboardingAppBar( + showBackButton: false, + showLanguageSelection: false, + ); + + Widget _buildTitle() => const Text( + 'Q3. Which word means the same as ‘expand’?', + style: TextStyle( + fontSize: 16, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildAnswers(OnboardingViewModel viewModel) => ListView.builder( + shrinkWrap: true, + itemCount: viewModel.thirdAnswers.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildAnswer( + title: viewModel.thirdAnswers[index], + selected: viewModel.isSelectedA3Answer(viewModel.thirdAnswers[index]), + onTap: () => + viewModel.setSelectedA3Answer(viewModel.thirdAnswers[index]), + ), + ); + + Widget _buildAnswer( + {required String title, + required bool selected, + required GestureTapCallback onTap}) => + CustomSmallRadioButton( + title: title, + onTap: onTap, + selected: selected, + ); + + 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: kcWhiteColor, + backgroundColor: viewModel.selectedA3Answer != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), + onTap: + viewModel.selectedA3Answer != null ? () => viewModel.next() : null, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/age_group_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/age_group_form.dart similarity index 88% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/age_group_form.dart rename to StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/age_group_form.dart index a4ee919..b8d0f9c 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/age_group_form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/age_group_form.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.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/shared/widgets/custom_elevated_button.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/views/onboarding/widgets/custom_small_radio_button.dart'; -import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart'; +import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; class AgeGroupForm extends ViewModelWidget { const AgeGroupForm({super.key}); @@ -14,6 +14,8 @@ class AgeGroupForm extends ViewModelWidget { Widget build(BuildContext context, OnboardingViewModel viewModel) => _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, body: _buildScaffold(viewModel), @@ -72,15 +74,13 @@ class AgeGroupForm extends ViewModelWidget { Widget _buildSubTitle() => const Text( 'We’ll personalize your learning experience based on your age.', - style: TextStyle( - color: kcMediumGrey, - ), + style: TextStyle(color: kcMediumGrey), ); Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder( shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), itemCount: viewModel.ageGroups.length, + physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildAgeGroup( title: viewModel.ageGroups[index], selected: viewModel.isSelectedAgeGroup(viewModel.ageGroups[index]), @@ -109,8 +109,11 @@ class AgeGroupForm extends ViewModelWidget { height: 55, text: 'Continue', borderRadius: 12, - onTap: () => viewModel.next(), foregroundColor: kcWhiteColor, - backgroundColor: kcPrimaryColor, + backgroundColor: viewModel.selectedAgeGroup != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), + onTap: + viewModel.selectedAgeGroup != null ? () => viewModel.next() : null, ); } diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/challenge_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/challenge_form.dart new file mode 100644 index 0000000..8eee3aa --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/challenge_form.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.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_view.form.dart'; +import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class ChallengeForm extends ViewModelWidget { + final TextEditingController challengeController; + + const ChallengeForm({super.key, required this.challengeController}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _buildExpandedBody(viewModel)]; + + Widget _buildExpandedBody(OnboardingViewModel viewModel) => + Expanded(child: _buildBodyWrapper(viewModel)); + + Widget _buildBodyWrapper(OnboardingViewModel viewModel) => + SingleChildScrollView( + child: 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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + verticalSpaceMedium, + _buildChallenges(viewModel), + if (viewModel.showChallengeTextBox) _buildChallengeFormField(viewModel), + if (viewModel.showChallengeTextBox && + viewModel.hasChallengeValidationMessage && + viewModel.focusChallenge) + verticalSpaceTiny, + if (viewModel.showChallengeTextBox && + viewModel.hasChallengeValidationMessage && + viewModel.focusChallenge) + _buildChallengeValidatorWrapper(viewModel), + verticalSpaceMedium, + ]; + + Widget _buildAppBar() => const OnboardingAppBar(); + + Widget _buildTitle() => const Text( + 'What challenge do you face most with English?', + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'Everyone has struggles, let’s start fixing yours 😊', + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildChallenges(OnboardingViewModel viewModel) => ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: viewModel.challenges.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildChallenge( + title: viewModel.challenges[index], + onTap: () => + viewModel.setSelectedChallenge(viewModel.challenges[index]), + selected: viewModel.isSelectedChallenge(viewModel.challenges[index]), + ), + ); + + Widget _buildChallenge( + {required String title, + required bool selected, + required GestureTapCallback onTap}) => + CustomSmallRadioButton( + title: title, + onTap: onTap, + selected: selected, + ); + + Widget _buildChallengeFormField(OnboardingViewModel viewModel) => + TextFormField( + maxLines: 3, + controller: challengeController, + onTap: viewModel.setChallengesFocus, + decoration: inputDecoration(focus: true, hint: 'Write your challenge…'), + ); + + Widget _buildChallengeValidatorWrapper(OnboardingViewModel viewModel) => + viewModel.hasChallengeValidationMessage + ? _buildChallengeValidator(viewModel) + : Container(); + + Widget _buildChallengeValidator(OnboardingViewModel viewModel) => Text( + viewModel.challengeValidationMessage!, + style: const TextStyle( + fontSize: 12, + color: Colors.red, + fontWeight: FontWeight.w700, + ), + ); + + 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: kcWhiteColor, + onTap: viewModel.selectedChallenge != null + ? viewModel.selectedChallenge?.toLowerCase() == 'other' + ? viewModel.focusChallenge + ? () => viewModel.next() + : null + : () => viewModel.next() + : null, + backgroundColor: viewModel.selectedChallenge != null + ? viewModel.selectedChallenge?.toLowerCase() == 'other' + ? viewModel.focusChallenge && + challengeController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2) + : kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2)); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/country_region_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/country_region_form.dart similarity index 82% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/country_region_form.dart rename to StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/country_region_form.dart index b8a58a8..5af3ee4 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/country_region_form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/country_region_form.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.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/shared/widgets/custom_elevated_button.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/views/onboarding/widgets/custom_dropdown.dart'; -import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart'; +import 'package:yimaru_app/ui/widgets/custom_dropdown.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; class CountryRegionForm extends ViewModelWidget { const CountryRegionForm({super.key}); @@ -75,26 +75,26 @@ class CountryRegionForm extends ViewModelWidget { Widget _buildSubTitle() => const Text( 'Select your country and region from the dropdown', - style: TextStyle( - color: kcMediumGrey, - ), + style: TextStyle(color: kcMediumGrey), ); Widget _buildCountryDropDown(OnboardingViewModel viewModel) => CustomDropDownPicker( - hint: 'Select country', - icon: _buildSearchIcon(), - items: (value, props) => viewModel.getCountries(), - onChanged: (value) {}, - selectedItem: 'Ethiopia'); + onChanged: (value) {}, + hint: 'Select country', + icon: _buildSearchIcon(), + selectedItem: 'Ethiopia', + items: (value, props) => viewModel.getCountries(), + ); Widget _buildRegionDropDown(OnboardingViewModel viewModel) => CustomDropDownPicker( - hint: 'Select region', - icon: _buildSearchIcon(), - items: (value, props) => viewModel.getRegions('Addis Ababa'), - onChanged: (value) {}, - selectedItem: 'Addis Ababa'); + hint: 'Select region', + onChanged: (value) {}, + icon: _buildSearchIcon(), + selectedItem: 'Addis Ababa', + items: (value, props) => viewModel.getRegions('Addis Ababa'), + ); Icon _buildSearchIcon() => const Icon( Icons.search, diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/educational_background_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/educational_background_form.dart similarity index 88% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/educational_background_form.dart rename to StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/educational_background_form.dart index 9dc61c6..963f474 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/educational_background_form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/educational_background_form.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.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/shared/widgets/custom_elevated_button.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/views/onboarding/widgets/custom_small_radio_button.dart'; -import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart'; +import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; class EducationalBackgroundForm extends ViewModelWidget { const EducationalBackgroundForm({super.key}); @@ -14,6 +14,7 @@ class EducationalBackgroundForm extends ViewModelWidget { Widget build(BuildContext context, OnboardingViewModel viewModel) => _buildScaffoldWrapper(viewModel); + Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, body: _buildScaffold(viewModel), @@ -72,9 +73,7 @@ class EducationalBackgroundForm extends ViewModelWidget { Widget _buildSubTitle() => const Text( 'This helps us tailor your lessons to your experience.', - style: TextStyle( - color: kcMediumGrey, - ), + style: TextStyle(color: kcMediumGrey), ); Widget _buildEducationalLevels(OnboardingViewModel viewModel) => @@ -111,8 +110,12 @@ class EducationalBackgroundForm extends ViewModelWidget { height: 55, text: 'Continue', borderRadius: 12, - onTap: () => viewModel.next(), foregroundColor: kcWhiteColor, - backgroundColor: kcPrimaryColor, + onTap: viewModel.selectedEducationalBackground != null + ? () => viewModel.next() + : null, + backgroundColor: viewModel.selectedEducationalBackground != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), ); } diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/full_name_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/full_name_form.dart similarity index 84% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/full_name_form.dart rename to StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/full_name_form.dart index 0432480..d153648 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/full_name_form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/full_name_form.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.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/shared/widgets/custom_elevated_button.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/views/onboarding/widgets/onboarding_app_bar.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; -import '../onboarding_view.form.dart'; +import '../../onboarding_view.form.dart'; class FullNameForm extends ViewModelWidget { final TextEditingController fullNameController; @@ -66,9 +66,7 @@ class FullNameForm extends ViewModelWidget { _buildFullNameValidatorWrapper(viewModel) ]; - Widget _buildAppBar() => const OnboardingAppBar( - showBackButton: false, - ); + Widget _buildAppBar() => const OnboardingAppBar(showBackButton: false); Widget _buildTitle() => const Text( 'What should we call you? 😊', @@ -81,17 +79,14 @@ class FullNameForm extends ViewModelWidget { Widget _buildSubTitle() => const Text( 'We’ll use your name to personalize your learning journey.', - style: TextStyle( - color: kcMediumGrey, - ), + style: TextStyle(color: kcMediumGrey), ); Widget _buildFullNameFormField(OnboardingViewModel viewModel) => TextFormField( controller: fullNameController, onTap: viewModel.setFullNameFocus, - decoration: inputDecoration(focus:viewModel.focusFullName), - + decoration: inputDecoration(focus: viewModel.focusFullName), ); Widget _buildFullNameValidatorWrapper(OnboardingViewModel viewModel) => @@ -102,8 +97,8 @@ class FullNameForm extends ViewModelWidget { Widget _buildFullNameValidator(OnboardingViewModel viewModel) => Text( viewModel.fullNameValidationMessage!, style: const TextStyle( - color: Colors.red, fontSize: 12, + color: Colors.red, fontWeight: FontWeight.w700, ), ); @@ -118,8 +113,13 @@ class FullNameForm extends ViewModelWidget { height: 55, text: 'Continue', borderRadius: 12, - onTap: () => viewModel.next(), foregroundColor: kcWhiteColor, - backgroundColor: kcPrimaryColor, + onTap: viewModel.focusFullName && fullNameController.text.isNotEmpty + ? () => viewModel.next() + : null, + backgroundColor: + viewModel.focusFullName && fullNameController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), ); } diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_goal_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/learning_goal_form.dart similarity index 90% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_goal_form.dart rename to StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/learning_goal_form.dart index 3f38523..1c3cc9a 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_goal_form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/learning_goal_form.dart @@ -3,10 +3,10 @@ import 'package:iconsax/iconsax.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/shared/widgets/custom_elevated_button.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/views/onboarding/widgets/custom_large_radio_button.dart'; -import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart'; +import 'package:yimaru_app/ui/widgets/custom_large_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; class LearningGoalForm extends ViewModelWidget { const LearningGoalForm({super.key}); @@ -83,8 +83,8 @@ class LearningGoalForm extends ViewModelWidget { Widget _buildLearningGoals(OnboardingViewModel viewModel) => ListView.builder( shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), itemCount: viewModel.learningGoals.length, + physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildLearningGoal( title: viewModel.learningGoals[index]['title'], icon: getIcon(viewModel.learningGoals[index]['icon']), @@ -120,8 +120,12 @@ class LearningGoalForm extends ViewModelWidget { height: 55, text: 'Continue', borderRadius: 12, - onTap: () => viewModel.next(), foregroundColor: kcWhiteColor, - backgroundColor: kcPrimaryColor, + onTap: viewModel.selectedLearningGoal != null + ? () => viewModel.next() + : null, + backgroundColor: viewModel.selectedLearningGoal != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), ); } diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_reason_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/learning_reason_form.dart similarity index 80% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_reason_form.dart rename to StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/learning_reason_form.dart index 7abdd27..09e9c62 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/learning_reason_form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/learning_reason_form.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.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/shared/widgets/custom_elevated_button.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; -import 'package:yimaru_app/ui/views/onboarding/widgets/custom_small_radio_button.dart'; -import 'package:yimaru_app/ui/views/onboarding/widgets/onboarding_app_bar.dart'; +import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; class LearningReasonForm extends ViewModelWidget { final TextEditingController learningReasonController; @@ -63,12 +63,12 @@ class LearningReasonForm extends ViewModelWidget { _buildSubTitle(), verticalSpaceMedium, _buildReasons(viewModel), - if (viewModel.showTextBox) _buildReasonFormField(viewModel), - if (viewModel.showTextBox && + if (viewModel.showReasonTextBox) _buildReasonFormField(viewModel), + if (viewModel.showReasonTextBox && viewModel.hasLearningReasonValidationMessage && viewModel.focusLearningReason) verticalSpaceTiny, - if (viewModel.showTextBox && + if (viewModel.showReasonTextBox && viewModel.hasLearningReasonValidationMessage && viewModel.focusLearningReason) _buildReasonValidatorWrapper(viewModel), @@ -145,11 +145,22 @@ class LearningReasonForm extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( - height: 55, - text: 'Continue', - borderRadius: 12, - onTap: () => viewModel.next(), - foregroundColor: kcWhiteColor, - backgroundColor: kcPrimaryColor, - ); + height: 55, + text: 'Continue', + borderRadius: 12, + foregroundColor: kcWhiteColor, + onTap: viewModel.selectedLearningReason != null + ? viewModel.selectedLearningReason?.toLowerCase() == 'other' + ? viewModel.focusLearningReason + ? () => viewModel.next() + : null + : () => viewModel.next() + : null, + backgroundColor: viewModel.selectedLearningReason != null + ? viewModel.selectedLearningReason?.toLowerCase() == 'other' + ? viewModel.focusLearningReason && learningReasonController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2) + : kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2)); } diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/occupation_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/occupation_form.dart similarity index 88% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/occupation_form.dart rename to StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/occupation_form.dart index 51f6c14..f8f155d 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/forms/occupation_form.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/occupation_form.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.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/shared/widgets/custom_elevated_button.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/views/onboarding/widgets/onboarding_app_bar.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; -import '../onboarding_view.form.dart'; +import '../../onboarding_view.form.dart'; class OccupationForm extends ViewModelWidget { final TextEditingController occupationController; @@ -81,9 +81,7 @@ class OccupationForm extends ViewModelWidget { Widget _buildSubTitle() => const Text( 'We’ll personalize your learning experience based on your occupation.', - style: TextStyle( - color: kcMediumGrey, - ), + style: TextStyle(color: kcMediumGrey), ); Widget _buildOccupationFormField(OnboardingViewModel viewModel) => @@ -101,8 +99,8 @@ class OccupationForm extends ViewModelWidget { Widget _buildOccupationValidator(OnboardingViewModel viewModel) => Text( viewModel.occupationValidationMessage!, style: const TextStyle( - color: Colors.red, fontSize: 12, + color: Colors.red, fontWeight: FontWeight.w700, ), ); @@ -117,8 +115,13 @@ class OccupationForm extends ViewModelWidget { height: 55, text: 'Continue', borderRadius: 12, - onTap: () => viewModel.next(), foregroundColor: kcWhiteColor, - backgroundColor: kcPrimaryColor, + onTap: viewModel.focusOccupation && occupationController.text.isNotEmpty + ? () => viewModel.next() + : null, + backgroundColor: + viewModel.focusOccupation && occupationController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2), ); } diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/topic_form.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/topic_form.dart new file mode 100644 index 0000000..f80cf4e --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/forms/topic_form.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.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_view.form.dart'; +import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class TopicForm extends ViewModelWidget { + final TextEditingController topicController; + + const TopicForm({super.key, required this.topicController}); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _buildExpandedBody(viewModel)]; + + Widget _buildExpandedBody(OnboardingViewModel viewModel) => + Expanded(child: _buildBodyWrapper(viewModel)); + + Widget _buildBodyWrapper(OnboardingViewModel viewModel) => + SingleChildScrollView( + child: 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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + verticalSpaceMedium, + _buildTopics(viewModel), + if (viewModel.showTopicTextBox) _buildTopicFormField(viewModel), + if (viewModel.showTopicTextBox && + viewModel.hasTopicValidationMessage && + viewModel.focusTopic) + verticalSpaceTiny, + if (viewModel.showTopicTextBox && + viewModel.hasTopicValidationMessage && + viewModel.focusTopic) + _buildTopicWrapper(viewModel), + verticalSpaceMedium, + ]; + + Widget _buildAppBar() => const OnboardingAppBar(); + + Widget _buildTitle() => const Text( + 'Which topics interest you most?', + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'Your favorite topics help us create fun, relatable lessons.', + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildTopics(OnboardingViewModel viewModel) => ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: viewModel.topics.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildTopic( + title: viewModel.topics[index], + selected: viewModel.isSelectedTopic(viewModel.topics[index]), + onTap: () => viewModel.setSelectedTopic(viewModel.topics[index]), + ), + ); + + Widget _buildTopic( + {required String title, + required bool selected, + required GestureTapCallback onTap}) => + CustomSmallRadioButton( + title: title, + onTap: onTap, + selected: selected, + ); + + Widget _buildTopicFormField(OnboardingViewModel viewModel) => TextFormField( + maxLines: 3, + controller: topicController, + onTap: viewModel.setTopicsFocus, + decoration: inputDecoration(focus: true, hint: 'Write you interest…'), + ); + + Widget _buildTopicWrapper(OnboardingViewModel viewModel) => + viewModel.hasTopicValidationMessage + ? _buildTopicValidator(viewModel) + : Container(); + + Widget _buildTopicValidator(OnboardingViewModel viewModel) => Text( + viewModel.topicValidationMessage!, + style: const TextStyle( + fontSize: 12, + color: Colors.red, + fontWeight: FontWeight.w700, + ), + ); + + 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: kcWhiteColor, + onTap: viewModel.selectedTopic != null + ? viewModel.selectedTopic?.toLowerCase() == 'other' + ? viewModel.focusTopic + ? () => viewModel.next() + : null + : () => viewModel.next() + : null, + backgroundColor: viewModel.selectedTopic != null + ? viewModel.selectedTopic?.toLowerCase() == 'other' + ? viewModel.focusTopic && topicController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2) + : kcPrimaryColor + : kcPrimaryColor.withOpacity(0.2)); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/language_selector.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/language_selector.dart new file mode 100644 index 0000000..b4450e1 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/language_selector.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/ui_helpers.dart'; +import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/custom_small_radio_button.dart'; +import '../../../widgets/onboarding_app_bar.dart'; + +class LanguageSelector extends ViewModelWidget { + const LanguageSelector({Key? key}) : super(key: key); + + @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 _buildScaffoldChildren(OnboardingViewModel viewModel) => + [_buildAppBar(), _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 _buildBodyChildren(OnboardingViewModel viewModel) => + [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; + + Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + + List _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceSmall, + _buildSubTitle(), + verticalSpaceMedium, + _buildLanguages(viewModel) + ]; + + Widget _buildAppBar() => const OnboardingAppBar(language: true); + + Widget _buildTitle() => const Text( + 'Choose your language', + style: TextStyle( + fontSize: 25, + color: kcDarkGreyColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildSubTitle() => const Text( + 'You can switch languages anytime in Settings', + style: TextStyle(color: kcMediumGrey), + ); + + Widget _buildLanguages(OnboardingViewModel viewModel) => ListView.builder( + shrinkWrap: true, + itemCount: viewModel.languages.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildLanguage( + title: viewModel.languages[index]['language'], + selected: viewModel + .isSelectedLanguage(viewModel.languages[index]['language']), + onTap: () => + viewModel.setSelectedLanguage(viewModel.languages[index]), + ), + ); + + Widget _buildLanguage( + {required String title, + required bool selected, + required GestureTapCallback onTap}) => + CustomSmallRadioButton( + title: title, + onTap: onTap, + selected: selected, + ); + + 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: kcWhiteColor, + backgroundColor: kcPrimaryColor, + onTap: () => viewModel.pop(language: true), + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/first_welcome.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/first_welcome.dart new file mode 100644 index 0000000..f99dbf0 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/first_welcome.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class FirstWelcome extends ViewModelWidget { + const FirstWelcome({super.key}); + + @override + Widget build(BuildContext context, OnboardingViewModel viewModel) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(OnboardingViewModel viewModel) => Stack( + children: _buildScaffoldChildren(viewModel), + ); + + List _buildScaffoldChildren(OnboardingViewModel viewModel) => [ + _buildBackground(), + _buildColumnWrapper(), + _buildContinueButtonWrapper(viewModel) + ]; + + Widget _buildBackground() => Image.asset( + 'assets/images/onboarding_1.png', + fit: BoxFit.fill, + width: double.maxFinite, + height: double.maxFinite, + ); + + Widget _buildColumnWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(), + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => [ + verticalSpaceMassive, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + ]; + + Widget _buildIcon() => SvgPicture.asset('assets/icons/logo.svg'); + + Widget _buildTitle() => const Text( + 'Small daily practice. Big lifelong results.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 30, + color: kcWhiteColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align( + alignment: Alignment.bottomCenter, + child: _buildButtonContainer(viewModel), + ); + + Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + icon: true, + borderRadius: 12, + text: 'Start Learning', + onTap: () => viewModel.next(), + backgroundColor: kcWhiteColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/second_welcome.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/second_welcome.dart new file mode 100644 index 0000000..2b49b96 --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/second_welcome.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class SecondWelcome extends ViewModelWidget { + const SecondWelcome({super.key}); + + @override + Widget build(BuildContext context, OnboardingViewModel viewModel) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(OnboardingViewModel viewModel) => Stack( + children: _buildScaffoldChildren(viewModel), + ); + + List _buildScaffoldChildren(OnboardingViewModel viewModel) => [ + _buildBackground(), + _buildColumnWrapper(), + _buildContinueButtonWrapper(viewModel) + ]; + + Widget _buildBackground() => Image.asset( + 'assets/images/onboarding_2.png', + fit: BoxFit.fill, + width: double.maxFinite, + height: double.maxFinite, + ); + + Widget _buildColumnWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(), + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => [ + verticalSpaceMassive, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + ]; + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo.svg', + ); + + Widget _buildTitle() => const Text( + 'Start speaking, Confidence will follow.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 30, + color: kcWhiteColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align( + alignment: Alignment.bottomCenter, + child: _buildButtonContainer(viewModel), + ); + + Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + icon: true, + borderRadius: 12, + text: 'Start Learning', + onTap: () => viewModel.next(), + backgroundColor: kcWhiteColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/third_welcome.dart b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/third_welcome.dart new file mode 100644 index 0000000..7702dda --- /dev/null +++ b/StudioProjects/yimaru_app/lib/ui/views/onboarding/screens/welcome/third_welcome.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/custom_small_radio_button.dart'; +import 'package:yimaru_app/ui/widgets/onboarding_app_bar.dart'; + +class ThirdWelcome extends ViewModelWidget { + const ThirdWelcome({super.key}); + + @override + Widget build(BuildContext context, OnboardingViewModel viewModel) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(OnboardingViewModel viewModel) => Stack( + children: _buildScaffoldChildren(viewModel), + ); + + List _buildScaffoldChildren(OnboardingViewModel viewModel) => [ + _buildBackground(), + _buildColumnWrapper(), + _buildContinueButtonWrapper(viewModel) + ]; + + Widget _buildBackground() => Image.asset( + 'assets/images/onboarding_3.png', + fit: BoxFit.fill, + width: double.maxFinite, + height: double.maxFinite, + ); + + Widget _buildColumnWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(), + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => [ + verticalSpaceMassive, + _buildIcon(), + verticalSpaceMedium, + _buildTitle(), + ]; + + Widget _buildIcon() => SvgPicture.asset('assets/icons/logo.svg'); + + Widget _buildTitle() => const Text( + 'Every conversation brings you closer to the life you want.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 30, + color: kcWhiteColor, + fontWeight: FontWeight.w600, + ), + ); + + Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align( + alignment: Alignment.bottomCenter, + child: _buildButtonContainer(viewModel), + ); + + Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(OnboardingViewModel viewModel) => + CustomElevatedButton( + height: 55, + icon: true, + borderRadius: 12, + text: 'Start Learning', + onTap: () => viewModel.next(), + backgroundColor: kcWhiteColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/StudioProjects/yimaru_app/lib/ui/views/startup/startup_view.dart b/StudioProjects/yimaru_app/lib/ui/views/startup/startup_view.dart index ea99e04..42e039f 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/startup/startup_view.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/startup/startup_view.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import '../../common/app_colors.dart'; import 'startup_viewmodel.dart'; class StartupView extends StackedView { @@ -13,36 +15,79 @@ class StartupView extends StackedView { BuildContext context, StartupViewModel viewModel, Widget? child, - ) { - return const Scaffold( - body: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'STACKED', - style: TextStyle(fontSize: 40, fontWeight: FontWeight.w900), - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text('Loading ...', style: TextStyle(fontSize: 16)), - horizontalSpaceSmall, - SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - color: Colors.black, - strokeWidth: 6, - ), - ), - ], - ), - ], - ), - ), - ); - } + ) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper() => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(), + ); + + Widget _buildScaffold() => Stack( + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren() => [ + _buildBackground(), + _buildColumn(), + ]; + + Widget _buildBackground() => Image.asset( + 'assets/images/onboarding_1.png', + fit: BoxFit.fill, + width: double.maxFinite, + height: double.maxFinite, + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [_buildIconWrapper(), _buildLoadingTextContainer()]; + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + ]; + + Widget _buildLoadingText() => const Text('Loading ...', + style: TextStyle(color: kcWhiteColor, fontSize: 16)); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => const CircularProgressIndicator( + strokeWidth: 6, + color: kcWhiteColor, + ); + + Widget _buildIconWrapper() => Padding( + padding: const EdgeInsets.only(top: 100), + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo.svg', + ); @override StartupViewModel viewModelBuilder(BuildContext context) => StartupViewModel(); diff --git a/StudioProjects/yimaru_app/lib/ui/views/startup/startup_viewmodel.dart b/StudioProjects/yimaru_app/lib/ui/views/startup/startup_viewmodel.dart index eb738e0..1f4bc32 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/startup/startup_viewmodel.dart +++ b/StudioProjects/yimaru_app/lib/ui/views/startup/startup_viewmodel.dart @@ -1,8 +1,9 @@ import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/app/app.locator.dart'; -import 'package:yimaru_app/app/app.router.dart'; import 'package:stacked_services/stacked_services.dart'; +import '../../../app/app.locator.dart'; +import '../../../app/app.router.dart'; + class StartupViewModel extends BaseViewModel { final _navigationService = locator(); diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/custom_dropdown.dart b/StudioProjects/yimaru_app/lib/ui/widgets/custom_dropdown.dart similarity index 100% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/custom_dropdown.dart rename to StudioProjects/yimaru_app/lib/ui/widgets/custom_dropdown.dart diff --git a/StudioProjects/yimaru_app/lib/ui/shared/widgets/custom_elevated_button.dart b/StudioProjects/yimaru_app/lib/ui/widgets/custom_elevated_button.dart similarity index 66% rename from StudioProjects/yimaru_app/lib/ui/shared/widgets/custom_elevated_button.dart rename to StudioProjects/yimaru_app/lib/ui/widgets/custom_elevated_button.dart index c5b94e0..113833a 100644 --- a/StudioProjects/yimaru_app/lib/ui/shared/widgets/custom_elevated_button.dart +++ b/StudioProjects/yimaru_app/lib/ui/widgets/custom_elevated_button.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; class CustomElevatedButton extends StatelessWidget { + final bool icon; final String text; final double width; final double height; + final Color? borderColor; final double borderRadius; final Color backgroundColor; final Color foregroundColor; @@ -12,6 +14,8 @@ class CustomElevatedButton extends StatelessWidget { const CustomElevatedButton({ super.key, this.onTap, + this.borderColor, + this.icon = false, required this.text, required this.height, this.borderRadius = 0, @@ -29,12 +33,27 @@ class CustomElevatedButton extends StatelessWidget { Widget _buildButton() => OutlinedButton( onPressed: onTap, style: OutlinedButton.styleFrom( - side: BorderSide.none, backgroundColor: backgroundColor, + side: borderColor == null + ? BorderSide.none + : BorderSide(color: borderColor!), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadius)), ), - child: _buildText(), + child: _buildRow(), + ); + + Widget _buildRow() => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildRowChildren(), + ); + + List _buildRowChildren() => + [_buildText(), const SizedBox(width: 5), if (icon) _buildIcon()]; + + Widget _buildIcon() => Icon( + Icons.arrow_forward, + color: foregroundColor, ); Widget _buildText() => Text( diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/custom_large_radio_button.dart b/StudioProjects/yimaru_app/lib/ui/widgets/custom_large_radio_button.dart similarity index 96% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/custom_large_radio_button.dart rename to StudioProjects/yimaru_app/lib/ui/widgets/custom_large_radio_button.dart index b7ca6cb..d62b74f 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/custom_large_radio_button.dart +++ b/StudioProjects/yimaru_app/lib/ui/widgets/custom_large_radio_button.dart @@ -61,8 +61,8 @@ class CustomLargeRadioButton extends StatelessWidget { Widget _buildLeadingIcon() => Icon( icon, - color: kcPrimaryColor, size: 25, + color: kcPrimaryColor, ); Widget _buildTitle() => Text( @@ -76,9 +76,7 @@ class CustomLargeRadioButton extends StatelessWidget { Widget _buildSubTitle() => Text( subtitle, - style: const TextStyle( - color: kcMediumGrey, - ), + style: const TextStyle(color: kcMediumGrey), ); Widget _buildSelectedCheckBox() => Checkbox( diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/custom_small_radio_button.dart b/StudioProjects/yimaru_app/lib/ui/widgets/custom_small_radio_button.dart similarity index 100% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/custom_small_radio_button.dart rename to StudioProjects/yimaru_app/lib/ui/widgets/custom_small_radio_button.dart diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/language_button.dart b/StudioProjects/yimaru_app/lib/ui/widgets/language_button.dart similarity index 69% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/language_button.dart rename to StudioProjects/yimaru_app/lib/ui/widgets/language_button.dart index b8883d4..ba30dcc 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/language_button.dart +++ b/StudioProjects/yimaru_app/lib/ui/widgets/language_button.dart @@ -3,10 +3,16 @@ import 'package:yimaru_app/ui/common/app_colors.dart'; class LanguageButton extends StatelessWidget { final String language; - const LanguageButton({super.key, required this.language}); + final GestureTapCallback? onTap; + + const LanguageButton({super.key, this.onTap, required this.language}); @override - Widget build(BuildContext context) => _buildContainer(); + Widget build(BuildContext context) => _buildContainerWrapper(); + + Widget _buildContainerWrapper() => + GestureDetector(onTap: onTap, child: _buildContainer()); + Widget _buildContainer() => Container( width: 40, height: 40, diff --git a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/onboarding_app_bar.dart b/StudioProjects/yimaru_app/lib/ui/widgets/onboarding_app_bar.dart similarity index 77% rename from StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/onboarding_app_bar.dart rename to StudioProjects/yimaru_app/lib/ui/widgets/onboarding_app_bar.dart index 85c0d7e..5344294 100644 --- a/StudioProjects/yimaru_app/lib/ui/views/onboarding/widgets/onboarding_app_bar.dart +++ b/StudioProjects/yimaru_app/lib/ui/widgets/onboarding_app_bar.dart @@ -2,16 +2,18 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; -import 'package:yimaru_app/ui/views/onboarding/widgets/language_button.dart'; +import 'package:yimaru_app/ui/widgets/language_button.dart'; class OnboardingAppBar extends ViewModelWidget { + final bool language; final bool showBackButton; final bool showLanguageSelection; const OnboardingAppBar( {super.key, - this.showBackButton = true, - this.showLanguageSelection = true}); + this.language = false, + this.showBackButton = true, + this.showLanguageSelection = true}); @override Widget build(BuildContext context, OnboardingViewModel viewModel) => @@ -38,7 +40,7 @@ class OnboardingAppBar extends ViewModelWidget { List _buildAppBarItemChildren(OnboardingViewModel viewModel) => [ if (showBackButton) _buildBackButtonWrapper(viewModel), - _buildRightButton() + _buildRightButton(viewModel) ]; Widget _buildBackButtonWrapper(OnboardingViewModel viewModel) => Align( @@ -47,18 +49,22 @@ class OnboardingAppBar extends ViewModelWidget { ); Widget _buildBackButton(OnboardingViewModel viewModel) => BackButton( - onPressed: viewModel.pop, + onPressed: ()=> viewModel.pop(language: language), style: const ButtonStyle( foregroundColor: WidgetStatePropertyAll(kcWhiteColor)), ); - Widget _buildRightButton() => Align( + Widget _buildRightButton(OnboardingViewModel viewModel) => Align( alignment: Alignment.bottomRight, child: showLanguageSelection - ? _buildLanguageSelector() + ? _buildLanguageSelector(viewModel) : _buildCloseButton()); - Widget _buildLanguageSelector() => const LanguageButton(language: 'EN'); + Widget _buildLanguageSelector(OnboardingViewModel viewModel) => + LanguageButton( + language: 'EN', + onTap: () => viewModel.next(page: 23), + ); Widget _buildCloseButton() => IconButton( onPressed: () {}, diff --git a/StudioProjects/yimaru_app/pubspec.lock b/StudioProjects/yimaru_app/pubspec.lock index 0ea6f34..da6e4ea 100644 --- a/StudioProjects/yimaru_app/pubspec.lock +++ b/StudioProjects/yimaru_app/pubspec.lock @@ -214,6 +214,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" flutter_test: dependency: "direct dev" description: flutter @@ -275,6 +283,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -435,6 +451,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" pool: dependency: transitive description: @@ -616,6 +648,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" vector_math: dependency: transitive description: @@ -672,6 +728,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" yaml: dependency: transitive description: @@ -682,4 +746,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.8.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.32.0" diff --git a/StudioProjects/yimaru_app/pubspec.yaml b/StudioProjects/yimaru_app/pubspec.yaml index e3538ef..16c45fd 100644 --- a/StudioProjects/yimaru_app/pubspec.yaml +++ b/StudioProjects/yimaru_app/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: sdk: flutter stacked: ^3.4.0 iconsax: ^0.0.8 + flutter_svg: ^2.2.3 dropdown_search: ^6.0.2 stacked_services: ^1.1.0 @@ -28,3 +29,13 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/icons/ + - assets/images/ + fonts: + - family: Aeonik + fonts: + - asset: assets/fonts/Aeonik-Regular.ttf + + + diff --git a/StudioProjects/yimaru_app/test/viewmodels/language_viewmodel_test.dart b/StudioProjects/yimaru_app/test/viewmodels/language_viewmodel_test.dart new file mode 100644 index 0000000..0b8735a --- /dev/null +++ b/StudioProjects/yimaru_app/test/viewmodels/language_viewmodel_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:yimaru_app/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('LanguageViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/StudioProjects/yimaru_app/test/viewmodels/settings_viewmodel_test.dart b/StudioProjects/yimaru_app/test/viewmodels/settings_viewmodel_test.dart new file mode 100644 index 0000000..aa5a11d --- /dev/null +++ b/StudioProjects/yimaru_app/test/viewmodels/settings_viewmodel_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:yimaru_app/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('SettingsViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/StudioProjects/yimaru_app/test/viewmodels/startup_viewmodel_test.dart b/StudioProjects/yimaru_app/test/viewmodels/startup_viewmodel_test.dart new file mode 100644 index 0000000..e560a6c --- /dev/null +++ b/StudioProjects/yimaru_app/test/viewmodels/startup_viewmodel_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:yimaru_app/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('StartupViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +}