diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/mapper/MemberMapper.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/mapper/MemberMapper.kt index 99fb8cc78..9ab660d5a 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/data/mapper/MemberMapper.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/mapper/MemberMapper.kt @@ -9,5 +9,5 @@ fun MemberResponse.toData() = Member( name = name, description = description, profileImageUrl = imageUrl, - activities = activities.toData(), + activities = activities.toData().toSet(), ) diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/model/Member.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/model/Member.kt index fae72c561..488c9f0fb 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/data/model/Member.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/model/Member.kt @@ -6,7 +6,7 @@ data class Member( val name: String = "", val description: String = "", val profileImageUrl: String = "", - val activities: List = emptyList(), + val activities: Set = emptySet(), ) { val fields: List get() = activities.filter { it.activityType == ActivityType.INTEREST_FIELD } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/data/repository/concretes/DefaultMemberRepository.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/data/repository/concretes/DefaultMemberRepository.kt index dbd48f493..73f6077e7 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/data/repository/concretes/DefaultMemberRepository.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/data/repository/concretes/DefaultMemberRepository.kt @@ -57,7 +57,7 @@ class DefaultMemberRepository @Inject constructor( activitiesApiResponse: ApiResponse>, ): ApiResponse { return when (activitiesApiResponse) { - is Success -> Success(member.copy(activities = activitiesApiResponse.data)) + is Success -> Success(member.copy(activities = activitiesApiResponse.data.toSet())) is Failure -> Failure(activitiesApiResponse.code, activitiesApiResponse.message) NetworkError -> NetworkError is Unexpected -> Unexpected(activitiesApiResponse.error) diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/ClubsAddBottomDialogFragment.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/ClubsAddBottomDialogFragment.kt index e45dbaf45..a460396b1 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/ClubsAddBottomDialogFragment.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/ClubsAddBottomDialogFragment.kt @@ -6,10 +6,10 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import com.emmsale.R +import com.emmsale.data.model.Activity import com.emmsale.databinding.FragmentEditmyprofileClubsAddBottomDialogBinding import com.emmsale.presentation.common.views.ActivityTag import com.emmsale.presentation.common.views.activityChipOf -import com.emmsale.presentation.ui.editMyProfile.uiState.ActivityUiState import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.chip.ChipGroup import dagger.hilt.android.AndroidEntryPoint @@ -39,7 +39,6 @@ class ClubsAddBottomDialogFragment : BottomSheetDialogFragment() { super.onViewCreated(view, savedInstanceState) initDataBinding() setupUiLogic() - viewModel.fetchUnselectedActivities() } override fun getTheme(): Int = R.style.RoundBottomSheetDialogStyle @@ -59,25 +58,25 @@ class ClubsAddBottomDialogFragment : BottomSheetDialogFragment() { } private fun setupClubsUiLogic() { - viewModel.activities.observe(viewLifecycleOwner) { allActivities -> + viewModel.clubs.observe(viewLifecycleOwner) { clubs -> binding.cgEditmyprofileclubsdialogClubs.removeAllViews() - binding.cgEditmyprofileclubsdialogClubs.addChips(allActivities.clubs) + binding.cgEditmyprofileclubsdialogClubs.addChips(clubs) } } - private fun ChipGroup.addChips(clubs: List) { + private fun ChipGroup.addChips(clubs: List) { clubs.forEach { club -> val chip = getActivityTag(club) addView(chip) } } - private fun getActivityTag(club: ActivityUiState): ActivityTag { + private fun getActivityTag(club: Activity): ActivityTag { return activityChipOf { - text = club.activity.name - isChecked = club.isSelected - setOnCheckedChangeListener { _, _ -> - viewModel.toggleActivitySelection(club.activity.id) + text = club.name + isChecked = club in viewModel.selectedClubs.value!! + setOnCheckedChangeListener { _, isChecked -> + viewModel.setActivitySelection(club.id, isChecked) } } } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileActivity.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileActivity.kt index eaebe8d12..a2a656fa7 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileActivity.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileActivity.kt @@ -7,10 +7,11 @@ import android.text.Editable import android.text.InputType import android.text.TextWatcher import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.EditText import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels -import androidx.core.view.isVisible import com.emmsale.R import com.emmsale.databinding.ActivityEditMyProfileBinding import com.emmsale.presentation.base.NetworkActivity @@ -88,10 +89,11 @@ class EditMyProfileActivity : private fun setupDataBinding() { binding.viewModel = viewModel - binding.onFieldTagsAddButtonClick = ::showFieldTags + binding.onFieldAddButtonClick = ::showFieldTags binding.onEducationAddButtonClick = ::showEducations binding.onClubAddButtonClick = ::showClubs - binding.onProfileImageUpdateUiClick = ::startToEditProfileImage + binding.onProfileImageUiClick = ::startToEditProfileImage + binding.onDescriptionEditButtonClick = ::startToEditDescription } private fun showFieldTags() { @@ -129,6 +131,20 @@ class EditMyProfileActivity : ) } + private fun startToEditDescription() { + showKeyboard(binding.etEditmyprofileDescription) + binding.etEditmyprofileDescription.moveCursorToLast() + } + + private fun showKeyboard(view: EditText) { + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + if (view.requestFocus()) imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) + } + + private fun EditText.moveCursorToLast() { + setSelection(text.length) + } + private fun setupBackPressedDispatcher() { onBackPressedDispatcher.addCallback( this, @@ -159,9 +175,6 @@ class EditMyProfileActivity : } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - binding.ivEditmyprofileDescriptionPlaceholderIcon.isVisible = - s?.length == 0 || s == null - if (s.toString().contains('\n')) { binding.etEditmyprofileDescription.setText( s.toString().filterNot { it == '\n' }, @@ -202,7 +215,10 @@ class EditMyProfileActivity : WarningDialog( context = this, title = getString(R.string.editmyprofile_activity_remove_warning_title), - message = getString(R.string.editmyprofile_activity_remove_warning_message), + message = getString( + R.string.editmyprofile_activity_remove_warning_message, + viewModel.activities.value.first { it.id == activityId }.name, + ), positiveButtonLabel = getString(R.string.all_delete_button_label), negativeButtonLabel = getString(R.string.all_cancel), onPositiveButtonClick = { viewModel.removeActivity(activityId) }, diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileViewModel.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileViewModel.kt index c8245e398..f224a4067 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileViewModel.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EditMyProfileViewModel.kt @@ -1,7 +1,11 @@ package com.emmsale.presentation.ui.editMyProfile import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.map import androidx.lifecycle.viewModelScope +import com.emmsale.data.model.Activity +import com.emmsale.data.model.ActivityType import com.emmsale.data.model.Member import com.emmsale.data.repository.interfaces.ActivityRepository import com.emmsale.data.repository.interfaces.MemberRepository @@ -10,7 +14,6 @@ import com.emmsale.presentation.base.RefreshableViewModel import com.emmsale.presentation.common.livedata.NotNullLiveData import com.emmsale.presentation.common.livedata.NotNullMutableLiveData import com.emmsale.presentation.common.livedata.SingleLiveEvent -import com.emmsale.presentation.ui.editMyProfile.uiState.ActivitiesUiState import com.emmsale.presentation.ui.editMyProfile.uiState.EditMyProfileUiEvent import com.emmsale.presentation.ui.editMyProfile.uiState.EditMyProfileUiState import dagger.hilt.android.lifecycle.HiltViewModel @@ -31,35 +34,97 @@ class EditMyProfileViewModel @Inject constructor( private val _profile = NotNullMutableLiveData(EditMyProfileUiState(Member())) val profile: NotNullLiveData = _profile + private val _activities = NotNullMutableLiveData(listOf()) + val activities: NotNullLiveData> = _activities + + val fields: LiveData> = _activities.map { + it.filter { activity -> activity.activityType == ActivityType.INTEREST_FIELD } + } + + val clubs: LiveData> = _activities.map { + it.filter { activity -> activity.activityType == ActivityType.CLUB } + } + + val educations: LiveData> = _activities.map { + it.filter { activity -> activity.activityType == ActivityType.EDUCATION } + } + + private val _selectedActivityIds = NotNullMutableLiveData(setOf()) + + val selectedFields = MediatorLiveData(setOf()).apply { + addSource(_selectedActivityIds) { value = getSelectedFields() } + addSource(fields) { value = getSelectedFields() } + } + + val selectedClubs = MediatorLiveData(setOf()).apply { + addSource(_selectedActivityIds) { value = getSelectedClubs() } + addSource(clubs) { value = getSelectedClubs() } + } + + val selectedEducations = MediatorLiveData(setOf()).apply { + addSource(_selectedActivityIds) { value = getSelectedEducations() } + addSource(educations) { value = getSelectedEducations() } + } + + val isFieldsSelectionChanged = MediatorLiveData(false).apply { + addSource(_profile) { value = selectedFields.value != _profile.value.member.fields.toSet() } + addSource(selectedFields) { + value = selectedFields.value != _profile.value.member.fields.toSet() + } + } + + val isClubsSelectionChanged = MediatorLiveData(false).apply { + addSource(_profile) { value = selectedClubs.value != _profile.value.member.clubs.toSet() } + addSource(selectedClubs) { + value = selectedClubs.value != _profile.value.member.clubs.toSet() + } + } + + val isEducationsSelectionChanged = MediatorLiveData(false).apply { + addSource(_profile) { + value = selectedEducations.value != _profile.value.member.educations.toSet() + } + addSource(selectedEducations) { + value = selectedEducations.value != _profile.value.member.educations.toSet() + } + } + private val _uiEvent = SingleLiveEvent() val uiEvent: LiveData = _uiEvent - private val _activities = NotNullMutableLiveData(ActivitiesUiState()) - val activities: NotNullLiveData = _activities + private fun getSelectedFields(): Set = + fields.value?.filter { it.id in _selectedActivityIds.value }?.toSet() ?: emptySet() + + private fun getSelectedClubs(): Set = + clubs.value?.filter { it.id in _selectedActivityIds.value }?.toSet() ?: emptySet() + + private fun getSelectedEducations(): Set = + educations.value?.filter { it.id in _selectedActivityIds.value }?.toSet() ?: emptySet() init { fetchProfile() + fetchActivities() } private fun fetchProfile(): Job = fetchData( fetchData = { memberRepository.getMember(uid) }, - onSuccess = { _profile.value = EditMyProfileUiState(it) }, + onSuccess = ::onProfileFetchSuccess, + ) + + private fun fetchActivities(): Job = fetchData( + fetchData = { activityRepository.getActivities() }, + onSuccess = { _activities.value = it }, ) override fun refresh(): Job = refreshData( refresh = { memberRepository.getMember(uid) }, - onSuccess = { _profile.value = EditMyProfileUiState(it) }, + onSuccess = ::onProfileFetchSuccess, ) - fun fetchUnselectedActivities(): Job = fetchData( - fetchData = { activityRepository.getActivities() }, - onSuccess = { - _activities.value = _activities.value.fetchUnselectedActivities( - allActivities = it, - myActivities = profile.value.member.activities, - ) - }, - ) + private fun onProfileFetchSuccess(member: Member) { + _profile.value = EditMyProfileUiState(member) + _selectedActivityIds.value = member.activities.map(Activity::id).toSet() + } fun updateProfileImage(profileImageFile: File): Job = command( command = { memberRepository.updateMemberProfileImage(uid, profileImageFile) }, @@ -78,36 +143,67 @@ class EditMyProfileViewModel @Inject constructor( onFailure = { _, _ -> _uiEvent.value = EditMyProfileUiEvent.ActivityRemoveFail }, ) - fun addSelectedFields() { + fun updateFields() { viewModelScope.launch { - val selectedFieldIds = - activities.value.fields.filter { it.isSelected }.map { it.activity.id } - updateMemberActivities(selectedFieldIds) + val addFieldIds = selectedFields.value + ?.filter { it !in _profile.value.member.fields } + ?.map(Activity::id) + ?: emptyList() + val removeFieldIds = _profile.value.member.fields + .filter { it !in (selectedFields.value ?: emptyList()) } + .map(Activity::id) + if (addFieldIds.isNotEmpty()) addActivities(addFieldIds) + if (removeFieldIds.isNotEmpty()) removeActivities(removeFieldIds) } } fun addSelectedEducations() { viewModelScope.launch { - val selectedEducationIds = - activities.value.educations.filter { it.isSelected }.map { it.activity.id } - updateMemberActivities(selectedEducationIds) + val addEducationIds = selectedEducations.value + ?.filter { it !in _profile.value.member.educations } + ?.map(Activity::id) + ?: emptyList() + val removeEducationIds = _profile.value.member.educations + .filter { it !in (selectedEducations.value ?: emptyList()) } + .map(Activity::id) + if (addEducationIds.isNotEmpty()) addActivities(addEducationIds) + if (removeEducationIds.isNotEmpty()) removeActivities(removeEducationIds) } } fun addSelectedClubs() { viewModelScope.launch { - val selectedClubIds = - activities.value.clubs.filter { it.isSelected }.map { it.activity.id } - updateMemberActivities(selectedClubIds) + val addClubIds = selectedClubs.value + ?.filter { it !in _profile.value.member.clubs } + ?.map(Activity::id) + ?: emptyList() + val removeClubIds = _profile.value.member.clubs + .filter { it !in (selectedClubs.value ?: emptyList()) } + .map(Activity::id) + if (addClubIds.isNotEmpty()) addActivities(addClubIds) + if (removeClubIds.isNotEmpty()) removeActivities(removeClubIds) } } - private suspend fun updateMemberActivities(activityIds: List): Job = commandAndRefresh( + private suspend fun addActivities(activityIds: List): Job = commandAndRefresh( command = { memberRepository.addMemberActivities(activityIds) }, onFailure = { _, _ -> _uiEvent.value = EditMyProfileUiEvent.ActivitiesAddFail }, ) - fun toggleActivitySelection(activityId: Long) { - _activities.value = activities.value.toggleIsSelected(activityId) + private suspend fun removeActivities(activityIds: List): Job = commandAndRefresh( + command = { memberRepository.deleteMemberActivities(activityIds) }, + onFailure = { _, _ -> _uiEvent.value = EditMyProfileUiEvent.ActivityRemoveFail }, + ) + + fun setActivitySelection(activityId: Long, isSelected: Boolean) { + if (isSelected) { + _selectedActivityIds.value += activityId + } else { + _selectedActivityIds.value -= activityId + } + } + + companion object { + const val MAX_FIELDS_COUNT = 4 } } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EducationsAddBottomDialogFragment.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EducationsAddBottomDialogFragment.kt index 38a8962b7..6c4f350ad 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EducationsAddBottomDialogFragment.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/EducationsAddBottomDialogFragment.kt @@ -6,10 +6,10 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import com.emmsale.R +import com.emmsale.data.model.Activity import com.emmsale.databinding.FragmentEditmyprofileEducationsAddBottomDialogBinding import com.emmsale.presentation.common.views.ActivityTag import com.emmsale.presentation.common.views.activityChipOf -import com.emmsale.presentation.ui.editMyProfile.uiState.ActivityUiState import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.chip.ChipGroup import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +38,6 @@ class EducationsAddBottomDialogFragment : BottomSheetDialogFragment() { initDataBinding() setupUiLogic() - viewModel.fetchUnselectedActivities() } override fun getTheme(): Int = R.style.RoundBottomSheetDialogStyle @@ -58,25 +57,25 @@ class EducationsAddBottomDialogFragment : BottomSheetDialogFragment() { } private fun setupEducationsUiLogic() { - viewModel.activities.observe(viewLifecycleOwner) { allActivities -> + viewModel.educations.observe(viewLifecycleOwner) { educations -> binding.cgEditmyprofileeducationsdialogEducations.removeAllViews() - binding.cgEditmyprofileeducationsdialogEducations.addChips(allActivities.educations) + binding.cgEditmyprofileeducationsdialogEducations.addChips(educations) } } - private fun ChipGroup.addChips(educations: List) { + private fun ChipGroup.addChips(educations: List) { educations.forEach { education -> val chip = getActivityTag(education) addView(chip) } } - private fun getActivityTag(education: ActivityUiState): ActivityTag { + private fun getActivityTag(education: Activity): ActivityTag { return activityChipOf { - text = education.activity.name - isChecked = education.isSelected - setOnCheckedChangeListener { _, _ -> - viewModel.toggleActivitySelection(education.activity.id) + text = education.name + isChecked = education in viewModel.selectedEducations.value!! + setOnCheckedChangeListener { _, isChecked -> + viewModel.setActivitySelection(education.id, isChecked) } } } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/FieldsAddBottomDialogFragment.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/FieldsAddBottomDialogFragment.kt index 228ba60df..6ca987df7 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/FieldsAddBottomDialogFragment.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/FieldsAddBottomDialogFragment.kt @@ -6,10 +6,10 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import com.emmsale.R +import com.emmsale.data.model.Activity import com.emmsale.databinding.FragmentEditmyprofileFieldsAddBottomDialogBinding import com.emmsale.presentation.common.views.ActivityTag import com.emmsale.presentation.common.views.activityChipOf -import com.emmsale.presentation.ui.editMyProfile.uiState.ActivityUiState import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.chip.ChipGroup import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +38,6 @@ class FieldsAddBottomDialogFragment : BottomSheetDialogFragment() { initDataBinding() setupUiLogic() - viewModel.fetchUnselectedActivities() } override fun getTheme(): Int = R.style.RoundBottomSheetDialogStyle @@ -49,7 +48,7 @@ class FieldsAddBottomDialogFragment : BottomSheetDialogFragment() { } private fun addFields() { - viewModel.addSelectedFields() + viewModel.updateFields() dismiss() } @@ -58,25 +57,25 @@ class FieldsAddBottomDialogFragment : BottomSheetDialogFragment() { } private fun setupFieldsUiLogic() { - viewModel.activities.observe(viewLifecycleOwner) { allActivities -> + viewModel.fields.observe(viewLifecycleOwner) { fields -> binding.cgEditmyprofilefieldsdialogFields.removeAllViews() - binding.cgEditmyprofilefieldsdialogFields.addChips(allActivities.fields) + binding.cgEditmyprofilefieldsdialogFields.addChips(fields) } } - private fun ChipGroup.addChips(fields: List) { + private fun ChipGroup.addChips(fields: List) { fields.forEach { field -> val chip = getActivityTag(field) addView(chip) } } - private fun getActivityTag(field: ActivityUiState): ActivityTag { + private fun getActivityTag(field: Activity): ActivityTag { return activityChipOf { - text = field.activity.name - isChecked = field.isSelected - setOnCheckedChangeListener { _, _ -> - viewModel.toggleActivitySelection(field.activity.id) + text = field.name + isChecked = field in viewModel.selectedFields.value!! + setOnCheckedChangeListener { _, isChecked -> + viewModel.setActivitySelection(field.id, isChecked) } } } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/ActivitiesUiState.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/ActivitiesUiState.kt deleted file mode 100644 index 5424919cb..000000000 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/ActivitiesUiState.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.emmsale.presentation.ui.editMyProfile.uiState - -import com.emmsale.data.model.Activity -import com.emmsale.data.model.ActivityType.CLUB -import com.emmsale.data.model.ActivityType.EDUCATION -import com.emmsale.data.model.ActivityType.INTEREST_FIELD - -data class ActivitiesUiState( - val activities: List = emptyList(), -) { - val fields = activities.filter { it.activity.activityType == INTEREST_FIELD } - - val selectedFieldsSize: Int = fields.count { it.isSelected } - - val educations = activities.filter { it.activity.activityType == EDUCATION } - - val selectedEducationsSize: Int = educations.count { it.isSelected } - - val clubs = activities.filter { it.activity.activityType == CLUB } - - val selectedClubsSize: Int = clubs.count { it.isSelected } - - fun fetchUnselectedActivities( - allActivities: List, - myActivities: List, - ): ActivitiesUiState { - val unSelectedActivities = allActivities - .filterNot { myActivities.contains(it) } - .map { ActivityUiState(it, false) } - - return ActivitiesUiState(unSelectedActivities) - } - - fun toggleIsSelected(activityId: Long): ActivitiesUiState = copy( - activities = activities.map { if (it.activity.id == activityId) it.toggleSelection() else it }, - ) -} diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/ActivityUiState.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/ActivityUiState.kt deleted file mode 100644 index 109ecc959..000000000 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/ActivityUiState.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.emmsale.presentation.ui.editMyProfile.uiState - -import com.emmsale.data.model.Activity - -data class ActivityUiState( - val activity: Activity, - val isSelected: Boolean, -) { - fun toggleSelection(): ActivityUiState = copy( - isSelected = !isSelected, - ) - - companion object { - fun from(activity: Activity, isSelected: Boolean = false) = ActivityUiState( - activity = activity, - isSelected = isSelected, - ) - } -} diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/EditMyProfileUiState.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/EditMyProfileUiState.kt index 9e2fcffaa..eda90bcc2 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/EditMyProfileUiState.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/editMyProfile/uiState/EditMyProfileUiState.kt @@ -6,9 +6,6 @@ data class EditMyProfileUiState( val member: Member = Member(), ) { - val selectableFieldSize: Int - get() = (MAX_FIELDS_COUNT - member.fields.size).coerceAtLeast(0) - fun changeDescription(description: String): EditMyProfileUiState = copy( member = member.copy(description = description), ) @@ -16,8 +13,4 @@ data class EditMyProfileUiState( fun updateProfileImageUrl(profileImageUrl: String): EditMyProfileUiState = copy( member = member.copy(profileImageUrl = profileImageUrl), ) - - companion object { - private const val MAX_FIELDS_COUNT = 4 - } } diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/myProfile/MyProfileFragment.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/myProfile/MyProfileFragment.kt index 773be2cfc..e53ac128c 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/myProfile/MyProfileFragment.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/myProfile/MyProfileFragment.kt @@ -56,8 +56,8 @@ class MyProfileFragment : NetworkFragment(R.layout.fra private fun setupActivitiesRecyclerView() { val decoration = IntervalItemDecoration(height = 13.dp) listOf( - binding.rvMyprofileEducations, - binding.rvMyprofileClubs, + binding.layoutProfile.rvProfileEducations, + binding.layoutProfile.rvProfileClubs, ).forEach { it.apply { adapter = ActivitiesAdapter() @@ -76,20 +76,20 @@ class MyProfileFragment : NetworkFragment(R.layout.fra } private fun handleFields(fields: List) { - binding.cgMyprofileFields.removeAllViews() + binding.layoutProfile.cgProfileFields.removeAllViews() fields.forEach { val tagView = CategoryTagChip(requireContext()).apply { text = it.name } - binding.cgMyprofileFields.addView(tagView) + binding.layoutProfile.cgProfileFields.addView(tagView) } } private fun handleEducations(educations: List) { - (binding.rvMyprofileEducations.adapter as ActivitiesAdapter).submitList(educations) + (binding.layoutProfile.rvProfileEducations.adapter as ActivitiesAdapter).submitList(educations) } private fun handleClubs(clubs: List) { - (binding.rvMyprofileClubs.adapter as ActivitiesAdapter).submitList(clubs) + (binding.layoutProfile.rvProfileClubs.adapter as ActivitiesAdapter).submitList(clubs) } companion object { diff --git a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/profile/ProfileActivity.kt b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/profile/ProfileActivity.kt index 84d9f8ecf..5890325ff 100644 --- a/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/profile/ProfileActivity.kt +++ b/android/2023-emmsale/app/src/main/java/com/emmsale/presentation/ui/profile/ProfileActivity.kt @@ -86,8 +86,8 @@ class ProfileActivity : NetworkActivity(R.layout.activit private fun setupActivitiesRecyclerView() { val decoration = IntervalItemDecoration(height = 13.dp) listOf( - binding.rvProfileEducations, - binding.rvProfileClubs, + binding.layoutProfile.rvProfileEducations, + binding.layoutProfile.rvProfileClubs, ).forEach { it.apply { adapter = ActivitiesAdapter() @@ -112,20 +112,20 @@ class ProfileActivity : NetworkActivity(R.layout.activit } private fun handleFields(profile: ProfileUiState) { - binding.cgProfileFields.removeAllViews() + binding.layoutProfile.cgProfileFields.removeAllViews() profile.member.fields.forEach { val tagView = CategoryTagChip(this).apply { text = it.name } - binding.cgProfileFields.addView(tagView) + binding.layoutProfile.cgProfileFields.addView(tagView) } } private fun handleActivities(profile: ProfileUiState) { - (binding.rvProfileEducations.adapter as ActivitiesAdapter).submitList( + (binding.layoutProfile.rvProfileEducations.adapter as ActivitiesAdapter).submitList( profile.member.educations, ) - (binding.rvProfileClubs.adapter as ActivitiesAdapter).submitList( + (binding.layoutProfile.rvProfileClubs.adapter as ActivitiesAdapter).submitList( profile.member.clubs, ) } diff --git a/android/2023-emmsale/app/src/main/res/drawable/ic_editmyprofile_description_edit.xml b/android/2023-emmsale/app/src/main/res/drawable/ic_editmyprofile_description_edit.xml new file mode 100644 index 000000000..03e9c65b5 --- /dev/null +++ b/android/2023-emmsale/app/src/main/res/drawable/ic_editmyprofile_description_edit.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/2023-emmsale/app/src/main/res/drawable/ic_editmyprofile_description_placeholder.xml b/android/2023-emmsale/app/src/main/res/drawable/ic_editmyprofile_description_placeholder.xml deleted file mode 100644 index 4edf0e363..000000000 --- a/android/2023-emmsale/app/src/main/res/drawable/ic_editmyprofile_description_placeholder.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/2023-emmsale/app/src/main/res/drawable/ic_profile_activitynamedecorator.xml b/android/2023-emmsale/app/src/main/res/drawable/ic_profile_activitynamedecorator.xml index 90f0b9dbd..b6724a4f8 100644 --- a/android/2023-emmsale/app/src/main/res/drawable/ic_profile_activitynamedecorator.xml +++ b/android/2023-emmsale/app/src/main/res/drawable/ic_profile_activitynamedecorator.xml @@ -1,5 +1,5 @@ - + diff --git a/android/2023-emmsale/app/src/main/res/drawable/ic_profile_education.xml b/android/2023-emmsale/app/src/main/res/drawable/ic_profile_education.xml index 690e3959c..e6981553d 100644 --- a/android/2023-emmsale/app/src/main/res/drawable/ic_profile_education.xml +++ b/android/2023-emmsale/app/src/main/res/drawable/ic_profile_education.xml @@ -3,7 +3,14 @@ android:height="12dp" android:viewportWidth="12" android:viewportHeight="12"> + + + android:pathData="M0.2,9.2L0,12L2.8,11.8L10.1,4.5L7.5,1.9L0.2,9.2Z" + android:fillColor="#ffffff"/> + + diff --git a/android/2023-emmsale/app/src/main/res/drawable/img_editmyprofile_certain_activity_add_button.xml b/android/2023-emmsale/app/src/main/res/drawable/img_editmyprofile_certain_activity_add_button.xml new file mode 100644 index 000000000..fafc3175d --- /dev/null +++ b/android/2023-emmsale/app/src/main/res/drawable/img_editmyprofile_certain_activity_add_button.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/android/2023-emmsale/app/src/main/res/layout/activity_edit_my_profile.xml b/android/2023-emmsale/app/src/main/res/layout/activity_edit_my_profile.xml index f3e0393ef..824f25afd 100644 --- a/android/2023-emmsale/app/src/main/res/layout/activity_edit_my_profile.xml +++ b/android/2023-emmsale/app/src/main/res/layout/activity_edit_my_profile.xml @@ -14,7 +14,7 @@ type="com.emmsale.presentation.ui.editMyProfile.EditMyProfileViewModel" /> + + + + @@ -89,13 +97,14 @@ android:textColor="@color/black" android:textColorHint="@color/dark_gray" android:textSize="13sp" - app:layout_constrainedWidth="true" - app:layout_constraintEnd_toEndOf="@+id/tv_editmyprofile_name" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintStart_toStartOf="@+id/tv_editmyprofile_name" + app:layout_constraintStart_toStartOf="parent" + android:layout_marginStart="17dp" app:layout_constraintTop_toBottomOf="@+id/tv_editmyprofile_name" app:layout_constraintWidth_max="260dp" - tools:ignore="LabelFor" /> + tools:ignore="LabelFor" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintEnd_toStartOf="@+id/iv_editmyprofile_description_placeholder_icon" /> + android:src="@drawable/img_editmyprofile_activity_add_button" /> @@ -180,12 +189,16 @@ android:id="@+id/iv_editmyprofile_description_placeholder_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="5dp" android:importantForAccessibility="no" + android:padding="4dp" + android:background="?attr/selectableItemBackgroundBorderless" app:layout_constraintBottom_toBottomOf="@+id/et_editmyprofile_description" + app:srcCompat="@drawable/ic_editmyprofile_description_edit" app:layout_constraintStart_toEndOf="@+id/et_editmyprofile_description" - app:layout_constraintTop_toTopOf="@+id/et_editmyprofile_description" - app:srcCompat="@drawable/ic_editmyprofile_description_placeholder" /> + android:layout_marginStart="1dp" + android:onClick="@{() -> onDescriptionEditButtonClick.invoke()}" + app:layout_constraintEnd_toStartOf="@+id/iv_editmyprofile_member_image" + android:layout_marginBottom="-3dp" /> + + + + + + tools:listitem="@layout/item_editmyprofile_activity" + app:layout_constraintBottom_toBottomOf="parent" /> + + - - - - + app:layout_constraintTop_toBottomOf="@+id/rv_editmyprofile_educations" /> diff --git a/android/2023-emmsale/app/src/main/res/layout/activity_profile.xml b/android/2023-emmsale/app/src/main/res/layout/activity_profile.xml index 55639d46f..4653bcc0f 100644 --- a/android/2023-emmsale/app/src/main/res/layout/activity_profile.xml +++ b/android/2023-emmsale/app/src/main/res/layout/activity_profile.xml @@ -1,7 +1,7 @@ + xmlns:bind="http://schemas.android.com/apk/res-auto"> @@ -31,189 +31,16 @@ app:titleCentered="true" app:titleTextAppearance="@style/ToolbarTitleFontStyle" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintEnd_toEndOf="parent" + bind:member="@{viewModel.profile.member}" + app:layout_constraintBottom_toBottomOf="parent" /> + app:visible="@{viewModel.networkUiState == NetworkUiState.NETWORK_ERROR}" + android:id="@+id/networkErrorView2" /> diff --git a/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_clubs_add_bottom_dialog.xml b/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_clubs_add_bottom_dialog.xml index 11f22ce2e..47ee3da15 100644 --- a/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_clubs_add_bottom_dialog.xml +++ b/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_clubs_add_bottom_dialog.xml @@ -35,7 +35,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="49dp" - android:text="@string/editmyprofile_clubs_label" + android:text="@string/editmyprofile_club_label" android:textColor="@color/black" android:textSize="17sp" android:textStyle="bold" @@ -57,11 +57,11 @@ android:layout_height="wrap_content" android:layout_marginTop="32dp" android:background="@drawable/bg_all_two_state_button" - android:enabled="@{viewModel.activities.selectedClubsSize > 0}" + android:enabled="@{viewModel.isClubsSelectionChanged}" android:gravity="center" android:onClick="@{() -> addClubs.invoke()}" android:paddingVertical="21dp" - android:text="@string/editmyprofile_add" + android:text="@string/editmyprofile_update" android:textColor="@color/white" android:textSize="16sp" android:textStyle="bold" diff --git a/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_educations_add_bottom_dialog.xml b/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_educations_add_bottom_dialog.xml index 36cc63e07..75408ced2 100644 --- a/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_educations_add_bottom_dialog.xml +++ b/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_educations_add_bottom_dialog.xml @@ -35,7 +35,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="49dp" - android:text="@string/editmyprofile_educations_label" + android:text="@string/editmyprofile_education_label" android:textColor="@color/black" android:textSize="17sp" android:textStyle="bold" @@ -57,11 +57,11 @@ android:layout_height="wrap_content" android:layout_marginTop="32dp" android:background="@drawable/bg_all_two_state_button" - android:enabled="@{viewModel.activities.selectedEducationsSize > 0}" + android:enabled="@{viewModel.isEducationsSelectionChanged}" android:gravity="center" android:onClick="@{() -> addEducations.invoke()}" android:paddingVertical="21dp" - android:text="@string/editmyprofile_add" + android:text="@string/editmyprofile_update" android:textColor="@color/white" android:textSize="16sp" android:textStyle="bold" diff --git a/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_fields_add_bottom_dialog.xml b/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_fields_add_bottom_dialog.xml index af3e6458b..e8ecd7eb8 100644 --- a/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_fields_add_bottom_dialog.xml +++ b/android/2023-emmsale/app/src/main/res/layout/fragment_editmyprofile_fields_add_bottom_dialog.xml @@ -48,7 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" - android:text="@{@string/editmyprofilebottomdialog_field_selection_guide(viewModel.profile.selectableFieldSize)}" + android:text="@{@string/editmyprofilebottomdialog_field_selection_guide(viewModel.MAX_FIELDS_COUNT)}" android:textColor="@color/dark_gray" android:textSize="12sp" app:layout_constraintStart_toStartOf="@+id/tv_editmyprofilefieldsdialog_interest_tag_label" @@ -72,11 +72,11 @@ android:layout_height="wrap_content" android:layout_marginTop="32dp" android:background="@drawable/bg_all_two_state_button" - android:enabled="@{viewModel.activities.selectedFieldsSize > 0 && viewModel.activities.selectedFieldsSize <=viewModel.profile.selectableFieldSize }" + android:enabled="@{viewModel.isFieldsSelectionChanged && viewModel.selectedFields.size <=viewModel.MAX_FIELDS_COUNT }" android:gravity="center" android:onClick="@{() -> addFields.invoke()}" android:paddingVertical="21dp" - android:text="@string/editmyprofile_add" + android:text="@string/editmyprofile_update" android:textColor="@color/white" android:textSize="16sp" android:textStyle="bold" diff --git a/android/2023-emmsale/app/src/main/res/layout/fragment_my_profile.xml b/android/2023-emmsale/app/src/main/res/layout/fragment_my_profile.xml index 0996852ed..9e0def305 100644 --- a/android/2023-emmsale/app/src/main/res/layout/fragment_my_profile.xml +++ b/android/2023-emmsale/app/src/main/res/layout/fragment_my_profile.xml @@ -1,6 +1,7 @@ @@ -28,190 +29,16 @@ app:layout_constraintTop_toTopOf="parent" app:menu="@menu/menu_myprofile_toolbar" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> + app:visible="@{viewModel.networkUiState == NetworkUiState.NETWORK_ERROR}" + android:id="@+id/networkErrorView" /> diff --git a/android/2023-emmsale/app/src/main/res/layout/item_editmyprofile_activity.xml b/android/2023-emmsale/app/src/main/res/layout/item_editmyprofile_activity.xml index eff92925e..6c3e0ae6f 100644 --- a/android/2023-emmsale/app/src/main/res/layout/item_editmyprofile_activity.xml +++ b/android/2023-emmsale/app/src/main/res/layout/item_editmyprofile_activity.xml @@ -40,7 +40,7 @@ android:id="@+id/iv_editmyprfileactivities_activity_remove_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:contentDescription="@string/editmyprofile_activity_remote_button_description" + android:contentDescription="@string/editmyprofile_activity_remove_button_description" android:padding="7dp" app:layout_constraintBottom_toBottomOf="@+id/tv_editmyprofileactivities_activityname" app:layout_constraintEnd_toEndOf="parent" diff --git a/android/2023-emmsale/app/src/main/res/layout/layout_profile.xml b/android/2023-emmsale/app/src/main/res/layout/layout_profile.xml new file mode 100644 index 000000000..e5fbb9dea --- /dev/null +++ b/android/2023-emmsale/app/src/main/res/layout/layout_profile.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/2023-emmsale/app/src/main/res/values/strings.xml b/android/2023-emmsale/app/src/main/res/values/strings.xml index 5ac9268c8..685396e8b 100644 --- a/android/2023-emmsale/app/src/main/res/values/strings.xml +++ b/android/2023-emmsale/app/src/main/res/values/strings.xml @@ -356,17 +356,17 @@ 관심 카테고리 추가 버튼 회원의 사진 관심 카테고리 제거 + 활동 추가 버튼 활동 활동했던 이력을 추가해보세요. - 동아리 활동 - 교육 활동 - 교육 활동 전체 삭제 버튼 - 동아리 활동 전체 삭제 버튼 - 활동 종류 추가 버튼 - 추가하기 - 활동 삭제 버튼 + 동아리 활동 + 교육 활동 + 동아리 활동 추가 버튼 + 교육 활동 추가 버튼 + 활동 삭제 버튼 + 변경하기 활동 삭제 - 해당 활동을 삭제하시겠습니까? + %s 활동을 삭제하시겠습니까? 프로필을 불러오는 데 실패했어요 😥 한 줄 소개를 수정하는 데 실패했어요 😥 활동 정보를 불러오는 데 실패했어요 😥