diff --git a/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditCategory.kt b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditCategory.kt new file mode 100644 index 00000000..7a3118bb --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditCategory.kt @@ -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()) + } + ) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditUiState.kt b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditUiState.kt new file mode 100644 index 00000000..499a6498 --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditUiState.kt @@ -0,0 +1,5 @@ +package com.kusitms.presentation.model.profile.edit + +data class ProfileEditUiState( + val currentSelectedProfileFilter: String = "기본 프로필", +) diff --git a/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditViewModel.kt b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditViewModel.kt new file mode 100644 index 00000000..d86695e1 --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditViewModel.kt @@ -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 = _uiState.asStateFlow() + + private val _isAllFieldsValid = MutableStateFlow(false) + val isAllFieldsValid: StateFlow = _isAllFieldsValid + + private val _expanded = MutableStateFlow(false) + val expended: StateFlow = _expanded.asStateFlow() + + private val _favoriteCategory = MutableStateFlow?>(null) + val favoriteCategory: StateFlow?> = _favoriteCategory + + private val _interests = MutableStateFlow>(emptyList()) + val interests: StateFlow> = _interests.asStateFlow() + + private val _name = MutableStateFlow("") + val name: StateFlow = _name + + private val _major = MutableStateFlow("") + val major: StateFlow = _major + + private val _phoneNum = MutableStateFlow("") + val phoneNum: StateFlow = _phoneNum + + private val _email = MutableStateFlow("") + val email: StateFlow = _email + + private val _emailError = MutableStateFlow(null) + val emailError: StateFlow = _emailError + + private val _phoneNumError = MutableStateFlow(null) + val phoneNumError: StateFlow = _phoneNumError + + + private val _selectedImage = MutableStateFlow(null) + val selectedImage: StateFlow = _selectedImage.asStateFlow() + + private val _linkItems = MutableStateFlow>(listOf(LinkItem(LinkType.LINK, ""))) + val linkItems: StateFlow> = _linkItems.asStateFlow() + + private val _introduce = MutableStateFlow("") + val introduce: StateFlow = _introduce + + private val _signInStatus = MutableStateFlow(SignInStatus.DEFAULT) + val signInStatus : StateFlow = _signInStatus + + private val _selectedPart = MutableStateFlow(null) + val selectedPart: StateFlow = _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) { + _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) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileFilterList.kt b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileFilterList.kt new file mode 100644 index 00000000..55a4451d --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileFilterList.kt @@ -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 = "추가 프로필"), +) diff --git a/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt b/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt index 84e61eaa..3535f67b 100644 --- a/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt +++ b/presentation/src/main/java/com/kusitms/presentation/navigation/NavGraph.kt @@ -2,7 +2,6 @@ package com.kusitms.presentation.navigation import ProfileDetailScreen import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.EnterTransition @@ -62,6 +61,7 @@ import com.kusitms.presentation.ui.notice.NoticeScreen import com.kusitms.presentation.ui.notice.detail.NoticeDetailScreen import com.kusitms.presentation.ui.notice.search.NoticeSearchScreen import com.kusitms.presentation.ui.profile.ProfileScreen +import com.kusitms.presentation.ui.profile.edit.ProfileEditScreen import com.kusitms.presentation.ui.profile.search.ProfileSearchScreen import com.kusitms.presentation.ui.setting.SettingMember import com.kusitms.presentation.ui.setting.SettingNonMember @@ -271,7 +271,10 @@ fun MainNavigation() { arguments = NavRoutes.MyProfileDetail.navArguments ) { MyProfileScreen( - onBack = { navController.navigateUp() } + onBack = { navController.navigateUp() }, + onClickModify = { + navController.navigate(NavRoutes.ProfileEdit.route) + } ) } @@ -362,7 +365,10 @@ fun MainNavigation() { arguments = NavRoutes.ProfileDetail.navArguments ) { ProfileDetailScreen( - onBack = { navController.navigateUp() } + onBack = { navController.navigateUp() }, + onClickModify = { + navController.navigate(NavRoutes.ProfileEdit.route) + } ) } @@ -381,6 +387,11 @@ fun MainNavigation() { ) } + kusitmsComposableWithAnimation(NavRoutes.ProfileEdit.route) { + ProfileEditScreen( + onBack = { navController.navigateUp() } + ) + } kusitmsComposableWithAnimation(NavRoutes.ImageViewer.route) { diff --git a/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt b/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt index d4a7c040..6f153024 100644 --- a/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt +++ b/presentation/src/main/java/com/kusitms/presentation/navigation/NavRoutes.kt @@ -95,5 +95,7 @@ sealed class NavRoutes( fun createRoute(memberId: Int) = "ProfileDetail/${memberId}" } + object ProfileEdit : NavRoutes(route = "ProfileEdit") + object ImageViewer : NavRoutes("ImageViewer") } diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/home/profile/MyProfileScreen.kt b/presentation/src/main/java/com/kusitms/presentation/ui/home/profile/MyProfileScreen.kt index ced81026..1ba7b6e0 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/home/profile/MyProfileScreen.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/home/profile/MyProfileScreen.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi fun MyProfileScreen( viewModel: MyProfileViewModel = hiltViewModel(), onBack: () -> Unit, + onClickModify: () -> Unit ) { val infoUser = viewModel.infoProfile val detailMemberInfo by viewModel.detailMemberInfo.collectAsStateWithLifecycle() @@ -44,7 +45,9 @@ fun MyProfileScreen( text = stringResource(id = R.string.profile_detail_edit), style = KusitmsTypo.current.Text_Medium, color = KusitmsColorPalette.current.Main400, - modifier = Modifier.clickable { /* 수정 */ } + modifier = Modifier.clickable { + onClickModify() + } ) } LazyColumn( diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/profile/detail/ProfileDetailScreen.kt b/presentation/src/main/java/com/kusitms/presentation/ui/profile/detail/ProfileDetailScreen.kt index 3bf75208..84f39c6a 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/profile/detail/ProfileDetailScreen.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/profile/detail/ProfileDetailScreen.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi fun ProfileDetailScreen( viewModel: ProfileDetailViewModel = hiltViewModel(), onBack: () -> Unit, + onClickModify: () -> Unit ) { val profile by viewModel.profile.collectAsStateWithLifecycle() val isMember = viewModel.infoProfile @@ -47,14 +48,9 @@ fun ProfileDetailScreen( text = stringResource(id = R.string.profile_detail_edit), style = KusitmsTypo.current.Text_Medium, color = KusitmsColorPalette.current.Main400, - modifier = Modifier.clickable { /* 수정 */ } - ) - } else { - Text( - text = stringResource(id = R.string.profile_detail_block), - style = KusitmsTypo.current.Text_Medium, - color = KusitmsColorPalette.current.Grey400, - modifier = Modifier.clickable { /* 차단 */ } + modifier = Modifier.clickable { + onClickModify() + } ) } } diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileAdd.kt b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileAdd.kt new file mode 100644 index 00000000..e7766d41 --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileAdd.kt @@ -0,0 +1,203 @@ +package com.kusitms.presentation.ui.profile.edit + +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.kusitms.presentation.R +import com.kusitms.presentation.common.ui.KusitmsMarginVerticalSpacer +import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette +import com.kusitms.presentation.common.ui.theme.KusitmsTypo +import com.kusitms.presentation.model.profile.edit.ProfileEditViewModel +import com.kusitms.presentation.ui.ImageVector.ImagePhoto +import com.kusitms.presentation.ui.ImageVector.StudyIcon +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun ProfileAdd( + viewModel: ProfileEditViewModel = hiltViewModel(), +) { + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .background(KusitmsColorPalette.current.Grey900), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.Top) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), + verticalAlignment = Alignment.Top, + ) { + StudyIcon.drawStudyIcon( + modifier = Modifier + .height(24.dp) + .width(24.dp) + ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 40.dp) + .background(KusitmsColorPalette.current.Grey900), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top + + ) { + Text( + text = stringResource(id = R.string.signin2_text1), + style = KusitmsTypo.current.SubTitle2_Semibold, + color = KusitmsColorPalette.current.Grey300 + ) + Text( + text = stringResource(id = R.string.signin2_text2), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Sub1 + ) + } + } + PhotoColumn(viewModel) + KusitmsMarginVerticalSpacer(size = 10) + IntroEditColumn(viewModel) + KusitmsMarginVerticalSpacer(size = 4) + ProfileLink(viewModel) + Spacer(modifier = Modifier.weight(1f)) + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun PhotoColumn(viewModel: ProfileEditViewModel) { + val imageUri by viewModel.selectedImage.collectAsState() // This should be a Uri? in your ViewModel + + val imagePickerLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> + uri?.let { + viewModel.updateSelectedImage(uri) + } + } + + Box( + modifier = Modifier + .width(96.dp) + .height(96.dp) + .background( + color = KusitmsColorPalette.current.Grey600, + shape = RoundedCornerShape(size = 12.dp) + ) + .clickable { + imagePickerLauncher.launch("image/*") + } + ) { + Column( + modifier = Modifier + .align(Alignment.Center), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + ImagePhoto(imageUri) + } + } +} + + +@Composable +fun IntroEditColumn(viewModel: ProfileEditViewModel) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(KusitmsColorPalette.current.Grey900), + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.Start, + ) { + Text( + text = stringResource(id = R.string.signin2_title1), + style = KusitmsTypo.current.SubTitle2_Semibold, + color = KusitmsColorPalette.current.Grey300 + ) + IntroEditTextField(viewModel) + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalCoroutinesApi::class) +@Composable +fun IntroEditTextField(viewModel: ProfileEditViewModel) { + val maxLength = 100 + val textState by viewModel.introduce.collectAsState() + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.Start + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(130.dp) + .background(KusitmsColorPalette.current.Grey600, shape = RoundedCornerShape(16.dp)) + ) { + TextField( + value = textState, + onValueChange = { + if (it.length <= maxLength) { + viewModel.updateIntroduce(it) + } + }, + shape = RoundedCornerShape(16.dp), + colors = TextFieldDefaults.colors( + focusedContainerColor = KusitmsColorPalette.current.Grey600, + unfocusedContainerColor = KusitmsColorPalette.current.Grey600, + disabledContainerColor = KusitmsColorPalette.current.Grey600, + cursorColor = KusitmsColorPalette.current.Grey400, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedTextColor = KusitmsColorPalette.current.White, + unfocusedTextColor = KusitmsColorPalette.current.White, + disabledLabelColor = KusitmsColorPalette.current.White, + ), + placeholder = { + Text( + stringResource(id = R.string.signin2_placeholder1), + style = KusitmsTypo.current.Text_Medium, + color = KusitmsColorPalette.current.Grey400 + ) + } + ) + } + Text( + text = "${textState.length}/$maxLength", + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + } +} + diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileBasic.kt b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileBasic.kt new file mode 100644 index 00000000..5851b231 --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileBasic.kt @@ -0,0 +1,433 @@ +package com.kusitms.presentation.ui.profile.edit + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +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.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.kusitms.presentation.R +import com.kusitms.presentation.common.ui.KusitmsMarginHorizontalSpacer +import com.kusitms.presentation.common.ui.KusitmsMarginVerticalSpacer +import com.kusitms.presentation.common.ui.KusitmsSnackField +import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette +import com.kusitms.presentation.common.ui.theme.KusitmsTypo +import com.kusitms.presentation.model.profile.edit.LikeCategoryBottomSheet +import com.kusitms.presentation.model.profile.edit.LikeCategoryTab +import com.kusitms.presentation.model.profile.edit.PartSelectColumn +import com.kusitms.presentation.model.profile.edit.ProfileEditViewModel +import com.kusitms.presentation.model.signIn.PartCategory +import com.kusitms.presentation.ui.ImageVector.StudyIcon +import com.kusitms.presentation.ui.login.member.TextColumn1 +import com.kusitms.presentation.ui.signIn.KusitmsInputField +import com.kusitms.presentation.ui.signIn.PartSnackTitle +import com.kusitms.presentation.ui.signIn.SignInFixedInput +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalMaterialApi::class, ExperimentalCoroutinesApi::class) +@Composable +fun ProfileBasic( + viewModel: ProfileEditViewModel = hiltViewModel(), +) { + val email by viewModel.email.collectAsState() + val phoneNum by viewModel.phoneNum.collectAsState() + val name by viewModel.name.collectAsState() + val major by viewModel.major.collectAsState() + val selectedPart by viewModel.selectedPart.collectAsState() + val interests by viewModel.interests.collectAsState() + val likeCategoryText = if (interests.isNotEmpty()) { + interests.joinToString(", ") { it.content } + } else { + stringResource(id = R.string.signin_member_hint1_3) + } + val emailError by viewModel.emailError.collectAsState() + val phoneNumError by viewModel.phoneNumError.collectAsState() + + var isOpenPartBottomSheet by remember { mutableStateOf(false) } + var isOpenLikeCategoryBottomSheet by remember { mutableStateOf(false) } + + if (isOpenPartBottomSheet) { + PartBottomSheet( + viewModel = viewModel, + isOpenPartBottomSheet + ) { + isOpenPartBottomSheet = it + if (!selectedPart.isNullOrBlank()) { + isOpenPartBottomSheet = false + } + } + } + + if (isOpenLikeCategoryBottomSheet) { + LikeCategoryBottomSheet( + viewModel = viewModel, + isOpenLikeCategoryBottomSheet + ) + { + isOpenLikeCategoryBottomSheet = it + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .background(KusitmsColorPalette.current.Grey900) + .height(64.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top + + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), + verticalAlignment = Alignment.Top, + ) { + StudyIcon.drawStudyIcon( + modifier = Modifier + .height(24.dp) + .width(24.dp) + ) + TextColumn1() + } + } + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = stringResource(id = R.string.signin_member_title1), + style = KusitmsTypo.current.SubTitle2_Semibold, + color = KusitmsColorPalette.current.Grey300 + ) + Spacer(modifier = Modifier.height(28.dp)) + Text( + text = stringResource(id = R.string.signin_member_caption1_1), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + Spacer(modifier = Modifier.height(5.dp)) + SignInFixedInput(modelValue = name) + Spacer(modifier = Modifier.height(20.dp)) + + //전공 + Text( + text = stringResource(id = R.string.signin_member_caption1_2), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + Spacer(modifier = Modifier.height(5.dp)) + KusitmsInputField( + text = R.string.signin_member_hint1_1, + value = major, + onValueChange = viewModel::updateMajor + ) + if (major.length > 20) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "최대 20자까지 입력할 수 있어요", + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + //파트 선택 + + Text( + text = stringResource(id = R.string.signin_member_caption1_3), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + Spacer(modifier = Modifier.height(5.dp)) + (if (!selectedPart.isNullOrEmpty()) { + selectedPart + } else { + stringResource(R.string.signin_member_hint1_2) + })?.let { + KusitmsSnackField( + text = it, + onSnackClick = { + isOpenPartBottomSheet = true + } + ) + } + + //관심 카테고리 + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(id = R.string.signin_member_caption1_4), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + Spacer(modifier = Modifier.height(5.dp)) + KusitmsSnackField( + text = likeCategoryText, + onSnackClick = { + isOpenLikeCategoryBottomSheet = true + } + ) + + + Spacer(modifier = Modifier.height(40.dp)) + Text( + text = stringResource(id = R.string.signin_member_title2), + style = KusitmsTypo.current.SubTitle2_Semibold, + color = KusitmsColorPalette.current.Grey300 + ) + //이메일 + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(id = R.string.signin_member_caption1_5), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + Spacer(modifier = Modifier.height(5.dp)) + KusitmsInputField( + text = R.string.signin_member_hint1_4, + value = email, + onValueChange = viewModel::updateEmail + ) + emailError?.let { error -> + Text( + text = error, + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + } + + //전화번호 + Spacer(modifier = Modifier.height(20.dp)) + Text( + text = stringResource(id = R.string.signin_member_caption1_6), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + Spacer(modifier = Modifier.height(5.dp)) + KusitmsInputField( + text = R.string.signin_member_hint1_5, + value = phoneNum, + onValueChange = viewModel::updatePhoneNumber + ) + phoneNumError?.let { error -> + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = error, + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@ExperimentalMaterialApi +@Composable +fun PartBottomSheet( + 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() + .wrapContentHeight() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .systemBarsPadding() + .statusBarsPadding(), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top + ) { + PartSnackTitle(onClick = { onChangeOpenBottomSheet(false) }) + KusitmsMarginVerticalSpacer(size = 20) + PartSelectColumn(viewModel = viewModel) + } + } + } +} + + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun PartSelectItem( + category: PartCategory, + onClick: (PartCategory) -> Unit, + viewModel: ProfileEditViewModel, +) { + val isSelected = viewModel.selectedPart.collectAsState().value?.contains(category.name) == true + val background = if (isSelected) KusitmsColorPalette.current.Grey500 else Color.Transparent + Row( + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .background(color = background, shape = RoundedCornerShape(size = 12.dp)) + .clickable { onClick(category) } + .padding(horizontal = 12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.Start), + verticalAlignment = Alignment.CenterVertically, + ) { + category.icon?.let { iconRes -> + Icon( + painter = painterResource(id = iconRes), + contentDescription = null, + tint = Color.Unspecified + ) + } + Text( + text = category.name, + style = KusitmsTypo.current.Text_Medium, + color = KusitmsColorPalette.current.Grey100 + ) + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun LikeBottomSheetContent(viewModel: ProfileEditViewModel, onClick: () -> Unit) { + var selectedCategory by remember { mutableStateOf(com.kusitms.presentation.model.signIn.categories.first()) } + val favoriteCategoryCount by viewModel.interests.collectAsState() + val buttonText = if (favoriteCategoryCount.isNotEmpty()) "확인" else "관심 카테고리를 선택해주세요" + val buttonColor = + if (favoriteCategoryCount.isNotEmpty()) KusitmsColorPalette.current.Grey100 else KusitmsColorPalette.current.Grey500 + val buttonTextColor = + if (favoriteCategoryCount.isNotEmpty()) KusitmsColorPalette.current.Grey600 else KusitmsColorPalette.current.Grey400 + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 0.dp) + .background( + color = KusitmsColorPalette.current.Grey600, + ), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top + ) { + KusitmsMarginVerticalSpacer(size = 24) + CategoryBottomSheetTitle( + viewModel = viewModel, + onClick + ) + KusitmsMarginVerticalSpacer(size = 16) + LikeCategoryTab( + selectedCategory = selectedCategory, + onCategorySelected = { category -> selectedCategory = category }, + viewModel = viewModel + ) + Spacer(modifier = Modifier.weight(1f)) + Button( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .height(56.dp), + onClick = { + onClick() + }, + colors = ButtonDefaults.buttonColors(containerColor = buttonColor), + shape = RoundedCornerShape(size = 16.dp) + ) { + Text( + text = buttonText, + style = KusitmsTypo.current.SubTitle2_Semibold, + color = buttonTextColor + ) + } + KusitmsMarginVerticalSpacer(size = 24) + } + +} + + +@Composable +fun CategoryBottomSheetTitle( + viewModel: ProfileEditViewModel, + onClick: () -> Unit, +) { + val favoriteCategoryCount = viewModel.interests.value.size ?: 0 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .height(24.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "관심 카테고리", + style = KusitmsTypo.current.SubTitle2_Semibold, + color = KusitmsColorPalette.current.Grey300 + ) + KusitmsMarginHorizontalSpacer(size = 12) + Box( + modifier = Modifier + .height(20.dp) + .width(20.dp) + .background( + color = KusitmsColorPalette.current.Grey500, + shape = RoundedCornerShape(size = 4.dp) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = favoriteCategoryCount.toString(), + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Main400 + ) + } + KusitmsMarginHorizontalSpacer(size = 2) + Text( + text = "개 선택", + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + Spacer(modifier = Modifier.weight(1f)) + IconButton(onClick = { + onClick() + }) { + Icon( + painter = painterResource(id = R.drawable.ic_inputx), + contentDescription = "bottomSheetDown", + tint = KusitmsColorPalette.current.Grey300 + ) + } + + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileEditScreen.kt b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileEditScreen.kt new file mode 100644 index 00000000..fbf21487 --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileEditScreen.kt @@ -0,0 +1,184 @@ +package com.kusitms.presentation.ui.profile.edit + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.kusitms.presentation.R +import com.kusitms.presentation.common.ui.KusitmsMarginVerticalSpacer +import com.kusitms.presentation.common.ui.KusitsmTopBarBackTextWithIcon +import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette +import com.kusitms.presentation.common.ui.theme.KusitmsTypo +import com.kusitms.presentation.model.profile.edit.ProfileEditViewModel +import com.kusitms.presentation.model.profile.edit.ProfileFilterList +import com.kusitms.presentation.model.profile.edit.categories +import com.kusitms.presentation.ui.ImageVector.icons.KusitmsIcons +import com.kusitms.presentation.ui.ImageVector.icons.kusitmsicons.ArrowDown +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun ProfileEditScreen( + viewModel: ProfileEditViewModel = hiltViewModel(), + onBack: () -> Unit, +) { + val scrollState = rememberScrollState() + + val expanded by viewModel.expended.collectAsStateWithLifecycle() + val uiState by viewModel.uiState.collectAsState() + + Column { + KusitsmTopBarBackTextWithIcon( + text = stringResource(id = R.string.profile_edit_toolbar), + onBackClick = { + onBack() + }, + ) { + Text( + text = stringResource(id = R.string.profile_edit_modify), + style = KusitmsTypo.current.Text_Medium, + color = KusitmsColorPalette.current.Main400, + modifier = Modifier.clickable { + + } + ) + } + KusitmsMarginVerticalSpacer(size = 32) + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .clickable { + viewModel.toggleExpanded() + }, + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.cardColors( + containerColor = KusitmsColorPalette.current.Grey700, + contentColor = KusitmsColorPalette.current.Grey100 + ) + ) { + ProfileFilterButton( + expanded = expanded, + viewModel = viewModel + ) + if (expanded) { + AllProfileFilterList( + partNameList = categories, + viewModel = viewModel + ) + } + } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + .background(KusitmsColorPalette.current.Grey900) + .verticalScroll(scrollState) + .height(910.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start + ) { + if (uiState.currentSelectedProfileFilter == "기본 프로필") { + ProfileBasic(viewModel) + } else { + ProfileAdd(viewModel) + } + } + } +} + + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +private fun ProfileFilterButton( + expanded: Boolean, + viewModel: ProfileEditViewModel, +) { + val uiState by viewModel.uiState.collectAsState() + + + Row( + modifier = Modifier + .height(48.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = uiState.currentSelectedProfileFilter.takeIf { it.isNotEmpty() } + ?: stringResource(id = R.string.profile_edit_basic), + style = KusitmsTypo.current.Text_Medium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + color = KusitmsColorPalette.current.Grey100, + ) + Icon( + imageVector = KusitmsIcons.ArrowDown, + contentDescription = stringResource(id = R.string.profile_edit_filter), + tint = KusitmsColorPalette.current.Grey400, + modifier = Modifier + .rotate(if (expanded) 180f else 0f) + .padding(horizontal = 16.dp) + ) + } +} + + +@Composable +fun AllProfileFilterList( + partNameList: List, + viewModel: ProfileEditViewModel, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + partNameList.forEach { profile -> + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp) + .clickable { + viewModel.changeSelectProfileFilter(profile.name) + + }, + contentAlignment = Alignment.CenterStart + ) { + Text( + text = profile.name, + style = KusitmsTypo.current.Text_Medium, + color = KusitmsColorPalette.current.Grey400, + ) + } + } + } +} + + +@Preview(showBackground = true, showSystemUi = true) +@Composable +fun ProfileEditScreenPreview() { + ProfileEditScreen( + onBack = {} + ) +} diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileLink.kt b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileLink.kt new file mode 100644 index 00000000..585df758 --- /dev/null +++ b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileLink.kt @@ -0,0 +1,305 @@ +package com.kusitms.presentation.ui.profile.edit + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.kusitms.presentation.R +import com.kusitms.presentation.common.ui.KusitmsMarginHorizontalSpacer +import com.kusitms.presentation.common.ui.KusitmsMarginVerticalSpacer +import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette +import com.kusitms.presentation.common.ui.theme.KusitmsTypo +import com.kusitms.presentation.model.profile.edit.ProfileEditViewModel +import com.kusitms.presentation.model.signIn.LinkType +import com.kusitms.presentation.ui.ImageVector.plusIcon +import com.kusitms.presentation.ui.signIn.KusitmsInputField +import com.kusitms.presentation.ui.signIn.component.LinkBottomSheet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@Composable +fun ProfileLink(viewModel: ProfileEditViewModel) { + LinkColumn(viewModel) +} + + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun LinkColumn(viewModel: ProfileEditViewModel) { + val linkItems by viewModel.linkItems.collectAsState() + val maxLength = 4 + var isOpenLinkBottomSheet by remember { mutableStateOf(false) } + var selectedLinkItemIndex by remember { mutableStateOf(-1) } // 선택된 링크 아이템의 인덱스 + + if (isOpenLinkBottomSheet) { + LinkBottomSheet( + viewModel = viewModel, + isOpenLinkBottomSheet, + selectedLinkItemIndex + ) { isOpen, selectedData -> + isOpenLinkBottomSheet = isOpen + if (!isOpen && selectedData is LinkType) { + viewModel.updateLinkItem( + selectedLinkItemIndex, + selectedData, + linkItems[selectedLinkItemIndex].linkUrl + ) + } + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + .background(KusitmsColorPalette.current.Grey900), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + style = KusitmsTypo.current.Text_Semibold, + color = KusitmsColorPalette.current.Grey300, + text = stringResource(id = R.string.signin2_title2), + ) + LinkRow1(viewModel, maxLength) + } + Spacer(modifier = Modifier.height(14.dp)) + LinkItemsDisplay(viewModel, onLinkTypeChange = { index -> + selectedLinkItemIndex = index + isOpenLinkBottomSheet = true + }) + } +} + + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun LinkRow1(viewModel: ProfileEditViewModel, maxLength: Int) { + val linkItems by viewModel.linkItems.collectAsState() + Row( + modifier = Modifier + .width(125.dp) + .height(36.dp) + .background( + color = KusitmsColorPalette.current.Grey900, + shape = RoundedCornerShape(size = 8.dp) + ), + horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + ) { + plusIcon() + Text( + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey300, + text = "추가하기${linkItems.size}/${maxLength}", + modifier = Modifier.clickable { + if (linkItems.size < maxLength) { + viewModel.addLinkItem() + } + } + ) + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun LinkItemsDisplay(viewModel: ProfileEditViewModel, onLinkTypeChange: (Int) -> Unit) { + val linkItems by viewModel.linkItems.collectAsState() + + linkItems.forEachIndexed { index, _ -> + LinkRow2(viewModel, index, onClick = { onLinkTypeChange(index) }) + KusitmsMarginVerticalSpacer(size = 8) + } +} + + +@OptIn(ExperimentalCoroutinesApi::class) +@Composable +fun LinkRow2( + viewModel: ProfileEditViewModel, + linkItemIndex: Int, + onClick: () -> Unit, +) { + val linkItems by viewModel.linkItems.collectAsState() + val currentLinkItem = linkItems.getOrNull(linkItemIndex) ?: return + Row( + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + horizontalArrangement = Arrangement.spacedBy(0.dp, Alignment.Start), + verticalAlignment = Alignment.CenterVertically, + ) { + KusitmsLinkCheck( + viewModel, + linkItemIndex, + currentLinkItem.linkType, + onClick + ) + IconButton( + onClick = { viewModel.removeLinkItem() }, + ) { + Icon( + painterResource(id = R.drawable.ic_trashcan), + contentDescription = "Localized description", + tint = Color.Unspecified, + modifier = Modifier + .size(25.dp) + ) + } + } +} + + +@Composable +fun KusitmsLinkCheck( + viewModel: ProfileEditViewModel, + linkItemIndex: Int, + currentLinkType: LinkType, + onLinkTypeChange: () -> Unit, +) { + Row( + modifier = Modifier + .width(300.dp) + .height(48.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.Start), + verticalAlignment = Alignment.CenterVertically + ) { + LinkCheckBox( + viewModel = viewModel, + linkItemIndex = linkItemIndex, + currentLinkType = currentLinkType, + onClick = onLinkTypeChange + ) + KusitmsMarginHorizontalSpacer(size = 5) + LinkTextField(viewModel, linkItemIndex) + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalCoroutinesApi::class) +@Composable +fun LinkTextField(viewModel: ProfileEditViewModel, linkItemIndex: Int) { + val linkItems by viewModel.linkItems.collectAsState() + val linkItem = linkItems[linkItemIndex] + val textState = remember { mutableStateOf(linkItem.linkUrl) } + val isClicked by remember { mutableStateOf(false) } + val borderColor = + if (isClicked) KusitmsColorPalette.current.Main500 else KusitmsColorPalette.current.Grey700 + Box( + modifier = Modifier + .width(220.dp) + .height(48.dp) + .border(width = 1.dp, color = borderColor, shape = RoundedCornerShape(12.dp)) + .background( + color = KusitmsColorPalette.current.Grey700, + shape = RoundedCornerShape(12.dp) + ), + ) { + KusitmsInputField( + text = R.string.signin2_placeholder2, + value = textState.value, + onValueChange = { + textState.value = it + viewModel.updateLinkItem(linkItemIndex, linkItem.linkType, it) + }, + modifier = Modifier + .width(220.dp) + .wrapContentHeight() + ) + } +} + + +@Composable +fun LinkCheckBox( + viewModel: ProfileEditViewModel, + linkItemIndex: Int, + currentLinkType: LinkType, + onClick: () -> Unit, +) { + var isOpenLinkBottomSheet by remember { mutableStateOf(false) } + + if (isOpenLinkBottomSheet) { + LinkBottomSheet( + viewModel, + isOpenLinkBottomSheet, + linkItemIndex + ) { isSelected, selectedLinkType -> + isOpenLinkBottomSheet = isSelected + if (isSelected && selectedLinkType is LinkType) { + viewModel.updateLinkItem( + linkItemIndex, + selectedLinkType, + viewModel.linkItems.value[linkItemIndex].linkUrl + ) + } + } + } + + Row( + modifier = Modifier + .width(110.dp) + .height(48.dp) + .background( + color = KusitmsColorPalette.current.Grey700, + shape = RoundedCornerShape(12.dp) + ) + .padding(start = 12.dp, top = 12.dp, end = 12.dp, bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + val displayText = if (currentLinkType == LinkType.LINK) { + stringResource(id = R.string.signin2_checkbox) + } else { + currentLinkType.displayName + } + + Text( + text = displayText, + style = KusitmsTypo.current.Caption1, + color = KusitmsColorPalette.current.Grey400 + ) + IconButton( + modifier = Modifier.size(24.dp), + onClick = onClick + ) { + Icon( + painter = painterResource(id = R.drawable.ic_under_errow), + contentDescription = null, + tint = KusitmsColorPalette.current.Grey400 + ) + } + } +} + + diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/LinkBottomSheet.kt b/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/LinkBottomSheet.kt index b7b738be..8124bede 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/LinkBottomSheet.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/LinkBottomSheet.kt @@ -13,10 +13,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModel import com.kusitms.presentation.R import com.kusitms.presentation.common.ui.KusitmsMarginVerticalSpacer import com.kusitms.presentation.common.ui.theme.KusitmsColorPalette import com.kusitms.presentation.common.ui.theme.KusitmsTypo +import com.kusitms.presentation.model.profile.edit.ProfileEditViewModel import com.kusitms.presentation.model.signIn.SignInViewModel import com.kusitms.presentation.model.signIn.linkCategories import com.kusitms.presentation.ui.ImageVector.xIcon @@ -25,7 +27,7 @@ import com.kusitms.presentation.ui.ImageVector.xIcon @OptIn(ExperimentalMaterial3Api::class) @Composable fun LinkBottomSheet( - viewModel: SignInViewModel, + viewModel: ViewModel, openBottomSheet: Boolean = false, linkItemIndex: Int, onChangeOpenBottomSheet: (Boolean, Any?) -> Unit = { b: Boolean, any: Any? -> } @@ -63,13 +65,17 @@ fun LinkBottomSheet( } @Composable -fun LinkSelectColumn(viewModel: SignInViewModel, linkItemIndex: Int) { +fun LinkSelectColumn(viewModel: ViewModel, linkItemIndex: Int) { LazyColumn(modifier = Modifier.padding(horizontal = 12.dp)) { items(linkCategories) { category -> LinkItem( category = category, onClick = { - viewModel.updateLinkTypeAt(linkItemIndex, category.linkType) + if (viewModel is SignInViewModel) { + viewModel.updateLinkTypeAt(linkItemIndex, category.linkType) + } else if (viewModel is ProfileEditViewModel) { + viewModel.updateLinkTypeAt(linkItemIndex, category.linkType) + } } ) } diff --git a/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/PartBottomSheet.kt b/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/PartBottomSheet.kt index 7e80eb82..933b4345 100644 --- a/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/PartBottomSheet.kt +++ b/presentation/src/main/java/com/kusitms/presentation/ui/signIn/component/PartBottomSheet.kt @@ -6,13 +6,11 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ScaffoldState import androidx.compose.material.Text import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -25,7 +23,6 @@ import com.kusitms.presentation.model.signIn.SignInViewModel import com.kusitms.presentation.model.signIn.categories import com.kusitms.presentation.model.signIn.mapCategoryToValue import com.kusitms.presentation.ui.ImageVector.xIcon -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -58,7 +55,7 @@ fun PartBottomSheet( horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Top ) { - partSnackTitle( onClick = { onChangeOpenBottomSheet(false) } ) + PartSnackTitle( onClick = { onChangeOpenBottomSheet(false) } ) KusitmsMarginVerticalSpacer(size = 20) partSelectColumn(viewModel = viewModel) } @@ -87,7 +84,7 @@ fun partSelectColumn(viewModel: SignInViewModel) { } @Composable -fun partSnackTitle(onClick: () -> Unit) { +fun PartSnackTitle(onClick: () -> Unit) { Row( modifier = Modifier .fillMaxWidth() diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 219146f1..df8edff8 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -229,6 +229,16 @@ 궁금한 학회원 프로필을\n검색해보세요 + + 프로필 설정 + 수정 + 프로필 선택 필터 + 기본 프로필 + 추가 프로필 + + + + 출석 공지