diff --git a/feature/post/src/main/java/com/msg/post/PostAddScreen.kt b/feature/post/src/main/java/com/msg/post/PostAddScreen.kt index 1f481e6a..609f4b99 100644 --- a/feature/post/src/main/java/com/msg/post/PostAddScreen.kt +++ b/feature/post/src/main/java/com/msg/post/PostAddScreen.kt @@ -1,6 +1,7 @@ package com.msg.post import androidx.activity.ComponentActivity +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Column @@ -16,8 +17,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.input.pointer.pointerInput @@ -25,6 +25,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.msg.design_system.component.button.BitgoeulButton import com.msg.design_system.component.button.ButtonState import com.msg.design_system.component.button.DetailSettingButton @@ -42,12 +43,20 @@ internal fun PostAddScreenRoute( onSettingClicked: () -> Unit, onAddClicked: () -> Unit ) { + val titleValue by viewModel.title.collectAsStateWithLifecycle() + val contentValue by viewModel.content.collectAsStateWithLifecycle() + val isFeedType = viewModel.currentFeedType.value + + val typeText = when (isFeedType) { + FeedType.EMPLOYMENT -> "게시글" + FeedType.NOTICE -> "공지사항" + } PostAddScreen( onBackClicked = onBackClicked, onSettingClicked = { title, content -> - viewModel.savedTitle.value = title - viewModel.savedContent.value = content + viewModel.onTitleChange(title) + viewModel.onContentChange(content) onSettingClicked() }, onAddClicked = { feedType, title, content -> @@ -65,14 +74,18 @@ internal fun PostAddScreenRoute( content = content ) } - viewModel.savedContent.value = "" - viewModel.savedTitle.value = "" + viewModel.onContentChange("") + viewModel.onTitleChange("") viewModel.isEditPage.value = false onAddClicked() }, - savedTitle = viewModel.savedTitle.value, - savedContent = viewModel.savedContent.value, - feedType = viewModel.currentFeedType.value + title = titleValue, + content = contentValue, + onTitleChange = viewModel::onTitleChange, + onContentChange = viewModel::onContentChange, + maxTitleLength = 100, + typeText = typeText, + feedType = isFeedType, ) } @@ -80,24 +93,18 @@ internal fun PostAddScreenRoute( internal fun PostAddScreen( modifier: Modifier = Modifier, focusManager: FocusManager = LocalFocusManager.current, + scrollState: ScrollState = rememberScrollState(), onBackClicked: () -> Unit, onSettingClicked: (title: String, content: String) -> Unit, onAddClicked: (feedType: FeedType, title: String, content: String) -> Unit, - savedTitle: String, - savedContent: String, - feedType: FeedType + title: String, + content: String, + onTitleChange: (String) -> Unit, + onContentChange: (String) -> Unit, + maxTitleLength: Int, + feedType: FeedType, + typeText: String ) { - val title = remember { mutableStateOf(savedTitle) } - val content = remember { mutableStateOf(savedContent) } - - val maxTitleLength = 100 - - val scrollState = rememberScrollState() - - val typeText = when (feedType) { - FeedType.EMPLOYMENT -> "게시글" - FeedType.NOTICE -> "공지사항" - } BitgoeulAndroidTheme { colors, typography -> Surface( modifier = modifier @@ -129,11 +136,11 @@ internal fun PostAddScreen( ) { BasicTextField( modifier = modifier.fillMaxWidth(), - value = title.value, - onValueChange = { if (it.length <= maxTitleLength) title.value = it }, + value = title, + onValueChange = { if (it.length <= maxTitleLength) onTitleChange(it) }, textStyle = typography.titleSmall, decorationBox = { innerTextField -> - if (title.value.isEmpty()) Text( + if (title.isEmpty()) Text( text = "$typeText 제목 (100자 이내)", style = typography.titleSmall, color = colors.G1 @@ -143,18 +150,18 @@ internal fun PostAddScreen( ) Spacer(modifier = modifier.height(16.dp)) HorizontalDivider( - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), thickness = 1.dp, color = colors.G9 ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = modifier.height(16.dp)) BasicTextField( modifier = modifier.fillMaxWidth(), - value = content.value, - onValueChange = { if (it.length <= maxTitleLength) content.value = it }, + value = content, + onValueChange = { if (it.length <= maxTitleLength) onContentChange(it) }, textStyle = typography.bodySmall, decorationBox = { innerTextField -> - if (content.value.isEmpty()) Text( + if (content.isEmpty()) Text( text = "본문 입력 (1000자 이내)", style = typography.bodySmall, color = colors.G1 @@ -169,7 +176,7 @@ internal fun PostAddScreen( .padding(horizontal = 28.dp) ) { HorizontalDivider( - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), thickness = 1.dp, color = colors.G9 ) @@ -178,15 +185,15 @@ internal fun PostAddScreen( modifier = modifier.fillMaxWidth(), type = typeText ) { - onSettingClicked(title.value, content.value) + onSettingClicked(title, content) } Spacer(modifier = modifier.height(8.dp)) BitgoeulButton( modifier = modifier.fillMaxWidth(), text = "$typeText 추가", - state = if (title.value.isNotEmpty() && content.value.isNotEmpty()) ButtonState.Enable else ButtonState.Disable + state = if (title.isNotEmpty() && content.isNotEmpty()) ButtonState.Enable else ButtonState.Disable ) { - onAddClicked(feedType, title.value, content.value) + onAddClicked(feedType, title, content) } Spacer(modifier = modifier.height(16.dp)) } @@ -202,9 +209,13 @@ fun PostAddScreenPre() { onBackClicked = {}, onSettingClicked = { _, _ -> }, onAddClicked = { _, _, _ -> }, - savedTitle = "", - savedContent = "", feedType = FeedType.NOTICE, - focusManager = LocalFocusManager.current + focusManager = LocalFocusManager.current, + title = "제목", + content = "내용", + onTitleChange = {}, + onContentChange = {}, + maxTitleLength = 100, + typeText = "공지사항" ) } \ No newline at end of file diff --git a/feature/post/src/main/java/com/msg/post/PostDetailScreen.kt b/feature/post/src/main/java/com/msg/post/PostDetailScreen.kt index b4b05dc9..d9f36697 100644 --- a/feature/post/src/main/java/com/msg/post/PostDetailScreen.kt +++ b/feature/post/src/main/java/com/msg/post/PostDetailScreen.kt @@ -2,6 +2,7 @@ package com.msg.post import com.msg.model.enumdata.Authority import androidx.activity.ComponentActivity +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -19,7 +20,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -96,6 +97,7 @@ private suspend fun getDetailPost( @Composable internal fun PostDetailScreen( modifier: Modifier = Modifier, + scrollState: ScrollState = rememberScrollState(), data: GetDetailPostEntity, id: UUID, role: Authority = Authority.ROLE_USER, @@ -103,9 +105,7 @@ internal fun PostDetailScreen( onEditClicked: () -> Unit, onBackClicked: () -> Unit ) { - val scrollState = rememberScrollState() - - val isDialogShow = remember { mutableStateOf(false) } + val (isDialogShow, setIsDialogShow) = rememberSaveable { mutableStateOf(false) } BitgoeulAndroidTheme { colors, typography -> Box { @@ -176,21 +176,19 @@ internal fun PostDetailScreen( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { if (role == Authority.ROLE_ADMIN) { - Row( - modifier = Modifier.weight(0.45f) - ) { + Row(modifier = modifier.weight(0.45f)) { NegativeBitgoeulButton( - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), text = "삭제하기" ) { - isDialogShow.value = true + setIsDialogShow(true) } } Row( - modifier = Modifier.weight(0.45f) + modifier = modifier.weight(0.45f) ) { BitgoeulButton( - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), text = "수정하기" ) { onEditClicked() @@ -202,8 +200,8 @@ internal fun PostDetailScreen( title = "게시글을 삭제하시겠습니까?", negativeAction = "삭제", content = data.title, - isVisible = isDialogShow.value, - onQuit = { isDialogShow.value = false }, + isVisible = isDialogShow, + onQuit = { setIsDialogShow(false) }, onActionClicked = { onDeleteClicked(id) } ) } diff --git a/feature/post/src/main/java/com/msg/post/PostDetailSettingScreen.kt b/feature/post/src/main/java/com/msg/post/PostDetailSettingScreen.kt index 861aba07..f9c34dd5 100644 --- a/feature/post/src/main/java/com/msg/post/PostDetailSettingScreen.kt +++ b/feature/post/src/main/java/com/msg/post/PostDetailSettingScreen.kt @@ -3,7 +3,6 @@ package com.msg.post import androidx.activity.ComponentActivity import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -12,12 +11,9 @@ 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.rememberScrollState import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusManager @@ -60,12 +56,6 @@ internal fun PostDetailSettingScreen( onClickAddButton: () -> Unit, onValueChanged: (index: Int, item: String) -> Unit ) { - val scrollState = rememberScrollState() - val interactionSource = remember { MutableInteractionSource() } - - val addedLinks = links - val count = remember { mutableIntStateOf(links.count()) } - BitgoeulAndroidTheme { colors, typography -> Column( modifier = modifier diff --git a/feature/post/src/main/java/com/msg/post/PostScreen.kt b/feature/post/src/main/java/com/msg/post/PostScreen.kt index 18526f0b..bb6af53f 100644 --- a/feature/post/src/main/java/com/msg/post/PostScreen.kt +++ b/feature/post/src/main/java/com/msg/post/PostScreen.kt @@ -14,9 +14,12 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.msg.common.event.Event @@ -38,22 +41,14 @@ internal fun PostScreenRoute( onAddClicked: () -> Unit ) { val role = viewModel.role - var state = FeedType.EMPLOYMENT + val state = rememberSaveable { mutableStateOf(viewModel.currentFeedType.value) } LaunchedEffect(true, state) { - viewModel.getPostList( - type = state - ) + viewModel.getPostList(type = state.value) getPostList( viewModel = viewModel, - onSuccess = { - viewModel.postList.value = it - }, - onFailure = { - viewModel.postList.value = GetPostListEntity( - posts = emptyList() - ) - } + onSuccess = { viewModel.postList.value = it }, + onFailure = { viewModel.clearPostList() } ) } @@ -70,13 +65,11 @@ internal fun PostScreenRoute( }, data = viewModel.postList.value, onViewChangeClicked = { - viewModel.postList.value = GetPostListEntity( - posts = emptyList() - ) + viewModel.postList.value = GetPostListEntity(posts = emptyList()) viewModel.getPostList(type = it) - state = it + state.value = it }, - feedType = viewModel.currentFeedType.value + viewState = state.value, ) } @@ -106,17 +99,15 @@ internal fun PostScreen( onItemClicked: (UUID) -> Unit, onViewChangeClicked: (type: FeedType) -> Unit, data: GetPostListEntity, - feedType: FeedType = FeedType.EMPLOYMENT + viewState: FeedType, ) { - val roleField = listOf( - Authority.ROLE_ADMIN.toString(), - Authority.ROLE_BBOZZAK.toString(), - Authority.ROLE_PROFESSOR.toString(), - Authority.ROLE_COMPANY_INSTRUCTOR.toString(), - Authority.ROLE_GOVERNMENT.toString() - ) - - var viewState: FeedType = feedType + val roleField = setOf( + Authority.ROLE_ADMIN, + Authority.ROLE_BBOZZAK, + Authority.ROLE_PROFESSOR, + Authority.ROLE_COMPANY_INSTRUCTOR, + Authority.ROLE_GOVERNMENT + ).map { it.toString() } BitgoeulAndroidTheme { colors, typography -> Column( @@ -135,12 +126,11 @@ internal fun PostScreen( style = typography.titleMedium, color = colors.BLACK ) - Spacer(Modifier.weight(1f)) + Spacer(modifier.weight(1f)) IconButton( onClick = { - viewState = - if (viewState == FeedType.EMPLOYMENT) FeedType.NOTICE else FeedType.EMPLOYMENT - onViewChangeClicked(viewState) + val newViewState = if (viewState == FeedType.EMPLOYMENT) FeedType.NOTICE else FeedType.EMPLOYMENT + onViewChangeClicked(newViewState) }, content = { when (viewState) { @@ -153,7 +143,7 @@ internal fun PostScreen( onClick = {}, content = { HelpIcon() } ) - if (roleField.contains(role)) { + if (role in roleField) { IconButton( modifier = modifier.padding(end = 28.dp), onClick = { onAddClicked(viewState) }, @@ -170,4 +160,17 @@ internal fun PostScreen( ) } } +} + +@Preview +@Composable +private fun postScreenPreview() { + PostScreen( + role = "", + onAddClicked = {}, + onItemClicked = {}, + onViewChangeClicked = {}, + data = GetPostListEntity(posts = emptyList()), + viewState = FeedType.EMPLOYMENT + ) } \ No newline at end of file diff --git a/feature/post/src/main/java/com/msg/post/component/AddLinkSection.kt b/feature/post/src/main/java/com/msg/post/component/AddLinkSection.kt index e1bfafcc..a3738b41 100644 --- a/feature/post/src/main/java/com/msg/post/component/AddLinkSection.kt +++ b/feature/post/src/main/java/com/msg/post/component/AddLinkSection.kt @@ -23,7 +23,7 @@ import com.msg.design_system.theme.BitgoeulAndroidTheme @Composable internal fun AddLinkSection( - modifier: Modifier, + modifier: Modifier = Modifier, links: List, onClickAddButton: () -> Unit, onValueChanged: (index: Int, item: String) -> Unit diff --git a/feature/post/src/main/java/com/msg/post/viewmodel/PostViewModel.kt b/feature/post/src/main/java/com/msg/post/viewmodel/PostViewModel.kt index a1e296b6..c777170a 100644 --- a/feature/post/src/main/java/com/msg/post/viewmodel/PostViewModel.kt +++ b/feature/post/src/main/java/com/msg/post/viewmodel/PostViewModel.kt @@ -2,6 +2,7 @@ package com.msg.post.viewmodel import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.msg.common.errorhandling.errorHandling @@ -28,9 +29,14 @@ class PostViewModel @Inject constructor( private val getDetailPostUseCase: GetDetailPostUseCase, private val getPostListUseCase: GetPostListUseCase, private val sendPostUseCase: SendPostUseCase, - private val getAuthorityUseCase: GetAuthorityUseCase - + private val getAuthorityUseCase: GetAuthorityUseCase, + private val savedStateHandle: SavedStateHandle ) : ViewModel() { + companion object { + private const val TITLE = "title" + private const val CONTENT = "content" + } + val role = getRole().toString() private val _deletePostResponse = MutableStateFlow>(Event.Loading) @@ -61,9 +67,7 @@ class PostViewModel @Inject constructor( private set var postList = mutableStateOf( - GetPostListEntity( - posts = listOf() - ) + GetPostListEntity(posts = listOf()) ) private set @@ -73,11 +77,9 @@ class PostViewModel @Inject constructor( var currentFeedType = mutableStateOf(FeedType.EMPLOYMENT) private set - var savedTitle = mutableStateOf("") - private set + internal var title = savedStateHandle.getStateFlow(key = TITLE, initialValue = "") - var savedContent = mutableStateOf("") - private set + internal var content = savedStateHandle.getStateFlow(key = CONTENT, initialValue = "") var selectedId = mutableStateOf(UUID.randomUUID()) private set @@ -192,8 +194,8 @@ class PostViewModel @Inject constructor( } internal fun getFilledEditPage() { - savedTitle.value = detailPost.value.title - savedContent.value = detailPost.value.content + onTitleChange(detailPost.value.title) + onContentChange(detailPost.value.content) links.addAll(detailPost.value.links) isEditPage.value = true } @@ -201,4 +203,11 @@ class PostViewModel @Inject constructor( private fun getRole() = viewModelScope.launch { getAuthorityUseCase() } + + internal fun clearPostList() { postList.value = GetPostListEntity(posts = emptyList()) } + + internal fun onTitleChange(value: String) { savedStateHandle[TITLE] = value } + + internal fun onContentChange(value: String) { savedStateHandle[CONTENT] = value } + } \ No newline at end of file