-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEAT] 프로필 설정 UI 구현 #103
Merged
Merged
[FEAT] 프로필 설정 UI 구현 #103
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e14d74f
FEAT: nav 경로 설정 (#101)
arinming fb7d909
FEAT: 프로필 설정 필터 버튼 구현 (#101)
arinming 47131af
FEAT: 이름, 전공 입력 (#101)
arinming 7afbee7
FEAT: 기본 프로필 설정 UI 구현 (#101)
arinming 2b8ca85
FEAT: 이메일, 전화번호 형식 규약 추가 (#101)
arinming 6a292ba
FEAT: 추가 프로필 이미지, 한 줄 소개 추가 (#101)
arinming 186a4b8
FEAT: 추가 프로필 설정 UI 구현 (#101)
arinming f18dbf1
FEAT: 기본 프로필 / 추가 프로필 구분 (#101)
arinming 68a506d
FEAT: 파트 / 관심 카테고리 바텀시트 추가 (#101)
arinming File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
152 changes: 152 additions & 0 deletions
152
...entation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditCategory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package com.kusitms.presentation.model.profile.edit | ||
|
||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.statusBarsPadding | ||
import androidx.compose.foundation.layout.systemBarsPadding | ||
import androidx.compose.foundation.layout.wrapContentHeight | ||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.foundation.lazy.grid.GridCells | ||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid | ||
import androidx.compose.foundation.lazy.grid.items | ||
import androidx.compose.foundation.lazy.items | ||
import androidx.compose.material.ExperimentalMaterialApi | ||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.ModalBottomSheet | ||
import androidx.compose.material3.rememberModalBottomSheetState | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.unit.dp | ||
import com.kusitms.presentation.common.ui.KusitmsTabItem | ||
import com.kusitms.presentation.common.ui.KusitmsTabRow | ||
import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette | ||
import com.kusitms.presentation.model.signIn.InterestItem | ||
import com.kusitms.presentation.model.signIn.PartCategory | ||
import com.kusitms.presentation.model.signIn.mapCategoryToValue | ||
import com.kusitms.presentation.ui.profile.edit.LikeBottomSheetContent | ||
import com.kusitms.presentation.ui.profile.edit.PartSelectItem | ||
import com.kusitms.presentation.ui.signIn.component.LikeCategoryItem | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
|
||
|
||
@Composable | ||
fun PartSelectColumn(viewModel: ProfileEditViewModel) { | ||
val filteredCategories = com.kusitms.presentation.model.signIn.categories.filter { it.name != "기타" } | ||
LazyColumn( | ||
modifier = Modifier | ||
.padding(horizontal = 24.dp) | ||
) { | ||
items(filteredCategories) { category -> | ||
PartSelectItem( | ||
category = category, | ||
onClick = { selectedCategory -> | ||
val selectedValue = mapCategoryToValue(selectedCategory.name) | ||
viewModel.updateSelectedPart(selectedValue) | ||
}, | ||
viewModel = viewModel | ||
) | ||
} | ||
} | ||
} | ||
|
||
|
||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@ExperimentalMaterialApi | ||
@Composable | ||
fun LikeCategoryBottomSheet( | ||
viewModel: ProfileEditViewModel, | ||
openBottomSheet: Boolean = false, | ||
onChangeOpenBottomSheet: (Boolean) -> Unit = {}, | ||
) { | ||
val bottomSheetState = rememberModalBottomSheetState( | ||
skipPartiallyExpanded = true | ||
) | ||
|
||
if (openBottomSheet) { | ||
ModalBottomSheet( | ||
containerColor = KusitmsColorPalette.current.Grey600, | ||
dragHandle = { Box(Modifier.height(0.dp)) }, | ||
onDismissRequest = { onChangeOpenBottomSheet(false) }, | ||
sheetState = bottomSheetState, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.height(704.dp) | ||
) { | ||
Column( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.wrapContentHeight() | ||
.systemBarsPadding() | ||
.statusBarsPadding() | ||
) { | ||
LikeBottomSheetContent( | ||
viewModel = viewModel, | ||
onClick = { onChangeOpenBottomSheet(false) }) | ||
} | ||
|
||
} | ||
} | ||
} | ||
|
||
|
||
|
||
|
||
@Composable | ||
fun LikeCategoryTab(selectedCategory: PartCategory, onCategorySelected: (PartCategory) -> Unit, viewModel: ProfileEditViewModel) { | ||
KusitmsTabRow( | ||
tabItemList = com.kusitms.presentation.model.signIn.categories, | ||
tabContent = { category -> | ||
KusitmsTabItem( | ||
text = category.name, | ||
isSelected = category == selectedCategory, | ||
onSelect = { onCategorySelected(category) } | ||
) | ||
} | ||
) | ||
when (selectedCategory.name) { | ||
"기획" -> LikeCategoryItems(category = com.kusitms.presentation.model.signIn.categories[0], viewModel = viewModel) | ||
"개발" -> LikeCategoryItems(category = com.kusitms.presentation.model.signIn.categories[1], viewModel = viewModel) | ||
"디자인" -> LikeCategoryItems(category = com.kusitms.presentation.model.signIn.categories[2], viewModel = viewModel) | ||
"기타" -> LikeCategoryItems(category = com.kusitms.presentation.model.signIn.categories[3], viewModel = viewModel) | ||
else -> {} | ||
} | ||
} | ||
|
||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
@Composable | ||
fun LikeCategoryItems(category: PartCategory, viewModel: ProfileEditViewModel) { | ||
val selectedInterests = viewModel.interests.collectAsState().value.toMutableSet() | ||
|
||
LazyVerticalGrid( | ||
columns = GridCells.Fixed(2), | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(horizontal = 10.dp, vertical = 16.dp) | ||
.height(436.dp), | ||
horizontalArrangement = Arrangement.spacedBy(12.dp), | ||
verticalArrangement = Arrangement.spacedBy(12.dp) | ||
) { | ||
items(category.subCategories) { subCategory -> | ||
val interestItem = InterestItem(mapCategoryToValue(category.name), subCategory) | ||
LikeCategoryItem( | ||
subCategoryName = subCategory, | ||
isSelected = interestItem in selectedInterests, | ||
onSelect = { | ||
if (interestItem in selectedInterests) { | ||
selectedInterests.remove(interestItem) | ||
} else { | ||
selectedInterests.add(interestItem) | ||
} | ||
viewModel.updateInterests(selectedInterests.toList()) | ||
} | ||
) | ||
} | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditUiState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.kusitms.presentation.model.profile.edit | ||
|
||
data class ProfileEditUiState( | ||
val currentSelectedProfileFilter: String = "기본 프로필", | ||
) |
170 changes: 170 additions & 0 deletions
170
...ntation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package com.kusitms.presentation.model.profile.edit | ||
|
||
import android.net.Uri | ||
import androidx.lifecycle.ViewModel | ||
import com.kusitms.presentation.model.signIn.InterestItem | ||
import com.kusitms.presentation.model.signIn.LinkItem | ||
import com.kusitms.presentation.model.signIn.LinkType | ||
import com.kusitms.presentation.model.signIn.SignInStatus | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
class ProfileEditViewModel @Inject constructor( | ||
|
||
) : ViewModel() { | ||
private val _uiState = MutableStateFlow(ProfileEditUiState()) | ||
val uiState: StateFlow<ProfileEditUiState> = _uiState.asStateFlow() | ||
|
||
private val _isAllFieldsValid = MutableStateFlow(false) | ||
val isAllFieldsValid: StateFlow<Boolean> = _isAllFieldsValid | ||
|
||
private val _expanded = MutableStateFlow(false) | ||
val expended: StateFlow<Boolean> = _expanded.asStateFlow() | ||
|
||
private val _favoriteCategory = MutableStateFlow<List<String>?>(null) | ||
val favoriteCategory: StateFlow<List<String>?> = _favoriteCategory | ||
|
||
private val _interests = MutableStateFlow<List<InterestItem>>(emptyList()) | ||
val interests: StateFlow<List<InterestItem>> = _interests.asStateFlow() | ||
|
||
private val _name = MutableStateFlow("") | ||
val name: StateFlow<String> = _name | ||
|
||
private val _major = MutableStateFlow("") | ||
val major: StateFlow<String> = _major | ||
|
||
private val _phoneNum = MutableStateFlow("") | ||
val phoneNum: StateFlow<String> = _phoneNum | ||
|
||
private val _email = MutableStateFlow("") | ||
val email: StateFlow<String> = _email | ||
|
||
private val _emailError = MutableStateFlow<String?>(null) | ||
val emailError: StateFlow<String?> = _emailError | ||
|
||
private val _phoneNumError = MutableStateFlow<String?>(null) | ||
val phoneNumError: StateFlow<String?> = _phoneNumError | ||
|
||
|
||
private val _selectedImage = MutableStateFlow<Uri?>(null) | ||
val selectedImage: StateFlow<Uri?> = _selectedImage.asStateFlow() | ||
|
||
private val _linkItems = MutableStateFlow<List<LinkItem>>(listOf(LinkItem(LinkType.LINK, ""))) | ||
val linkItems: StateFlow<List<LinkItem>> = _linkItems.asStateFlow() | ||
|
||
private val _introduce = MutableStateFlow("") | ||
val introduce: StateFlow<String> = _introduce | ||
|
||
private val _signInStatus = MutableStateFlow(SignInStatus.DEFAULT) | ||
val signInStatus : StateFlow<SignInStatus> = _signInStatus | ||
|
||
private val _selectedPart = MutableStateFlow<String?>(null) | ||
val selectedPart: StateFlow<String?> = _selectedPart | ||
|
||
fun changeSelectProfileFilter(category: String) { | ||
_uiState.value = _uiState.value.copy(currentSelectedProfileFilter = category) | ||
_expanded.value = false | ||
} | ||
|
||
fun toggleExpanded() { | ||
_expanded.value = !_expanded.value | ||
} | ||
|
||
fun updateMajor(newMajor: String) { | ||
_major.value = newMajor | ||
validateFields() | ||
} | ||
|
||
|
||
fun updateSelectedPart(part: String) { | ||
_selectedPart.value = part | ||
validateFields() | ||
} | ||
|
||
fun updateInterests(interestItems: List<InterestItem>) { | ||
_interests.value = interestItems | ||
validateFields() | ||
} | ||
|
||
fun updateEmail(newEmail: String) { | ||
_email.value = newEmail | ||
if (!isValidEmail(newEmail)) { | ||
_emailError.value = "유효한 이메일 주소를 입력해주세요" | ||
} else { | ||
_emailError.value = null | ||
} | ||
validateFields() | ||
} | ||
|
||
fun updatePhoneNumber(newPhoneNumber: String) { | ||
_phoneNum.value = newPhoneNumber | ||
if (!isValidPhoneNumber(newPhoneNumber)) { | ||
_phoneNumError.value = "유효한 전화번호를 입력해주세요 (010-1234-1234)" | ||
} else { | ||
_phoneNumError.value = null | ||
} | ||
validateFields() | ||
} | ||
|
||
fun updateSelectedImage(uri: Uri?) { | ||
_selectedImage.value = uri | ||
} | ||
|
||
fun updateLinkTypeAt(index: Int, linkType: LinkType) { | ||
val updatedItems = _linkItems.value.toMutableList() | ||
if (index in updatedItems.indices) { | ||
val currentItem = updatedItems[index] | ||
updatedItems[index] = currentItem.copy(linkType = linkType) | ||
_linkItems.value = updatedItems | ||
} | ||
} | ||
|
||
|
||
|
||
fun addLinkItem() { | ||
val newLinkItem = LinkItem(LinkType.LINK, "") //기본 설정값 | ||
_linkItems.value = _linkItems.value + newLinkItem | ||
} | ||
|
||
fun updateLinkItem(index: Int, linkType: LinkType, url: String) { | ||
val updatedItems = _linkItems.value.toMutableList() | ||
if (index in updatedItems.indices) { | ||
updatedItems[index] = LinkItem(linkType, url) | ||
_linkItems.value = updatedItems | ||
} | ||
} | ||
|
||
fun removeLinkItem() { | ||
if (_linkItems.value.isNotEmpty()) { | ||
_linkItems.value = _linkItems.value.dropLast(1) | ||
} | ||
} | ||
|
||
fun updateIntroduce(introduce: String) { | ||
_introduce.value = introduce | ||
} | ||
|
||
|
||
private fun validateFields() { | ||
_isAllFieldsValid.value = _major.value.isNotBlank() && | ||
_selectedPart.value != null && | ||
_interests.value.isNotEmpty() && | ||
_email.value.isNotBlank() && | ||
_phoneNum.value.isNotBlank() | ||
} | ||
|
||
private fun isValidEmail(email: String): Boolean { | ||
val emailRegex = Regex("^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}\$") | ||
return email.matches(emailRegex) | ||
} | ||
|
||
private fun isValidPhoneNumber(phoneNumber: String): Boolean { | ||
val phoneRegex = Regex("^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}\$") | ||
return phoneNumber.matches(phoneRegex) | ||
} | ||
|
||
} |
11 changes: 11 additions & 0 deletions
11
presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileFilterList.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.kusitms.presentation.model.profile.edit | ||
|
||
data class ProfileFilterList( | ||
val id: Int, | ||
val name: String, | ||
) | ||
|
||
val categories = listOf( | ||
ProfileFilterList(1, name = "기본 프로필"), | ||
ProfileFilterList(2, name = "추가 프로필"), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 이전 코드를 짠 것을 사용하는데에 문제가 있었는지 혹시 궁금합니다 🥹 .... 변경 사항 보고 놀래서 들어왔는데 재사용을 안하시고 새로 만드셨길래
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
민서님 코드 재사용 하려고 했는데, Composable 선언할 때 파라미터로 SignInViewModel을 받으셨더라구요!
그래서 저도 SignInViewModel을 써야하거나, 뷰를 새로 만들던가 해야했는데
민서님 코드와 달리 프로필 설정 부분에서 이메일/전화번호 수정이 가능하기도 했고
Profile 설정쪽 뷰모델을 아예 다르게 지정하는게 맞을 것 같아서 뷰를 새로 만들고 ProfileEditViewModel을 파라미터로 설정했습니당
ProfileEditViewModel에서 사용하는 API, SignInViewModel에서 사용하는 API가 달라 분리를 하긴 했습니다.!
이 부분에 대해서는 저도 얘기를 드리고 싶었는데, 차라리 ViewModel을 하나로 통일하고 재사용하는 식으로 리팩토링 하는게 나을 것 같아요! 피그마상에서도 자잘하게 다른 부분이 꽤나 있어 추후 작업이 필요해 보입니당.. 그래도 ViewModel이 없는 쪽에서는 최대한 민서님 코드를 재사용 해둔 것 같아요 이 부분에 대해서 저희끼리 회의할 때 얘기하면 좋을 것 같습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
같은 뷰모델을 사용해도 될것이라 생각했는데 이전 화면에는 필요없는 api도 같이 딸려 들어있다보니 조금 낭비적인 측면도 있긴 하겠네요 .. 🤔
추후에 아린님 말씀처럼 공통 뷰 모델로 통일하는 방안이 좋은 해결 방안일것 같습니다 출시 후에 논의해봐요
답변 감사합니다!