From 68a506d4d7076385536bb12e72f5e3a5bb1e37b8 Mon Sep 17 00:00:00 2001 From: arinming Date: Wed, 7 Feb 2024 23:16:50 +0900 Subject: [PATCH] =?UTF-8?q?FEAT:=20=ED=8C=8C=ED=8A=B8=20/=20=EA=B4=80?= =?UTF-8?q?=EC=8B=AC=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=B0=94?= =?UTF-8?q?=ED=85=80=EC=8B=9C=ED=8A=B8=20=EC=B6=94=EA=B0=80=20(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/profile/edit/ProfileEditCategory.kt | 152 ++++++++++++ .../profile/edit/ProfileEditViewModel.kt | 13 + .../ui/profile/edit/ProfileBasic.kt | 234 +++++++++++++++++- .../ui/signIn/component/PartBottomSheet.kt | 7 +- 4 files changed, 400 insertions(+), 6 deletions(-) create mode 100644 presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditCategory.kt 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/ProfileEditViewModel.kt b/presentation/src/main/java/com/kusitms/presentation/model/profile/edit/ProfileEditViewModel.kt index 4bf97253..d86695e1 100644 --- 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 @@ -79,6 +79,17 @@ class ProfileEditViewModel @Inject constructor( 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)) { @@ -113,6 +124,7 @@ class ProfileEditViewModel @Inject constructor( } + fun addLinkItem() { val newLinkItem = LinkItem(LinkType.LINK, "") //기본 설정값 _linkItems.value = _linkItems.value + newLinkItem @@ -154,4 +166,5 @@ class ProfileEditViewModel @Inject constructor( 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/ui/profile/edit/ProfileBasic.kt b/presentation/src/main/java/com/kusitms/presentation/ui/profile/edit/ProfileBasic.kt index 50678969..5851b231 100644 --- 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 @@ -1,14 +1,30 @@ 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 @@ -17,21 +33,30 @@ 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(ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalCoroutinesApi::class) @Composable fun ProfileBasic( viewModel: ProfileEditViewModel = hiltViewModel(), @@ -53,6 +78,27 @@ fun ProfileBasic( 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 @@ -198,4 +244,190 @@ fun ProfileBasic( 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/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()