diff --git a/lib/features/profile/domain/entities/profile_entity.dart b/lib/features/profile/domain/entities/profile_entity.dart index db60c5f..551566c 100644 --- a/lib/features/profile/domain/entities/profile_entity.dart +++ b/lib/features/profile/domain/entities/profile_entity.dart @@ -11,8 +11,8 @@ class ProfileEntity with _$ProfileEntity { required String gender, required String ageRange, required CommunityEntity community, - required String residenceProvince, - required String residenceDistrict, + String? residenceProvince, + String? residenceDistrict, required String hometownProvince, required String hometownDistrict, }) = _ProfileEntity; diff --git a/lib/features/profile/domain/entities/profile_entity.freezed.dart b/lib/features/profile/domain/entities/profile_entity.freezed.dart index 630f916..c99702c 100644 --- a/lib/features/profile/domain/entities/profile_entity.freezed.dart +++ b/lib/features/profile/domain/entities/profile_entity.freezed.dart @@ -21,8 +21,8 @@ mixin _$ProfileEntity { String get gender => throw _privateConstructorUsedError; String get ageRange => throw _privateConstructorUsedError; CommunityEntity get community => throw _privateConstructorUsedError; - String get residenceProvince => throw _privateConstructorUsedError; - String get residenceDistrict => throw _privateConstructorUsedError; + String? get residenceProvince => throw _privateConstructorUsedError; + String? get residenceDistrict => throw _privateConstructorUsedError; String get hometownProvince => throw _privateConstructorUsedError; String get hometownDistrict => throw _privateConstructorUsedError; @@ -45,8 +45,8 @@ abstract class $ProfileEntityCopyWith<$Res> { String gender, String ageRange, CommunityEntity community, - String residenceProvince, - String residenceDistrict, + String? residenceProvince, + String? residenceDistrict, String hometownProvince, String hometownDistrict}); @@ -73,8 +73,8 @@ class _$ProfileEntityCopyWithImpl<$Res, $Val extends ProfileEntity> Object? gender = null, Object? ageRange = null, Object? community = null, - Object? residenceProvince = null, - Object? residenceDistrict = null, + Object? residenceProvince = freezed, + Object? residenceDistrict = freezed, Object? hometownProvince = null, Object? hometownDistrict = null, }) { @@ -99,14 +99,14 @@ class _$ProfileEntityCopyWithImpl<$Res, $Val extends ProfileEntity> ? _value.community : community // ignore: cast_nullable_to_non_nullable as CommunityEntity, - residenceProvince: null == residenceProvince + residenceProvince: freezed == residenceProvince ? _value.residenceProvince : residenceProvince // ignore: cast_nullable_to_non_nullable - as String, - residenceDistrict: null == residenceDistrict + as String?, + residenceDistrict: freezed == residenceDistrict ? _value.residenceDistrict : residenceDistrict // ignore: cast_nullable_to_non_nullable - as String, + as String?, hometownProvince: null == hometownProvince ? _value.hometownProvince : hometownProvince // ignore: cast_nullable_to_non_nullable @@ -143,8 +143,8 @@ abstract class _$$ProfileEntityImplCopyWith<$Res> String gender, String ageRange, CommunityEntity community, - String residenceProvince, - String residenceDistrict, + String? residenceProvince, + String? residenceDistrict, String hometownProvince, String hometownDistrict}); @@ -170,8 +170,8 @@ class __$$ProfileEntityImplCopyWithImpl<$Res> Object? gender = null, Object? ageRange = null, Object? community = null, - Object? residenceProvince = null, - Object? residenceDistrict = null, + Object? residenceProvince = freezed, + Object? residenceDistrict = freezed, Object? hometownProvince = null, Object? hometownDistrict = null, }) { @@ -196,14 +196,14 @@ class __$$ProfileEntityImplCopyWithImpl<$Res> ? _value.community : community // ignore: cast_nullable_to_non_nullable as CommunityEntity, - residenceProvince: null == residenceProvince + residenceProvince: freezed == residenceProvince ? _value.residenceProvince : residenceProvince // ignore: cast_nullable_to_non_nullable - as String, - residenceDistrict: null == residenceDistrict + as String?, + residenceDistrict: freezed == residenceDistrict ? _value.residenceDistrict : residenceDistrict // ignore: cast_nullable_to_non_nullable - as String, + as String?, hometownProvince: null == hometownProvince ? _value.hometownProvince : hometownProvince // ignore: cast_nullable_to_non_nullable @@ -225,8 +225,8 @@ class _$ProfileEntityImpl implements _ProfileEntity { required this.gender, required this.ageRange, required this.community, - required this.residenceProvince, - required this.residenceDistrict, + this.residenceProvince, + this.residenceDistrict, required this.hometownProvince, required this.hometownDistrict}); @@ -242,9 +242,9 @@ class _$ProfileEntityImpl implements _ProfileEntity { @override final CommunityEntity community; @override - final String residenceProvince; + final String? residenceProvince; @override - final String residenceDistrict; + final String? residenceDistrict; @override final String hometownProvince; @override @@ -308,8 +308,8 @@ abstract class _ProfileEntity implements ProfileEntity { required final String gender, required final String ageRange, required final CommunityEntity community, - required final String residenceProvince, - required final String residenceDistrict, + final String? residenceProvince, + final String? residenceDistrict, required final String hometownProvince, required final String hometownDistrict}) = _$ProfileEntityImpl; @@ -324,9 +324,9 @@ abstract class _ProfileEntity implements ProfileEntity { @override CommunityEntity get community; @override - String get residenceProvince; + String? get residenceProvince; @override - String get residenceDistrict; + String? get residenceDistrict; @override String get hometownProvince; @override diff --git a/lib/features/profile/presentation/view_models/profile_input_view_model.dart b/lib/features/profile/presentation/view_models/profile_input_view_model.dart index 1eb0e4e..4f76555 100644 --- a/lib/features/profile/presentation/view_models/profile_input_view_model.dart +++ b/lib/features/profile/presentation/view_models/profile_input_view_model.dart @@ -15,6 +15,7 @@ import 'package:debateseason_frontend_v1/utils/logger.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +//TODO: 거주지를 선택하지 않을 때, 거주지와 동일 체크박스 상태변경 필요함. 현재는 체크박스 상태를 관리없어서 추후 구현. class ProfileInputViewModel extends GetxController { late ProfileViewModel _profileViewModel; late ProfileRepository _profileRepository; @@ -50,11 +51,16 @@ class ProfileInputViewModel extends GetxController { final _selectedAge = ''.obs; final _isModifyScreen = false.obs; final _isApiLoading = false.obs; + final residenceText = ''.obs; + final homeTownText = ''.obs; final _selectedResidenceProvince = Rx(ProvinceType.seoul); final _selectedResidenceDistrict = Rx(null); final _selectedHomeTownProvince = Rx(ProvinceType.seoul); final _selectedHomeTownDistrict = Rx(null); + late final VoidCallback _residenceControllerListener; + late final VoidCallback _homeTownControllerListener; + ProfileEntity get profile => _profile.value; String get previousNickname => _previousNickname.value; @@ -95,7 +101,22 @@ class ProfileInputViewModel extends GetxController { communitySearchController = TextEditingController(); ageController = TextEditingController(); residenceController = TextEditingController(); + _residenceControllerListener = () { + residenceText.value = residenceController.text; + if (residenceText.value == "") { + _setResidenceNoResponse(); + } + }; + residenceController.addListener(_residenceControllerListener); homeTownController = TextEditingController(); + _homeTownControllerListener = () { + homeTownText.value = homeTownController.text; + if (homeTownText.value == "") { + _setHomeTownNoResponse(); + } + }; + homeTownController.addListener(_homeTownControllerListener); + _debounceNickname?.cancel(); _debounceCommunity?.cancel(); _profileRepository = Get.find(); @@ -118,18 +139,27 @@ class ProfileInputViewModel extends GetxController { _selectedCommunityId.value = previousProfile.community.id; ageController.text = previousProfile.ageRange; _selectedAge.value = previousProfile.ageRange; - _selectedResidenceProvince.value = - ProvinceType.fromCode(previousProfile.residenceProvince); - _selectedResidenceDistrict.value = - DistrictType.fromCode(previousProfile.residenceDistrict); - _selectedHomeTownProvince.value = - ProvinceType.fromCode(previousProfile.hometownProvince); - _selectedHomeTownDistrict.value = - DistrictType.fromCode(previousProfile.hometownDistrict); - residenceController.text = - '${_selectedResidenceProvince.value.name} ${_selectedResidenceDistrict.value?.name}'; - homeTownController.text = - '${_selectedHomeTownProvince.value.name} ${_selectedHomeTownDistrict.value?.name}'; + + if (previousProfile.residenceDistrict != '') { + _selectedResidenceProvince.value = + ProvinceType.fromCode(previousProfile.residenceProvince!); + _selectedResidenceDistrict.value = + DistrictType.fromCode(previousProfile.residenceDistrict!); + residenceController.text = + '${_selectedResidenceProvince.value.name} ${_selectedResidenceDistrict.value?.name}'; + } else { + residenceController.text = ''; + } + if (previousProfile.hometownDistrict != '') { + _selectedHomeTownProvince.value = + ProvinceType.fromCode(previousProfile.hometownProvince); + _selectedHomeTownDistrict.value = + DistrictType.fromCode(previousProfile.hometownDistrict); + homeTownController.text = + '${_selectedHomeTownProvince.value.name} ${_selectedHomeTownDistrict.value?.name}'; + } else { + homeTownController.text = ''; + } _isModifyScreen.value = true; _profile.refresh(); }); @@ -143,7 +173,9 @@ class ProfileInputViewModel extends GetxController { communityController.dispose(); communitySearchController.dispose(); ageController.dispose(); + residenceController.removeListener(_residenceControllerListener); residenceController.dispose(); + homeTownController.removeListener(_homeTownControllerListener); homeTownController.dispose(); super.dispose(); @@ -181,16 +213,18 @@ class ProfileInputViewModel extends GetxController { } Future> postProfile() async => + // 가입페이지에서는 거주지와 출신을 선택하지 않으므로 null 값일 수 있음. await _profileRepository.postProfile( entity: _profile.value.copyWith( - residenceProvince: _selectedResidenceProvince.value.code, - residenceDistrict: _selectedResidenceDistrict.value!.code, - hometownProvince: _selectedHomeTownProvince.value.code, - hometownDistrict: _selectedHomeTownDistrict.value!.code, + residenceProvince: null, + residenceDistrict: null, + hometownProvince: '', + hometownDistrict: '', ), ); Future> patchProfile() async { + // profile 수정 페이지 _profileViewModel.updateProfile(updatedProfile: _profile.value); return await _profileRepository.patchProfile(entity: _profile.value); @@ -283,10 +317,25 @@ class ProfileInputViewModel extends GetxController { _profile.value.gender.isNotEmpty && _profile.value.ageRange.isNotEmpty && _profile.value.community.id != -1 && - _selectedResidenceProvince.value.code.isNotEmpty && - _selectedResidenceDistrict.value != null && - _selectedHomeTownProvince.value.code.isNotEmpty && - _selectedHomeTownDistrict.value != null) { + (residenceText.value == '' || + (_selectedResidenceProvince.value.code.isNotEmpty && + _selectedResidenceDistrict.value != null)) && + (homeTownText.value == '' || + (_selectedHomeTownProvince.value.code.isNotEmpty && + _selectedHomeTownDistrict.value != null))) { + return true; + } else { + return false; + } + } + + bool isValidSignUpBtn() { + // 가입시에는 거주지와 도시가 없음. + if (_profile.value.nickname.isNotEmpty && + _nicknameErrorText.value.isEmpty && + _profile.value.gender.isNotEmpty && + _profile.value.ageRange.isNotEmpty && + _profile.value.community.id != -1) { return true; } else { return false; @@ -321,7 +370,26 @@ class ProfileInputViewModel extends GetxController { ); } + void _setResidenceNoResponse() { + _profile.value = _profile.value.copyWith( + residenceProvince: '', + residenceDistrict: '', + ); + } + + void _setHomeTownNoResponse() { + _profile.value = _profile.value.copyWith( + hometownProvince: '', + hometownDistrict: '', + ); + } + void checkSameToResidence() { + if (residenceText.value == '') { + homeTownController.text = ''; + return; + } + if (_selectedResidenceDistrict.value != null) { _selectedHomeTownProvince.value = _selectedResidenceProvince.value; _selectedHomeTownDistrict.value = _selectedResidenceDistrict.value; diff --git a/lib/features/profile/presentation/views/profile_input_screen.dart b/lib/features/profile/presentation/views/profile_input_screen.dart index 0f98832..d0634a4 100644 --- a/lib/features/profile/presentation/views/profile_input_screen.dart +++ b/lib/features/profile/presentation/views/profile_input_screen.dart @@ -21,7 +21,8 @@ class ProfileInputScreen extends GetView { return Obx(() { return DeScaffold( appBar: _appBar(isModifyScreen: controller.isModifyScreen), - body: _body(context: context), + body: + _body(isModifyScreen: controller.isModifyScreen, context: context), ); }); } @@ -38,7 +39,7 @@ class ProfileInputScreen extends GetView { ); } - Widget _body({required BuildContext context}) { + Widget _body({required bool isModifyScreen, required BuildContext context}) { Obx(() { if (controller.isApiLoading) { return DeProgressIndicator(); @@ -62,10 +63,12 @@ class ProfileInputScreen extends GetView { _widgetGender(), DeGaps.v32, _widgetAge(), - DeGaps.v32, - _widgetResidence(context: context), - DeGaps.v32, - _widgetHomeTown(context: context), + if (isModifyScreen) ...[ + DeGaps.v32, + _widgetResidence(context: context), + DeGaps.v32, + _widgetHomeTown(context: context), + ], DeGaps.v40, _widgetBottomButton(), DeGaps.v20, @@ -309,10 +312,6 @@ class ProfileInputScreen extends GetView { ProfileConstants.PROFILE_RESIDENCE, style: DeFonts.body14Sb, ), - DeText( - ProfileConstants.PROFILE_ESSENTIAL_STAR, - style: DeFonts.body14Sb.copyWith(color: DeColors.brand), - ) ], ), DeGaps.v4, @@ -335,13 +334,16 @@ class ProfileInputScreen extends GetView { ); } }, - child: DeTextField( - style: DeFonts.body16M, - hintText: ProfileConstants.PROFILE_RESIDENCE_HINT_TEXT, - controller: controller.residenceController, - enabled: false, - isCleanIcon: false, - ), + child: Obx(() { + // 선택한 사항이 없을 경우, isCleanItem 이 사라짐. + return DeTextField( + style: DeFonts.body16M, + hintText: ProfileConstants.PROFILE_RESIDENCE_HINT_TEXT, + controller: controller.residenceController, + enabled: false, + isCleanIcon: controller.residenceText.value != "", + ); + }), ), ], ); @@ -356,10 +358,6 @@ class ProfileInputScreen extends GetView { DeText( ProfileConstants.PROFILE_HOME_TOWN, style: DeFonts.body14Sb, - ), - DeText( - ProfileConstants.PROFILE_ESSENTIAL_STAR, - style: DeFonts.body14Sb.copyWith(color: DeColors.brand), ) ], ), @@ -383,12 +381,18 @@ class ProfileInputScreen extends GetView { ); } }, - child: DeTextField( - style: DeFonts.body16M, - hintText: ProfileConstants.PROFILE_HOME_TOWN_HINT_TEXT, - controller: controller.homeTownController, - enabled: false, - isCleanIcon: false, + child: Obx( + () { + // 선택한 사항이 없을 경우, isCleanItem 이 사라짐. + + return DeTextField( + style: DeFonts.body16M, + hintText: ProfileConstants.PROFILE_HOME_TOWN_HINT_TEXT, + controller: controller.homeTownController, + enabled: false, + isCleanIcon: controller.homeTownText.value != "", + ); + }, ), ), DeGaps.v12, @@ -427,38 +431,42 @@ class ProfileInputScreen extends GetView { Widget _widgetBottomButton() { return Obx(() { return DeButtonLarge( - controller.isModifyScreen - ? ProfileConstants.PROFILE_MODIFY_BTN_TEXT - : ProfileConstants.PROFILE_NEXT_BTN_TEXT, - onPressed: controller.isValidStartBtn() - ? controller.isModifyScreen - ? () => controller.patchProfile().then((result) { - result.when(loading: () { - controller.setApiLoading(isApiLoading: true); - }, success: (_) { - controller.setApiLoading(isApiLoading: false); - Get.back(); - }, failure: (msg) { - controller.setApiLoading(isApiLoading: false); - deSnackBar(msg); - }); - }) - : () => controller.postProfile().then((result) { - result.when(loading: () { - controller.setApiLoading(isApiLoading: true); - }, success: (_) { - controller.setApiLoading(isApiLoading: false); - Get.offAllNamed(GetRouterName.profileImage, arguments: { - 'is_modify_screen': controller.isModifyScreen, + controller.isModifyScreen + ? ProfileConstants.PROFILE_MODIFY_BTN_TEXT + : ProfileConstants.PROFILE_NEXT_BTN_TEXT, + onPressed: (controller.isModifyScreen + ? controller.isValidStartBtn() + : controller.isValidSignUpBtn()) + ? controller.isModifyScreen + ? () => controller.patchProfile().then((result) { + result.when(loading: () { + controller.setApiLoading(isApiLoading: true); + }, success: (_) { + controller.setApiLoading(isApiLoading: false); + Get.back(); + }, failure: (msg) { + controller.setApiLoading(isApiLoading: false); + deSnackBar(msg); }); - }, failure: (msg) { - controller.setApiLoading(isApiLoading: false); - deSnackBar(msg); - }); - }) - : () => {}, - enable: controller.isValidStartBtn(), - ); + }) + : () => controller.postProfile().then((result) { + result.when(loading: () { + controller.setApiLoading(isApiLoading: true); + }, success: (_) { + controller.setApiLoading(isApiLoading: false); + Get.offAllNamed(GetRouterName.profileImage, + arguments: { + 'is_modify_screen': controller.isModifyScreen, + }); + }, failure: (msg) { + controller.setApiLoading(isApiLoading: false); + deSnackBar(msg); + }); + }) + : () => {}, + enable: controller.isModifyScreen + ? controller.isValidStartBtn() + : controller.isValidSignUpBtn()); }); } } diff --git a/lib/widgets/de_text_field.dart b/lib/widgets/de_text_field.dart index 817d87b..1d8c93f 100644 --- a/lib/widgets/de_text_field.dart +++ b/lib/widgets/de_text_field.dart @@ -2,7 +2,6 @@ import 'package:debateseason_frontend_v1/core/constants/de_colors.dart'; import 'package:debateseason_frontend_v1/core/constants/de_dimensions.dart'; import 'package:debateseason_frontend_v1/core/constants/de_fonts.dart'; import 'package:debateseason_frontend_v1/core/constants/de_icons.dart'; -import 'package:debateseason_frontend_v1/widgets/de_gesture_detector.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -59,67 +58,86 @@ class DeTextField extends StatelessWidget { data: MediaQuery.of(context).copyWith( textScaler: const TextScaler.linear(1), ), - child: TextField( - controller: controller, - focusNode: focusNode, - decoration: decoration ?? - InputDecoration( - border: InputBorder.none, - hintStyle: style?.copyWith(color: DeColors.grey50), - hintText: hintText ?? '내용을 입력해 주세요.', - contentPadding: DeDimensions.all12, - isDense: true, - counterText: "", - filled: true, - fillColor: fillColor ?? DeColors.grey80, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: DeColors.brand, width: 1), - ), - disabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, + child: Stack( + children: [ + TextField( + controller: controller, + focusNode: focusNode, + decoration: decoration ?? + InputDecoration( + border: InputBorder.none, + hintStyle: style?.copyWith(color: DeColors.grey50), + hintText: hintText ?? '내용을 입력해 주세요.', + contentPadding: DeDimensions.all12, + isDense: true, + counterText: "", + filled: true, + fillColor: fillColor ?? DeColors.grey80, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: DeColors.brand, width: 1), + ), + disabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + // suffixIcon: isCleanIcon + // ? (controller.text.isNotEmpty + // ? DeGestureDetector( + // onTap: () { + // controller.clear(); + // }, + // child: Padding( + // padding: DeDimensions.all10, + // child: SvgPicture.asset(DeIcons.icXGrey50), + // ), + // ) + // : null) + // : SizedBox.shrink(), + ), + keyboardType: keyboardType, + textInputAction: textInputAction, + autofocus: autofocus, + maxLines: maxLines, + minLines: minLines, + expands: expands, + maxLength: maxLength, + // 색상 변경 여부 확인 + cursorColor: DeColors.grey10, + style: style ?? DeFonts.body14R, + textAlign: textAlign ?? TextAlign.start, + onChanged: (value) { + if (onChanged != null) { + onChanged!(value); + } + }, + onSubmitted: (value) { + if (onSubmitted != null) { + onSubmitted!(value); + } + }, + inputFormatters: inputFormatters, + + enabled: enabled, + textAlignVertical: textAlignVertical, + ), + if (isCleanIcon) + Positioned( + right: 4, + top: 0, + bottom: 0, + child: GestureDetector( + onTap: () => controller.clear(), + child: Padding( + padding: DeDimensions.all10, + child: SvgPicture.asset(DeIcons.icXGrey50)), ), - suffixIcon: isCleanIcon - ? (controller.text.isNotEmpty - ? DeGestureDetector( - onTap: () => controller.clear(), - child: Padding( - padding: DeDimensions.all10, - child: SvgPicture.asset(DeIcons.icXGrey50), - ), - ) - : null) - : SizedBox.shrink(), - ), - keyboardType: keyboardType, - textInputAction: textInputAction, - autofocus: autofocus, - maxLines: maxLines, - minLines: minLines, - expands: expands, - maxLength: maxLength, - // 색상 변경 여부 확인 - cursorColor: DeColors.grey10, - style: style ?? DeFonts.body14R, - textAlign: textAlign ?? TextAlign.start, - onChanged: (value) { - if (onChanged != null) { - onChanged!(value); - } - }, - onSubmitted: (value) { - if (onSubmitted != null) { - onSubmitted!(value); - } - }, - inputFormatters: inputFormatters, - enabled: enabled, - textAlignVertical: textAlignVertical, + ) + ], ), ); }