From 0b86a5c7bba4d539782c283026e987438f9a466d Mon Sep 17 00:00:00 2001 From: Conner Date: Sat, 23 Nov 2024 19:57:25 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[#17]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20UI=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/translations/ko.json | 13 +- lib/core/router/router.dart | 4 + lib/core/router/router.gr.dart | 79 +++++--- lib/core/types/gender_type.dart | 23 ++- lib/core/utils/extensions/extensions.dart | 1 + lib/core/utils/extensions/key_ext.dart | 7 + lib/core/utils/resource/string_res.dart | 11 ++ .../bloc/sign_up/sign_up_bloc.dart | 11 +- .../bloc/sign_up/sign_up_bloc.handler.dart | 61 +++++- .../bloc/sign_up/sign_up_event.dart | 49 +++++ .../bloc/sign_up/sign_up_state.dart | 12 ++ .../presentation/page/login/login_page.dart | 4 +- .../presentation/page/sign_up/sign_up.dart | 2 + .../page/sign_up/sign_up_page.dart | 183 ++++++++++++++++++ .../page/sign_up/sign_up_page_key.dart | 19 ++ .../widget/gender_select/gender_select.dart | 84 ++++++++ lib/feature/account/type/password.dart | 3 + lib/shared/widgets/base_input/base_input.dart | 1 + .../{ => button}/base_button/base_button.dart | 0 lib/shared/widgets/button/button.dart | 2 + .../button/enabled_button/enabled_button.dart | 37 ++++ lib/shared/widgets/widgets.dart | 2 +- 22 files changed, 568 insertions(+), 40 deletions(-) create mode 100644 lib/core/utils/extensions/key_ext.dart create mode 100644 lib/feature/account/presentation/page/sign_up/sign_up.dart create mode 100644 lib/feature/account/presentation/page/sign_up/sign_up_page.dart create mode 100644 lib/feature/account/presentation/page/sign_up/sign_up_page_key.dart create mode 100644 lib/feature/account/presentation/widget/gender_select/gender_select.dart rename lib/shared/widgets/{ => button}/base_button/base_button.dart (100%) create mode 100644 lib/shared/widgets/button/button.dart create mode 100644 lib/shared/widgets/button/enabled_button/enabled_button.dart diff --git a/assets/translations/ko.json b/assets/translations/ko.json index 8a35268..b7d8595 100644 --- a/assets/translations/ko.json +++ b/assets/translations/ko.json @@ -96,5 +96,16 @@ "enterVerificationCode": "인증번호 입력", "verification": "인증", "invalidVerificationCode": "인증번호가 틀립니다,", - "emailDuplicateError": "! 중복된 이메일입니다." + "emailDuplicateError": "! 중복된 이메일입니다.", + "name": "이름", + "enterTwoOrMoreChars": "2자 이상 이름을 입력하세요.", + "birthDate": "생년월일", + "enterEightChars": "8리를 입력하세요.", + "gender": "성별", + "man": "남자,", + "woman": "여자", + "phone": "휴대폰", + "email": "이메일", + "password": "비밀번호", + "verifyPassword": "비밀번호 확인" } diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart index dec89ee..ef1b494 100644 --- a/lib/core/router/router.dart +++ b/lib/core/router/router.dart @@ -33,5 +33,9 @@ class AppRouter extends RootStackRouter { page: JobPostingWorkersRoute.page, path: '/job-posting-workers', ), + AutoRoute( + page: SignUpRoute.page, + path: '/sign-up', + ), ]; } diff --git a/lib/core/router/router.gr.dart b/lib/core/router/router.gr.dart index 478e6c2..ace2ade 100644 --- a/lib/core/router/router.gr.dart +++ b/lib/core/router/router.gr.dart @@ -8,10 +8,12 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i7; -import 'package:flutter/material.dart' as _i8; +import 'package:auto_route/auto_route.dart' as _i8; +import 'package:flutter/material.dart' as _i9; import 'package:withu_app/feature/account/presentation/page/login/login_page.dart' as _i5; +import 'package:withu_app/feature/account/presentation/page/sign_up/sign_up_page.dart' + as _i6; import 'package:withu_app/feature/job_posting/presentation/pages/job_posting_detail_page.dart' as _i1; import 'package:withu_app/feature/job_posting/presentation/pages/job_posting_form_page.dart' @@ -21,16 +23,16 @@ import 'package:withu_app/feature/job_posting/presentation/pages/job_posting_wor import 'package:withu_app/feature/job_posting/presentation/pages/job_postings_page.dart' as _i4; import 'package:withu_app/feature/splash/presentation/pages/splash_page.dart' - as _i6; + as _i7; /// generated route for /// [_i1.JobPostingDetailPage] class JobPostingDetailRoute - extends _i7.PageRouteInfo { + extends _i8.PageRouteInfo { JobPostingDetailRoute({ - _i8.Key? key, + _i9.Key? key, required String jobPostingId, - List<_i7.PageRouteInfo>? children, + List<_i8.PageRouteInfo>? children, }) : super( JobPostingDetailRoute.name, args: JobPostingDetailRouteArgs( @@ -42,7 +44,7 @@ class JobPostingDetailRoute static const String name = 'JobPostingDetailRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -60,7 +62,7 @@ class JobPostingDetailRouteArgs { required this.jobPostingId, }); - final _i8.Key? key; + final _i9.Key? key; final String jobPostingId; @@ -72,11 +74,11 @@ class JobPostingDetailRouteArgs { /// generated route for /// [_i2.JobPostingFormPage] -class JobPostingFormRoute extends _i7.PageRouteInfo { +class JobPostingFormRoute extends _i8.PageRouteInfo { JobPostingFormRoute({ - _i8.Key? key, + _i9.Key? key, String? jobPostingId, - List<_i7.PageRouteInfo>? children, + List<_i8.PageRouteInfo>? children, }) : super( JobPostingFormRoute.name, args: JobPostingFormRouteArgs( @@ -88,7 +90,7 @@ class JobPostingFormRoute extends _i7.PageRouteInfo { static const String name = 'JobPostingFormRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -107,7 +109,7 @@ class JobPostingFormRouteArgs { this.jobPostingId, }); - final _i8.Key? key; + final _i9.Key? key; final String? jobPostingId; @@ -120,11 +122,11 @@ class JobPostingFormRouteArgs { /// generated route for /// [_i3.JobPostingWorkersPage] class JobPostingWorkersRoute - extends _i7.PageRouteInfo { + extends _i8.PageRouteInfo { JobPostingWorkersRoute({ - _i8.Key? key, + _i9.Key? key, required String jobPostingId, - List<_i7.PageRouteInfo>? children, + List<_i8.PageRouteInfo>? children, }) : super( JobPostingWorkersRoute.name, args: JobPostingWorkersRouteArgs( @@ -136,7 +138,7 @@ class JobPostingWorkersRoute static const String name = 'JobPostingWorkersRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -154,7 +156,7 @@ class JobPostingWorkersRouteArgs { required this.jobPostingId, }); - final _i8.Key? key; + final _i9.Key? key; final String jobPostingId; @@ -166,8 +168,8 @@ class JobPostingWorkersRouteArgs { /// generated route for /// [_i4.JobPostingsPage] -class JobPostingsRoute extends _i7.PageRouteInfo { - const JobPostingsRoute({List<_i7.PageRouteInfo>? children}) +class JobPostingsRoute extends _i8.PageRouteInfo { + const JobPostingsRoute({List<_i8.PageRouteInfo>? children}) : super( JobPostingsRoute.name, initialChildren: children, @@ -175,7 +177,7 @@ class JobPostingsRoute extends _i7.PageRouteInfo { static const String name = 'JobPostingsRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { return const _i4.JobPostingsPage(); @@ -185,8 +187,8 @@ class JobPostingsRoute extends _i7.PageRouteInfo { /// generated route for /// [_i5.LoginPage] -class LoginRoute extends _i7.PageRouteInfo { - const LoginRoute({List<_i7.PageRouteInfo>? children}) +class LoginRoute extends _i8.PageRouteInfo { + const LoginRoute({List<_i8.PageRouteInfo>? children}) : super( LoginRoute.name, initialChildren: children, @@ -194,7 +196,7 @@ class LoginRoute extends _i7.PageRouteInfo { static const String name = 'LoginRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { return const _i5.LoginPage(); @@ -203,9 +205,28 @@ class LoginRoute extends _i7.PageRouteInfo { } /// generated route for -/// [_i6.SplashPage] -class SplashRoute extends _i7.PageRouteInfo { - const SplashRoute({List<_i7.PageRouteInfo>? children}) +/// [_i6.SignUpPage] +class SignUpRoute extends _i8.PageRouteInfo { + const SignUpRoute({List<_i8.PageRouteInfo>? children}) + : super( + SignUpRoute.name, + initialChildren: children, + ); + + static const String name = 'SignUpRoute'; + + static _i8.PageInfo page = _i8.PageInfo( + name, + builder: (data) { + return const _i6.SignUpPage(); + }, + ); +} + +/// generated route for +/// [_i7.SplashPage] +class SplashRoute extends _i8.PageRouteInfo { + const SplashRoute({List<_i8.PageRouteInfo>? children}) : super( SplashRoute.name, initialChildren: children, @@ -213,10 +234,10 @@ class SplashRoute extends _i7.PageRouteInfo { static const String name = 'SplashRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { - return const _i6.SplashPage(); + return const _i7.SplashPage(); }, ); } diff --git a/lib/core/types/gender_type.dart b/lib/core/types/gender_type.dart index a6494a2..d340208 100644 --- a/lib/core/types/gender_type.dart +++ b/lib/core/types/gender_type.dart @@ -1,15 +1,26 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:withu_app/core/utils/mixins/l10n_key_provider.dart'; /// 계정 타입 @JsonEnum(valueField: 'serverKey') -enum GenderType { - none(serverKey: ''), +enum GenderType with L10nKeyProvider { + none(l10nKey: '', serverKey: ''), + woman(l10nKey: 'woman', serverKey: 'WOMAN'), + man(l10nKey: 'man', serverKey: 'MAN'); - man(serverKey: 'MAN'), + final String serverKey; - woman(serverKey: 'WOMAN'); + @override + final String l10nKey; - final String serverKey; + const GenderType({ + required this.l10nKey, + required this.serverKey, + }); - const GenderType({required this.serverKey}); + static List get valuesWithoutNone => values + .where( + (type) => type != none, + ) + .toList(); } diff --git a/lib/core/utils/extensions/extensions.dart b/lib/core/utils/extensions/extensions.dart index ad81f6c..cb5a290 100644 --- a/lib/core/utils/extensions/extensions.dart +++ b/lib/core/utils/extensions/extensions.dart @@ -1,3 +1,4 @@ export 'date_ext.dart'; export 'theme_ext.dart'; export 'string_ext.dart'; +export 'key_ext.dart'; diff --git a/lib/core/utils/extensions/key_ext.dart b/lib/core/utils/extensions/key_ext.dart new file mode 100644 index 0000000..c209bf9 --- /dev/null +++ b/lib/core/utils/extensions/key_ext.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +extension KeyExt on Key { + Key makeChildKey(String childKey) { + return Key('${(this as ValueKey).value}_$childKey'); + } +} diff --git a/lib/core/utils/resource/string_res.dart b/lib/core/utils/resource/string_res.dart index 34f8550..672408d 100644 --- a/lib/core/utils/resource/string_res.dart +++ b/lib/core/utils/resource/string_res.dart @@ -78,6 +78,17 @@ enum StringRes { verification, invalidVerificationCode, emailDuplicateError, + name, + enterTwoOrMoreChars, + birthDate, + enterEightChars, + gender, + man, + woman, + phone, + email, + password, + verifyPassword, } extension StringResEx on StringRes { diff --git a/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.dart b/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.dart index a93af40..60dcd4e 100644 --- a/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.dart +++ b/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.dart @@ -1,3 +1,4 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:withu_app/core/core.dart'; import 'package:withu_app/feature/account/account.dart'; @@ -17,5 +18,13 @@ class SignUpBloc extends BaseBloc { required this.signUpUseCase, }) : super( SignUpState(status: BaseBlocStatus.initial()), - ); + ) { + on(_onNameInputted); + on(_onBirthDateInputted); + on(_onGenderSelected); + on(_onPasswordObscureToggled); + on(_onPasswordInputted); + on(_onPasswordVerifyInputted); + on(_onSubmitPressed); + } } diff --git a/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.handler.dart b/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.handler.dart index aebd1f2..df29596 100644 --- a/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.handler.dart +++ b/lib/feature/account/presentation/bloc/sign_up/sign_up_bloc.handler.dart @@ -1,3 +1,62 @@ part of 'sign_up_bloc.dart'; -extension SignUpBlocHandler on SignUpBloc {} +extension SignUpBlocHandler on SignUpBloc { + void _onNameInputted( + SignUpNameInputted event, + Emitter emit, + ) { + emit(state.copyWith(name: event.name)); + } + + void _onBirthDateInputted( + SignUpBirthDateInputted event, + Emitter emit, + ) { + emit(state.copyWith(birthDate: event.birthDate)); + } + + void _onGenderSelected( + SignUpGenderSelected event, + Emitter emit, + ) { + emit(state.copyWith(gender: event.gender)); + } + + void _onPasswordObscureToggled( + SignUpPasswordObscureToggled event, + Emitter emit, + ) { + emit(state.copyWith(isPasswordObscure: !state.isPasswordObscure)); + } + + void _onPasswordInputted( + SignUpPasswordInputted event, + Emitter emit, + ) { + emit(state.copyWith( + password: event.password, + )); + + emit(state.copyWith( + isPasswordErrorVisible: state.getPasswordErrorVisible(), + )); + } + + void _onPasswordVerifyInputted( + SignUpPasswordVerifyInputted event, + Emitter emit, + ) { + emit(state.copyWith( + passwordVerify: event.password, + )); + + emit(state.copyWith( + isPasswordErrorVisible: state.getPasswordErrorVisible(), + )); + } + + void _onSubmitPressed( + SignUpSubmitPressed event, + Emitter emit, + ) {} +} diff --git a/lib/feature/account/presentation/bloc/sign_up/sign_up_event.dart b/lib/feature/account/presentation/bloc/sign_up/sign_up_event.dart index bde7e00..e7c85b4 100644 --- a/lib/feature/account/presentation/bloc/sign_up/sign_up_event.dart +++ b/lib/feature/account/presentation/bloc/sign_up/sign_up_event.dart @@ -1,3 +1,52 @@ part of 'sign_up_bloc.dart'; abstract class SignUpEvent extends BaseBlocEvent {} + +/// 이름 입력 이벤트 +class SignUpNameInputted extends SignUpEvent { + final String value; + + SignUpNameInputted({required this.value}); + + Name get name => Name(value); +} + +/// 생년월일 입력 이벤트 +class SignUpBirthDateInputted extends SignUpEvent { + final String value; + + SignUpBirthDateInputted({required this.value}); + + BirthDate get birthDate => BirthDate(value); +} + +/// 비밀번호 표시/숨기기 이벤트 +class SignUpPasswordObscureToggled extends SignUpEvent {} + +/// 성별 선택 이벤트 +class SignUpGenderSelected extends SignUpEvent { + final GenderType gender; + + SignUpGenderSelected({required this.gender}); +} + +/// 비밀번호 입력 이벤트 +class SignUpPasswordInputted extends SignUpEvent { + final String value; + + SignUpPasswordInputted({required this.value}); + + Password get password => Password(value); +} + +/// 비밀번호 확인 입력 이벤트 +class SignUpPasswordVerifyInputted extends SignUpEvent { + final String value; + + SignUpPasswordVerifyInputted({required this.value}); + + Password get password => Password(value); +} + +/// 회원가입 버튼 클릭 이벤트. +class SignUpSubmitPressed extends SignUpEvent {} diff --git a/lib/feature/account/presentation/bloc/sign_up/sign_up_state.dart b/lib/feature/account/presentation/bloc/sign_up/sign_up_state.dart index 56668d6..221360a 100644 --- a/lib/feature/account/presentation/bloc/sign_up/sign_up_state.dart +++ b/lib/feature/account/presentation/bloc/sign_up/sign_up_state.dart @@ -12,5 +12,17 @@ class SignUpState extends BaseBlocState with _$SignUpState { @Default(Email.empty) LoginId loginId, @Default(Password.empty) Password password, @Default(Password.empty) Password passwordVerify, + @Default(true) bool isPasswordObscure, + @Default(VisibleType.none) VisibleType isPasswordErrorVisible, }) = _SignUpState; } + +extension SignUpStateExt on SignUpState { + VisibleType getPasswordErrorVisible() { + return VisibleTypeExt.fromBool(!checkPasswordValid()); + } + + bool checkPasswordValid() { + return password.isValid && password.isEqual(passwordVerify); + } +} diff --git a/lib/feature/account/presentation/page/login/login_page.dart b/lib/feature/account/presentation/page/login/login_page.dart index 0d4fab8..611206b 100644 --- a/lib/feature/account/presentation/page/login/login_page.dart +++ b/lib/feature/account/presentation/page/login/login_page.dart @@ -110,7 +110,9 @@ class _LoginPageState extends State { Row( children: [ TextButton( - onPressed: () {}, + onPressed: () { + getItAppRouter.push(const SignUpRoute()); + }, child: Text( StringRes.signUp.tr, style: context.textTheme.bodySmall, diff --git a/lib/feature/account/presentation/page/sign_up/sign_up.dart b/lib/feature/account/presentation/page/sign_up/sign_up.dart new file mode 100644 index 0000000..c91a27f --- /dev/null +++ b/lib/feature/account/presentation/page/sign_up/sign_up.dart @@ -0,0 +1,2 @@ +export 'sign_up_page.dart'; +export 'sign_up_page_key.dart'; diff --git a/lib/feature/account/presentation/page/sign_up/sign_up_page.dart b/lib/feature/account/presentation/page/sign_up/sign_up_page.dart new file mode 100644 index 0000000..95fd813 --- /dev/null +++ b/lib/feature/account/presentation/page/sign_up/sign_up_page.dart @@ -0,0 +1,183 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:withu_app/core/core.dart'; +import 'package:withu_app/feature/account/account.dart'; +import 'package:withu_app/feature/account/presentation/widget/gender_select/gender_select.dart'; +import 'package:withu_app/shared/shared.dart'; + +import 'sign_up_page_key.dart'; + +typedef SignUpBlocBuilder = BlocBuilder; + +@RoutePage() +class SignUpPage extends StatelessWidget { + const SignUpPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => getIt(), + child: const SignUpPageContent(), + ); + } +} + +class SignUpPageContent extends StatelessWidget { + const SignUpPageContent({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return PageRoot( + isLoading: state.status.isLoading, + appBar: CustomAppBar.back(context: context), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FieldName(text: StringRes.name.tr), + const NameInput(), + const SizedBox(height: 30), + FieldName(text: StringRes.birthDate.tr), + const BirthDateInput(), + const SizedBox(height: 30), + FieldName(text: StringRes.gender.tr), + GenderSelect( + key: SignUpPageKey.gender.toKey(), + selectedType: state.gender, + ), + const SizedBox(height: 30), + FieldName(text: StringRes.phone.tr), + const PhoneAuthWidget(), + const SizedBox(height: 30), + FieldName(text: StringRes.email.tr), + EmailDuplicateCheckWidget(), + const SizedBox(height: 30), + FieldName(text: StringRes.password.tr), + const Password(), + const PasswordVerify(), + const SizedBox(height: 52), + const SubmitButton(), + const SizedBox(height: 40), + ], + ), + ), + ); + }, + ); + } +} + +class FieldName extends StatelessWidget { + final String text; + + const FieldName({super.key, required this.text}); + + @override + Widget build(BuildContext context) { + return Text( + text, + style: context.textTheme.bodyMedium, + ); + } +} + +class NameInput extends StatelessWidget { + const NameInput({super.key}); + + @override + Widget build(BuildContext context) { + return BaseInput( + key: SignUpPageKey.name.toKey(), + hintText: StringRes.enterTwoOrMoreChars.tr, + onChanged: (String text) { + context.read().add(SignUpNameInputted(value: text)); + }, + ); + } +} + +class BirthDateInput extends StatelessWidget { + const BirthDateInput({super.key}); + + @override + Widget build(BuildContext context) { + return BaseInput( + key: SignUpPageKey.birthDate.toKey(), + keyboardType: TextInputType.number, + hintText: StringRes.enterEightChars.tr, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onChanged: (String text) { + context.read().add(SignUpBirthDateInputted(value: text)); + }, + ); + } +} + +class Password extends StatelessWidget { + const Password({super.key}); + + @override + Widget build(BuildContext context) { + return SignUpBlocBuilder( + builder: (context, state) { + return BaseInput.password( + key: SignUpPageKey.password.toKey(), + obscureText: state.isPasswordObscure, + onSuffixPressed: () { + context.read().add(SignUpPasswordObscureToggled()); + }, + onChanged: (String text) { + context.read().add(SignUpPasswordInputted(value: text)); + }, + ); + }, + ); + } +} + +class PasswordVerify extends StatelessWidget { + const PasswordVerify({super.key}); + + @override + Widget build(BuildContext context) { + return SignUpBlocBuilder(builder: (context, state) { + return BaseInput( + key: SignUpPageKey.passwordVerify.toKey(), + hintText: StringRes.verifyPassword.tr, + obscureText: state.isPasswordObscure, + errorText: StringRes.pleaseEnterValidPassword.tr, + errorVisible: state.isPasswordErrorVisible.isVisible, + onChanged: (String text) { + context + .read() + .add(SignUpPasswordVerifyInputted(value: text)); + }, + ); + }); + } +} + +class SubmitButton extends StatelessWidget { + const SubmitButton({super.key}); + + @override + Widget build(BuildContext context) { + return SignUpBlocBuilder(builder: (context, state) { + return EnabledButton( + key: SignUpPageKey.submitBtn.toKey(), + text: StringRes.signUp.tr, + isEnabled: true, + onTap: () { + context.read().add(SignUpSubmitPressed()); + }, + ); + }); + } +} diff --git a/lib/feature/account/presentation/page/sign_up/sign_up_page_key.dart b/lib/feature/account/presentation/page/sign_up/sign_up_page_key.dart new file mode 100644 index 0000000..ee180f1 --- /dev/null +++ b/lib/feature/account/presentation/page/sign_up/sign_up_page_key.dart @@ -0,0 +1,19 @@ +import 'package:withu_app/core/utils/mixins/widget_key_utils.dart'; + +enum SignUpPageKey with WidgetKeyUtils { + name(key: 'name'), + birthDate(key: 'birth_date'), + gender(key: 'gender'), + password(key: 'password'), + passwordVerify(key: 'password_verify'), + submitBtn(key:'submit_btn'); + + final String key; + + const SignUpPageKey({required this.key}); + + @override + String getKey() { + return key; + } +} diff --git a/lib/feature/account/presentation/widget/gender_select/gender_select.dart b/lib/feature/account/presentation/widget/gender_select/gender_select.dart new file mode 100644 index 0000000..828b0b6 --- /dev/null +++ b/lib/feature/account/presentation/widget/gender_select/gender_select.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:withu_app/core/core.dart'; +import 'package:withu_app/feature/account/account.dart'; +import 'package:withu_app/gen/colors.gen.dart'; + +class GenderSelect extends StatelessWidget { + final GenderType selectedType; + + const GenderSelect({ + super.key, + required this.selectedType, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: GenderType.valuesWithoutNone + .map( + (gender) => Expanded( + child: GenderOption( + key: super.key?.makeChildKey('$gender'), + gender: gender, + isSelected: selectedType == gender, + ), + ), + ) + .toList(), + ); + } +} + +class GenderOption extends StatelessWidget { + final GenderType gender; + + final bool isSelected; + + const GenderOption({ + super.key, + required this.gender, + required this.isSelected, + }); + + Color get textColor => isSelected ? ColorName.primary : ColorName.secondary; + + Border get defaultBorder => const Border( + bottom: BorderSide( + color: ColorName.teritary, + width: 1, + ), + ); + + Border get selectedBorder => const Border( + bottom: BorderSide( + color: ColorName.primary, + width: 3, + ), + ); + + Border get border => isSelected ? selectedBorder : defaultBorder; + + @override + Widget build(BuildContext context) { + return InkWell( + key: super.key?.makeChildKey('tap'), + onTap: () { + context.read().add(SignUpGenderSelected(gender: gender)); + }, + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 11), + decoration: BoxDecoration( + border: border, + ), + child: Text( + gender.tr, + style: context.textTheme.bodyMediumBold?.copyWith( + color: textColor, + ), + ), + ), + ); + } +} diff --git a/lib/feature/account/type/password.dart b/lib/feature/account/type/password.dart index 705fc36..ce9d4da 100644 --- a/lib/feature/account/type/password.dart +++ b/lib/feature/account/type/password.dart @@ -14,4 +14,7 @@ class Password extends Equatable { bool get isValid => RegExUtil.passwordPattern.hasMatch(value); + bool isEqual(Password password) { + return value == password.value; + } } diff --git a/lib/shared/widgets/base_input/base_input.dart b/lib/shared/widgets/base_input/base_input.dart index 1a7230b..9610eb3 100644 --- a/lib/shared/widgets/base_input/base_input.dart +++ b/lib/shared/widgets/base_input/base_input.dart @@ -91,6 +91,7 @@ class BaseInput extends StatelessWidget { children: [ Expanded( child: TextField( + key: Key('${(super.key as ValueKey).value}_input'), controller: controller, focusNode: focusNode, style: style ?? defaultTextStyle, diff --git a/lib/shared/widgets/base_button/base_button.dart b/lib/shared/widgets/button/base_button/base_button.dart similarity index 100% rename from lib/shared/widgets/base_button/base_button.dart rename to lib/shared/widgets/button/base_button/base_button.dart diff --git a/lib/shared/widgets/button/button.dart b/lib/shared/widgets/button/button.dart new file mode 100644 index 0000000..12f8ce9 --- /dev/null +++ b/lib/shared/widgets/button/button.dart @@ -0,0 +1,2 @@ +export 'base_button/base_button.dart'; +export 'enabled_button/enabled_button.dart'; diff --git a/lib/shared/widgets/button/enabled_button/enabled_button.dart b/lib/shared/widgets/button/enabled_button/enabled_button.dart new file mode 100644 index 0000000..c9d13d2 --- /dev/null +++ b/lib/shared/widgets/button/enabled_button/enabled_button.dart @@ -0,0 +1,37 @@ +import 'package:flutter/cupertino.dart'; +import 'package:withu_app/core/utils/utils.dart'; +import 'package:withu_app/gen/colors.gen.dart'; +import 'package:withu_app/shared/shared.dart'; + +class EnabledButton extends StatelessWidget { + final String text; + + final bool isEnabled; + + final VoidCallback onTap; + + const EnabledButton({ + super.key, + required this.text, + required this.isEnabled, + required this.onTap, + }); + + Color get textColor => isEnabled ? ColorName.white : ColorName.secondary; + + Color get bgColor => isEnabled ? ColorName.primary : ColorName.teritary; + + @override + Widget build(BuildContext context) { + return BaseButton( + text: Text( + text, + style: context.textTheme.bodyMediumBold?.copyWith( + color: textColor, + ), + ), + backgroundColor: bgColor, + onTap: onTap, + ); + } +} diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index 13bc3b5..aaa8113 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -8,4 +8,4 @@ export 'base_switch/base_switch.dart'; export 'time_picker/time_picker.dart'; export 'lined_text_form/lined_text_form.dart'; export 'more_options/more_options.dart'; -export 'base_button/base_button.dart'; +export 'button/button.dart';