From d6d64a72331fa09daaba35ac23d1357bb7b6b254 Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Tue, 28 Jan 2025 18:48:31 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[PC-434]=20value=20talk=20screen=20ui=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/component/TextFields.kt | 73 +++- .../designsystem/foundation/Typography.kt | 5 + .../src/main/res/drawable/ic_info.xml | 23 ++ .../puzzle/domain/model/matching/ValueTalk.kt | 2 + .../main/java/com/puzzle/navigation/Route.kt | 5 +- .../profile/graph/main/MainProfileScreen.kt | 3 +- .../graph/main/MainProfileViewModel.kt | 9 + .../graph/main/contract/MainProfileIntent.kt | 1 + .../graph/valuepick/ValuePickScreen.kt | 43 +++ .../graph/valuepick/ValuePickViewModel.kt | 56 +++ .../valuepick/contract/ValuePickIntent.kt | 7 + .../valuepick/contract/ValuePickSideEffect.kt | 7 + .../valuepick/contract/ValuePickState.kt | 7 + .../graph/valuetalk/ValueTalkScreen.kt | 354 ++++++++++++++++++ .../graph/valuetalk/ValueTalkViewModel.kt | 56 +++ .../valuetalk/contract/ValueTalkIntent.kt | 7 + .../valuetalk/contract/ValueTalkSideEffect.kt | 7 + .../valuetalk/contract/ValueTalkState.kt | 36 ++ .../profile/navigation/ProfileNavigation.kt | 9 +- .../presentation/di/ViewModelsModule.kt | 12 + .../navigation/TopLevelDestinvation.kt | 2 +- .../java/com/puzzle/presentation/ui/App.kt | 7 +- 22 files changed, 707 insertions(+), 24 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/ic_info.xml create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickScreen.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickViewModel.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickIntent.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickSideEffect.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickState.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkSideEffect.kt create mode 100644 feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt index ffb7fd52..8bce80f0 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt @@ -136,7 +136,7 @@ fun PieceTextInputLong( onValueChange = { input -> limit?.let { if (input.length <= limit) onValueChange(input) } ?: onValueChange(input) }, - singleLine = true, + singleLine = false, readOnly = readOnly, keyboardOptions = KeyboardOptions( keyboardType = keyboardType, @@ -155,19 +155,22 @@ fun PieceTextInputLong( textStyle = PieceTheme.typography.bodyMM, cursorBrush = SolidColor(PieceTheme.colors.primaryDefault), decorationBox = { innerTextField -> + val isTextCountVisible: Boolean = limit != null && (isFocused || value.isNotEmpty()) Box { - if (value.isEmpty()) { - Text( - text = hint, - style = PieceTheme.typography.bodyMM, - color = PieceTheme.colors.dark3, - modifier = Modifier.align(Alignment.TopStart) - ) - } + Box(modifier = Modifier.padding(bottom = if (isTextCountVisible) 36.dp else 0.dp)) { + if (value.isEmpty()) { + Text( + text = hint, + style = PieceTheme.typography.bodyMM, + color = PieceTheme.colors.dark3, + modifier = Modifier.align(Alignment.TopStart) + ) + } - innerTextField() + innerTextField() + } - if (limit != null && isFocused && value.isNotEmpty()) { + if (isTextCountVisible) { Text( text = buildAnnotatedString { withStyle(style = SpanStyle(color = PieceTheme.colors.primaryDefault)) { @@ -176,7 +179,9 @@ fun PieceTextInputLong( append("/${limit}") }, style = PieceTheme.typography.bodySM, - modifier = Modifier.align(Alignment.BottomEnd), + modifier = Modifier + .align(Alignment.BottomEnd) + .height(20.dp), ) } } @@ -197,13 +202,17 @@ fun PieceTextInputLong( fun PieceTextInputAI( value: String, onValueChange: (String) -> Unit, + onSaveClick: (String) -> Unit, modifier: Modifier = Modifier, throttleTime: Long = 2000L, + readOnly: Boolean = true, onDone: () -> Unit = {}, ) { val keyboardController = LocalSoftwareKeyboardController.current var lastDoneTime by remember { mutableLongStateOf(0L) } var isFocused by remember { mutableStateOf(false) } + var isReadOnly: Boolean by remember { mutableStateOf(readOnly) } + var isLoading: Boolean by remember { mutableStateOf(value.isBlank()) } BasicTextField( value = value, @@ -222,9 +231,10 @@ fun PieceTextInputAI( ), textStyle = PieceTheme.typography.bodyMM, cursorBrush = SolidColor(PieceTheme.colors.primaryDefault), + readOnly = isReadOnly, decorationBox = { innerTextField -> Box { - if (value.isEmpty()) { + if (isLoading) { Text( text = "작성해주신 내용을 AI가 요약하고 있어요", style = PieceTheme.typography.bodyMM, @@ -235,16 +245,31 @@ fun PieceTextInputAI( innerTextField() - val imageRes = if (value.isEmpty()) R.drawable.ic_textinput_3dots - else if (isFocused) R.drawable.ic_textinput_check - else R.drawable.ic_textinput_pencil + val imageRes = if (isLoading) { + R.drawable.ic_textinput_3dots + } else { + if (isReadOnly) { + R.drawable.ic_textinput_pencil + } else { + R.drawable.ic_textinput_check + } + } Image( painter = painterResource(imageRes), contentDescription = null, modifier = Modifier .size(24.dp) - .align(Alignment.CenterEnd), + .align(Alignment.CenterEnd) + .clickable { + if (isReadOnly) { + isReadOnly = false + } else { + isLoading = true + isReadOnly = true + onSaveClick(value) + } + }, ) } }, @@ -329,6 +354,18 @@ private fun PreviewPieceTextInputAI() { PieceTextInputAI( value = "", onValueChange = {}, + onSaveClick = {}, + readOnly = true, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + + PieceTextInputAI( + value = "Label", + onValueChange = { }, + onSaveClick = { }, + readOnly = true, modifier = Modifier .fillMaxWidth() .padding(16.dp), @@ -337,6 +374,8 @@ private fun PreviewPieceTextInputAI() { PieceTextInputAI( value = "Label", onValueChange = { }, + onSaveClick = { }, + readOnly = false, modifier = Modifier .fillMaxWidth() .padding(16.dp), diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt index ff976358..a46d9ea6 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/foundation/Typography.kt @@ -88,6 +88,11 @@ data class PieceTypography( fontSize = 14.sp, lineHeight = 20.sp, ), + val bodySR: TextStyle = TextStyle( + fontFamily = PretendardRegular, + fontSize = 14.sp, + lineHeight = 20.sp, + ), val captionM: TextStyle = TextStyle( fontFamily = PretendardMedium, fontSize = 12.sp, diff --git a/core/designsystem/src/main/res/drawable/ic_info.xml b/core/designsystem/src/main/res/drawable/ic_info.xml new file mode 100644 index 00000000..5189d642 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_info.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt b/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt index 2bba75c5..3ae9883b 100644 --- a/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt +++ b/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt @@ -4,4 +4,6 @@ data class ValueTalk( val label: String = "", val title: String = "", val content: String = "", + val aiSummary: String = "", + val helpMessage: String = "", ) \ No newline at end of file diff --git a/core/navigation/src/main/java/com/puzzle/navigation/Route.kt b/core/navigation/src/main/java/com/puzzle/navigation/Route.kt index 57fb5b90..e2e8eb86 100644 --- a/core/navigation/src/main/java/com/puzzle/navigation/Route.kt +++ b/core/navigation/src/main/java/com/puzzle/navigation/Route.kt @@ -48,5 +48,8 @@ sealed class ProfileGraphDest : Route { data object RegisterProfileRoute : Route @Serializable - data object ProfileRoute : Route + data object MainProfileRoute : Route + + @Serializable + data object ValueTalkProfileRoute : Route } diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileScreen.kt index 4845854f..d85a14b4 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileScreen.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileScreen.kt @@ -36,6 +36,7 @@ import com.puzzle.common.ui.repeatOnStarted import com.puzzle.designsystem.R import com.puzzle.designsystem.component.PieceMainTopBar import com.puzzle.designsystem.foundation.PieceTheme +import com.puzzle.profile.graph.main.contract.MainProfileIntent import com.puzzle.profile.graph.main.contract.MainProfileSideEffect import com.puzzle.profile.graph.main.contract.MainProfileState @@ -60,7 +61,7 @@ internal fun MainProfileRoute( MainProfileScreen( state = state, onMyProfileClick = {}, - onValueTalkClick = {}, + onValueTalkClick = { viewModel.onIntent(MainProfileIntent.OnValueTalkClick) }, onValuePickClick = {}, ) } diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileViewModel.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileViewModel.kt index 53aea0a9..e261edbf 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileViewModel.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/main/MainProfileViewModel.kt @@ -5,7 +5,9 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.hilt.AssistedViewModelFactory import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory import com.puzzle.domain.model.error.ErrorHelper +import com.puzzle.navigation.NavigationEvent import com.puzzle.navigation.NavigationHelper +import com.puzzle.navigation.ProfileGraphDest import com.puzzle.profile.graph.main.contract.MainProfileIntent import com.puzzle.profile.graph.main.contract.MainProfileSideEffect import com.puzzle.profile.graph.main.contract.MainProfileState @@ -42,9 +44,16 @@ class MainProfileViewModel @AssistedInject constructor( private suspend fun processIntent(intent: MainProfileIntent) { when (intent) { is MainProfileIntent.Navigate -> _sideEffects.send(MainProfileSideEffect.Navigate(intent.navigationEvent)) + MainProfileIntent.OnValueTalkClick -> moveToValueTalkScreen() } } + private suspend fun moveToValueTalkScreen() { + _sideEffects.send( + MainProfileSideEffect.Navigate(NavigationEvent.NavigateTo(ProfileGraphDest.ValueTalkProfileRoute)) + ) + } + @AssistedFactory interface Factory : AssistedViewModelFactory { override fun create(state: MainProfileState): MainProfileViewModel diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/main/contract/MainProfileIntent.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/main/contract/MainProfileIntent.kt index ec881372..2b64b0b0 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/main/contract/MainProfileIntent.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/main/contract/MainProfileIntent.kt @@ -4,4 +4,5 @@ import com.puzzle.navigation.NavigationEvent sealed class MainProfileIntent { data class Navigate(val navigationEvent: NavigationEvent) : MainProfileIntent() + data object OnValueTalkClick : MainProfileIntent() } \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickScreen.kt new file mode 100644 index 00000000..b02617c6 --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickScreen.kt @@ -0,0 +1,43 @@ +package com.puzzle.profile.graph.valuepick + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.LocalLifecycleOwner +import com.airbnb.mvrx.compose.collectAsState +import com.airbnb.mvrx.compose.mavericksViewModel +import com.puzzle.common.ui.repeatOnStarted +import com.puzzle.profile.graph.valuepick.contract.ValuePickSideEffect +import com.puzzle.profile.graph.valuepick.contract.ValuePickState + +@Composable +internal fun ValuePickRoute( + viewModel: ValuePickViewModel = mavericksViewModel() +) { + val state by viewModel.collectAsState() + val lifecycleOwner = LocalLifecycleOwner.current + + LaunchedEffect(viewModel) { + lifecycleOwner.repeatOnStarted { + viewModel.sideEffects.collect { sideEffect -> + when (sideEffect) { + is ValuePickSideEffect.Navigate -> + viewModel.navigationHelper.navigate(sideEffect.navigationEvent) + } + } + } + } + + ValuePickScreen( + state = state, + ) +} + +@Composable +private fun ValuePickScreen( + state: ValuePickState, + modifier: Modifier = Modifier, +) { + +} \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickViewModel.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickViewModel.kt new file mode 100644 index 00000000..65c12fc2 --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/ValuePickViewModel.kt @@ -0,0 +1,56 @@ +package com.puzzle.profile.graph.valuepick + +import com.airbnb.mvrx.MavericksViewModel +import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.hilt.AssistedViewModelFactory +import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory +import com.puzzle.domain.model.error.ErrorHelper +import com.puzzle.navigation.NavigationHelper +import com.puzzle.profile.graph.valuepick.contract.ValuePickIntent +import com.puzzle.profile.graph.valuepick.contract.ValuePickSideEffect +import com.puzzle.profile.graph.valuepick.contract.ValuePickState +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.BUFFERED +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +class ValuePickViewModel @AssistedInject constructor( + @Assisted initialState: ValuePickState, + internal val navigationHelper: NavigationHelper, + private val errorHelper: ErrorHelper, +) : MavericksViewModel(initialState) { + + private val intents = Channel(BUFFERED) + private val _sideEffects = Channel(BUFFERED) + val sideEffects = _sideEffects.receiveAsFlow() + + init { + intents.receiveAsFlow() + .onEach(::processIntent) + .launchIn(viewModelScope) + } + + internal fun onIntent(intent: ValuePickIntent) = viewModelScope.launch { + intents.send(intent) + } + + private suspend fun processIntent(intent: ValuePickIntent) { + when (intent) { + is ValuePickIntent.Navigate -> _sideEffects.send(ValuePickSideEffect.Navigate(intent.navigationEvent)) + } + } + + @AssistedFactory + interface Factory : AssistedViewModelFactory { + override fun create(state: ValuePickState): ValuePickViewModel + } + + companion object : + MavericksViewModelFactory by hiltMavericksViewModelFactory() + +} \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickIntent.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickIntent.kt new file mode 100644 index 00000000..b9f7fae1 --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickIntent.kt @@ -0,0 +1,7 @@ +package com.puzzle.profile.graph.valuepick.contract + +import com.puzzle.navigation.NavigationEvent + +sealed class ValuePickIntent { + data class Navigate(val navigationEvent: NavigationEvent) : ValuePickIntent() +} \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickSideEffect.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickSideEffect.kt new file mode 100644 index 00000000..8e34c238 --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickSideEffect.kt @@ -0,0 +1,7 @@ +package com.puzzle.profile.graph.valuepick.contract + +import com.puzzle.navigation.NavigationEvent + +sealed class ValuePickSideEffect { + data class Navigate(val navigationEvent: NavigationEvent) : ValuePickSideEffect() +} diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickState.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickState.kt new file mode 100644 index 00000000..65ce9aa3 --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuepick/contract/ValuePickState.kt @@ -0,0 +1,7 @@ +package com.puzzle.profile.graph.valuepick.contract + +import com.airbnb.mvrx.MavericksState + +data class ValuePickState( + val a: String = "" +) : MavericksState \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt new file mode 100644 index 00000000..96d149a4 --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt @@ -0,0 +1,354 @@ +package com.puzzle.profile.graph.valuetalk + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.draw.clip +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LocalLifecycleOwner +import com.airbnb.mvrx.compose.collectAsState +import com.airbnb.mvrx.compose.mavericksViewModel +import com.puzzle.common.ui.repeatOnStarted +import com.puzzle.designsystem.R +import com.puzzle.designsystem.component.PieceSubTopBar +import com.puzzle.designsystem.component.PieceTextInputAI +import com.puzzle.designsystem.component.PieceTextInputLong +import com.puzzle.designsystem.foundation.PieceTheme +import com.puzzle.domain.model.matching.ValueTalk +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkSideEffect +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState.ScreenState + +@Composable +internal fun ValueTalkRoute( + viewModel: ValueTalkViewModel = mavericksViewModel(), +) { + val state by viewModel.collectAsState() + val lifecycleOwner = LocalLifecycleOwner.current + + LaunchedEffect(viewModel) { + lifecycleOwner.repeatOnStarted { + viewModel.sideEffects.collect { sideEffect -> + when (sideEffect) { + is ValueTalkSideEffect.Navigate -> + viewModel.navigationHelper.navigate(sideEffect.navigationEvent) + } + } + } + } + + ValueTalkScreen( + state = state, + onBackClick = {}, + onSaveClick = {}, + onAiSummarySaveClick = {}, + ) +} + +@Composable +private fun ValueTalkScreen( + state: ValueTalkState, + onSaveClick: (List) -> Unit, + onBackClick: () -> Unit, + onAiSummarySaveClick: (ValueTalk) -> Unit, + modifier: Modifier = Modifier, +) { + var screenState: ScreenState by remember { mutableStateOf(ScreenState.SAVED) } + var valueTalks: List by remember { mutableStateOf(state.valueTalks) } + var isContentEdited: Boolean by remember { mutableStateOf(false) } + + BackHandler { + if (screenState == ScreenState.EDITING) { + // TODO : 데이터 초기화 + screenState = ScreenState.SAVED + } else { + onBackClick() + } + } + + Column( + modifier = modifier + .fillMaxSize() + .background(PieceTheme.colors.white), + ) { + PieceSubTopBar( + title = when (screenState) { + ScreenState.EDITING -> "가치관 Talk 수정" + ScreenState.SAVED -> "가치관 Talk" + }, + onNavigationClick = onBackClick, + rightComponent = { + when (screenState) { + ScreenState.EDITING -> { + Text( + text = "저장", + style = PieceTheme.typography.bodyMM, + color = if (isContentEdited) { + PieceTheme.colors.primaryDefault + } else { + PieceTheme.colors.dark3 + }, + modifier = Modifier.clickable { + if (isContentEdited) { + valueTalks = valueTalks.map { it.copy(aiSummary = "") } + onSaveClick(valueTalks) + isContentEdited = false + } + screenState = ScreenState.SAVED + }, + ) + } + + ScreenState.SAVED -> { + Text( + text = "수정", + style = PieceTheme.typography.bodyMM, + color = PieceTheme.colors.primaryDefault, + modifier = Modifier.clickable { + screenState = ScreenState.EDITING + }, + ) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 20.dp, + vertical = 14.dp, + ), + ) + + ValueTalkCards( + valueTalks = valueTalks, + screenState = screenState, + onContentChange = { editedValueTalk -> + valueTalks = valueTalks.map { valueTalk -> + if (valueTalk.label == editedValueTalk.label) { + valueTalk.copy(content = editedValueTalk.content) + } else { + valueTalk + } + } + isContentEdited = valueTalks != state.valueTalks + }, + onAiSummarySaveClick = onAiSummarySaveClick, + ) + } +} + +@Composable +private fun ValueTalkCards( + valueTalks: List, + screenState: ScreenState, + onContentChange: (ValueTalk) -> Unit, + onAiSummarySaveClick: (ValueTalk) -> Unit, + modifier: Modifier = Modifier, +) { + LazyColumn(modifier = modifier.fillMaxSize()) { + itemsIndexed(valueTalks) { idx, item -> + ValueTalkCard( + item = item, + screenState = screenState, + onContentChange = onContentChange, + onAiSummarySaveClick = onAiSummarySaveClick, + modifier = Modifier.padding( + horizontal = 20.dp, + vertical = 24.dp, + ) + ) + } + } +} + +@Composable +private fun ValueTalkCard( + item: ValueTalk, + screenState: ScreenState, + onContentChange: (ValueTalk) -> Unit, + onAiSummarySaveClick: (ValueTalk) -> Unit, + modifier: Modifier = Modifier, +) { + var editableContent: String by remember { mutableStateOf(item.content) } + + Column(modifier = modifier) { + Text( + text = item.label, + style = PieceTheme.typography.bodySSB, + color = PieceTheme.colors.primaryDefault, + modifier = Modifier.padding(bottom = 6.dp), + ) + + Text( + text = item.title, + style = PieceTheme.typography.headingMSB, + color = PieceTheme.colors.dark1, + modifier = Modifier.padding(bottom = 20.dp), + ) + + PieceTextInputLong( + value = editableContent, + onValueChange = { + editableContent = it + onContentChange(item.copy(content = it)) + }, + limit = 300, + readOnly = when (screenState) { + ScreenState.EDITING -> false + ScreenState.SAVED -> true + }, + ) + + when (screenState) { + ScreenState.EDITING -> + HelpMessageContent(helpMessage = item.helpMessage) + + ScreenState.SAVED -> + AiSummaryContent( + item = item, + onAiSummarySaveClick = onAiSummarySaveClick, + ) + } + } +} + +@Composable +private fun AiSummaryContent( + item: ValueTalk, + onAiSummarySaveClick: (ValueTalk) -> Unit +) { + var editableAiSummary: String by remember { mutableStateOf(item.aiSummary) } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + ) { + Text( + text = "AI 요약", + style = PieceTheme.typography.bodySSB, + color = PieceTheme.colors.primaryDefault, + ) + + Image( + painter = painterResource(R.drawable.ic_info), + contentDescription = "정보", + colorFilter = ColorFilter.tint(PieceTheme.colors.dark3), + modifier = Modifier + .size(20.dp) + .padding(start = 4.dp), + ) + } + + PieceTextInputAI( + value = editableAiSummary, + onValueChange = { + editableAiSummary = it + }, + onSaveClick = { + onAiSummarySaveClick( + item.copy( + content = item.content, + aiSummary = it, + ) + ) + }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp), + ) +} + +@Composable +private fun HelpMessageContent( + helpMessage: String, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp) + .height(26.dp), + ) { + Text( + text = "도움말", + style = PieceTheme.typography.bodySR, + color = PieceTheme.colors.subDefault, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(PieceTheme.colors.subLight) + .padding( + vertical = 4.dp, + horizontal = 6.dp, + ), + ) + + Text( + text = helpMessage, + style = PieceTheme.typography.bodySR, + color = PieceTheme.colors.dark2, + modifier = Modifier.padding(start = 8.dp), + ) + } +} + +@Preview +@Composable +private fun ValueTalkPreview() { + PieceTheme { + ValueTalkScreen( + state = ValueTalkState( + valueTalks = listOf( + ValueTalk( + label = "연애관", + title = "어떠한 사람과 어떠한 연애를 하고 싶은지 들려주세요", + content = "저는 연애에서 서로의 존중과 신뢰가 가장 중요하다고 생각합니다. 진정한 소통을 통해 서로의 감정을 이해하고, 함께 성장할 수 있는 관계를 원합니다. 일상 속 작은 것에도 감사하며, 서로의 꿈과 목표를 지지하고 응원하는 파트너가 되고 싶습니다. 또한, 유머와 즐거움을 잃지 않으며, 함께하는 순간들을 소중히 여기고 싶습니다. 사랑은 서로를 더 나은 사람으로 만들어주는 힘이 있다고 믿습니다. 서로에게 긍정적인 영향을 주며 행복한 시간을 함께하고 싶습니다!", + aiSummary = "신뢰하며, 함께 성장하고 싶어요.", + helpMessage = "어떤 데이트를 즐기고 싶나요?", + ), + ValueTalk( + label = "관심사와 취향", + title = "무엇을 할 때 가장 행복한가요?\n요즘 어떠한 것에 관심을 두고 있나요?", + content = "저는 다양한 취미와 관심사를 가진 사람입니다. 음악을 사랑하여 콘서트에 자주 가고, 특히 인디 음악과 재즈에 매력을 느낍니다. 요리도 좋아해 새로운 레시피에 도전하는 것을 즐깁니다. 여행을 통해 새로운 맛과 문화를 경험하는 것도 큰 기쁨입니다. 또, 자연을 사랑해서 주말마다 하이킹이나 캠핑을 자주 떠납니다. 영화와 책도 좋아해, 좋은 이야기와 감동을 나누는 시간을 소중히 여깁니다. 서로의 취향을 공유하며 즐거운 시간을 보낼 수 있기를 기대합니다!", + aiSummary = "음악, 요리, 하이킹을 좋아해요.", + helpMessage = "최근에 가장 행복했던 경험을 공유해 주세요", + ), + ValueTalk( + label = "꿈과 목표", + title = "어떤 일을 하며 무엇을 목표로 살아가나요?\n인생에서 이루고 싶은 꿈은 무엇인가요?", + content = "안녕하세요! 저는 삶의 매 순간을 소중히 여기며, 꿈과 목표를 이루기 위해 노력하는 사람입니다. 제 가장 큰 꿈은 여행을 통해 다양한 문화와 사람들을 경험하고, 그 과정에서 얻은 지혜를 나누는 것입니다. 또한, LGBTQ+ 커뮤니티를 위한 긍정적인 변화를 이끌어내고 싶습니다. 내가 이루고자 하는 목표는 나 자신을 발전시키고, 사랑하는 사람들과 함께 행복한 순간들을 만드는 것입니다. 서로의 꿈을 지지하며 함께 성장할 수 있는 관계를 기대합니다!", + aiSummary = "여행하며 LGBTQ+ 변화를 원해요.", + helpMessage = "당신의 직업은 무엇인가요?", + ) + ) + ), + onBackClick = {}, + onSaveClick = {}, + onAiSummarySaveClick = {}, + ) + } +} \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt new file mode 100644 index 00000000..95f281ad --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt @@ -0,0 +1,56 @@ +package com.puzzle.profile.graph.valuetalk + +import com.airbnb.mvrx.MavericksViewModel +import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.hilt.AssistedViewModelFactory +import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory +import com.puzzle.domain.model.error.ErrorHelper +import com.puzzle.navigation.NavigationHelper +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkIntent +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkSideEffect +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.BUFFERED +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +class ValueTalkViewModel @AssistedInject constructor( + @Assisted initialState: ValueTalkState, + internal val navigationHelper: NavigationHelper, + private val errorHelper: ErrorHelper, +) : MavericksViewModel(initialState) { + + private val intents = Channel(BUFFERED) + private val _sideEffects = Channel(BUFFERED) + val sideEffects = _sideEffects.receiveAsFlow() + + init { + intents.receiveAsFlow() + .onEach(::processIntent) + .launchIn(viewModelScope) + } + + internal fun onIntent(intent: ValueTalkIntent) = viewModelScope.launch { + intents.send(intent) + } + + private suspend fun processIntent(intent: ValueTalkIntent) { + when (intent) { + is ValueTalkIntent.Navigate -> _sideEffects.send(ValueTalkSideEffect.Navigate(intent.navigationEvent)) + } + } + + @AssistedFactory + interface Factory : AssistedViewModelFactory { + override fun create(state: ValueTalkState): ValueTalkViewModel + } + + companion object : + MavericksViewModelFactory by hiltMavericksViewModelFactory() + +} \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt new file mode 100644 index 00000000..eb84a8fa --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt @@ -0,0 +1,7 @@ +package com.puzzle.profile.graph.valuetalk.contract + +import com.puzzle.navigation.NavigationEvent + +sealed class ValueTalkIntent { + data class Navigate(val navigationEvent: NavigationEvent) : ValueTalkIntent() +} \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkSideEffect.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkSideEffect.kt new file mode 100644 index 00000000..7ed5222c --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkSideEffect.kt @@ -0,0 +1,7 @@ +package com.puzzle.profile.graph.valuetalk.contract + +import com.puzzle.navigation.NavigationEvent + +sealed class ValueTalkSideEffect { + data class Navigate(val navigationEvent: NavigationEvent) : ValueTalkSideEffect() +} diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt new file mode 100644 index 00000000..936174d4 --- /dev/null +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt @@ -0,0 +1,36 @@ +package com.puzzle.profile.graph.valuetalk.contract + +import com.airbnb.mvrx.MavericksState +import com.puzzle.domain.model.matching.ValueTalk + +data class ValueTalkState( + val valueTalks: List = listOf( + ValueTalk( + label = "연애관", + title = "어떠한 사람과 어떠한 연애를 하고 싶은지 들려주세요", + content = "저는 연애에서 서로의 존중과 신뢰가 가장 중요하다고 생각합니다. 진정한 소통을 통해 서로의 감정을 이해하고, 함께 성장할 수 있는 관계를 원합니다. 일상 속 작은 것에도 감사하며, 서로의 꿈과 목표를 지지하고 응원하는 파트너가 되고 싶습니다. 또한, 유머와 즐거움을 잃지 않으며, 함께하는 순간들을 소중히 여기고 싶습니다. 사랑은 서로를 더 나은 사람으로 만들어주는 힘이 있다고 믿습니다. 서로에게 긍정적인 영향을 주며 행복한 시간을 함께하고 싶습니다!", + aiSummary = "신뢰하며, 함께 성장하고 싶어요.", + helpMessage = "어떤 데이트를 즐기고 싶나요?", + ), + ValueTalk( + label = "관심사와 취향", + title = "무엇을 할 때 가장 행복한가요?\n요즘 어떠한 것에 관심을 두고 있나요?", + content = "저는 다양한 취미와 관심사를 가진 사람입니다. 음악을 사랑하여 콘서트에 자주 가고, 특히 인디 음악과 재즈에 매력을 느낍니다. 요리도 좋아해 새로운 레시피에 도전하는 것을 즐깁니다. 여행을 통해 새로운 맛과 문화를 경험하는 것도 큰 기쁨입니다. 또, 자연을 사랑해서 주말마다 하이킹이나 캠핑을 자주 떠납니다. 영화와 책도 좋아해, 좋은 이야기와 감동을 나누는 시간을 소중히 여깁니다. 서로의 취향을 공유하며 즐거운 시간을 보낼 수 있기를 기대합니다!", + aiSummary = "음악, 요리, 하이킹을 좋아해요.", + helpMessage = "최근에 가장 행복했던 경험을 공유해 주세요", + ), + ValueTalk( + label = "꿈과 목표", + title = "어떤 일을 하며 무엇을 목표로 살아가나요?\n인생에서 이루고 싶은 꿈은 무엇인가요?", + content = "안녕하세요! 저는 삶의 매 순간을 소중히 여기며, 꿈과 목표를 이루기 위해 노력하는 사람입니다. 제 가장 큰 꿈은 여행을 통해 다양한 문화와 사람들을 경험하고, 그 과정에서 얻은 지혜를 나누는 것입니다. 또한, LGBTQ+ 커뮤니티를 위한 긍정적인 변화를 이끌어내고 싶습니다. 내가 이루고자 하는 목표는 나 자신을 발전시키고, 사랑하는 사람들과 함께 행복한 순간들을 만드는 것입니다. 서로의 꿈을 지지하며 함께 성장할 수 있는 관계를 기대합니다!", + aiSummary = "여행하며 LGBTQ+ 변화를 원해요.", + helpMessage = "당신의 직업은 무엇인가요?", + ) + ) +) : MavericksState { + + enum class ScreenState { + EDITING, + SAVED + } +} \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/navigation/ProfileNavigation.kt b/feature/profile/src/main/java/com/puzzle/profile/navigation/ProfileNavigation.kt index 74122b73..0683d1c4 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/navigation/ProfileNavigation.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/navigation/ProfileNavigation.kt @@ -7,13 +7,18 @@ import com.puzzle.navigation.ProfileGraph import com.puzzle.navigation.ProfileGraphDest import com.puzzle.profile.graph.main.MainProfileRoute import com.puzzle.profile.graph.register.RegisterProfileRoute +import com.puzzle.profile.graph.valuetalk.ValueTalkRoute fun NavGraphBuilder.profileNavGraph() { - navigation(startDestination = ProfileGraphDest.ProfileRoute) { - composable { + navigation(startDestination = ProfileGraphDest.MainProfileRoute) { + composable { MainProfileRoute() } + composable { + ValueTalkRoute() + } + composable { RegisterProfileRoute() } diff --git a/presentation/src/main/java/com/puzzle/presentation/di/ViewModelsModule.kt b/presentation/src/main/java/com/puzzle/presentation/di/ViewModelsModule.kt index 1d0d7cf1..a6b13717 100644 --- a/presentation/src/main/java/com/puzzle/presentation/di/ViewModelsModule.kt +++ b/presentation/src/main/java/com/puzzle/presentation/di/ViewModelsModule.kt @@ -10,6 +10,8 @@ import com.puzzle.matching.graph.detail.MatchingDetailViewModel import com.puzzle.matching.graph.main.MatchingViewModel import com.puzzle.profile.graph.main.MainProfileViewModel import com.puzzle.profile.graph.register.RegisterProfileViewModel +import com.puzzle.profile.graph.valuepick.ValuePickViewModel +import com.puzzle.profile.graph.valuetalk.ValueTalkViewModel import com.puzzle.setting.graph.main.SettingViewModel import com.puzzle.setting.graph.withdraw.WithdrawViewModel import dagger.Binds @@ -65,4 +67,14 @@ interface ViewModelsModule { @IntoMap @ViewModelKey(WithdrawViewModel::class) fun settingWithdrawViewModelFactory(factory: WithdrawViewModel.Factory): AssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @ViewModelKey(ValueTalkViewModel::class) + fun valueTalkViewModelFactory(factory: ValueTalkViewModel.Factory): AssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @ViewModelKey(ValuePickViewModel::class) + fun valuePickViewModelFactory(factory: ValuePickViewModel.Factory): AssistedViewModelFactory<*, *> } diff --git a/presentation/src/main/java/com/puzzle/presentation/navigation/TopLevelDestinvation.kt b/presentation/src/main/java/com/puzzle/presentation/navigation/TopLevelDestinvation.kt index 9cc85c28..97669ce0 100644 --- a/presentation/src/main/java/com/puzzle/presentation/navigation/TopLevelDestinvation.kt +++ b/presentation/src/main/java/com/puzzle/presentation/navigation/TopLevelDestinvation.kt @@ -17,7 +17,7 @@ enum class TopLevelDestination( iconDrawableId = R.drawable.ic_profile, contentDescription = "프로필", title = "프로필", - route = ProfileGraphDest.ProfileRoute::class, + route = ProfileGraphDest.MainProfileRoute::class, ), MATCHING( iconDrawableId = R.drawable.ic_profile, diff --git a/presentation/src/main/java/com/puzzle/presentation/ui/App.kt b/presentation/src/main/java/com/puzzle/presentation/ui/App.kt index de3e13b0..738be96d 100644 --- a/presentation/src/main/java/com/puzzle/presentation/ui/App.kt +++ b/presentation/src/main/java/com/puzzle/presentation/ui/App.kt @@ -41,7 +41,7 @@ import com.puzzle.navigation.AuthGraph import com.puzzle.navigation.MatchingGraph import com.puzzle.navigation.MatchingGraphDest.MatchingDetailRoute import com.puzzle.navigation.ProfileGraphDest -import com.puzzle.navigation.ProfileGraphDest.ProfileRoute +import com.puzzle.navigation.ProfileGraphDest.MainProfileRoute import com.puzzle.navigation.Route import com.puzzle.navigation.SettingGraph import com.puzzle.navigation.SettingGraphDest @@ -142,7 +142,10 @@ private fun AppBottomBar( onClick = { when (topLevelRoute) { TopLevelDestination.MATCHING -> navigateToTopLevelDestination(MatchingGraph) - TopLevelDestination.PROFILE -> navigateToTopLevelDestination(ProfileRoute) + TopLevelDestination.PROFILE -> navigateToTopLevelDestination( + MainProfileRoute + ) + TopLevelDestination.SETTING -> navigateToTopLevelDestination(SettingGraph) } }, From 870cd4c77348db213f25ed030c6db7b3d1856d0f Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Tue, 28 Jan 2025 18:56:48 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[PC-434]=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20remember=20=EB=B3=80=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt index 96d149a4..84bd7c74 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt @@ -192,8 +192,6 @@ private fun ValueTalkCard( onAiSummarySaveClick: (ValueTalk) -> Unit, modifier: Modifier = Modifier, ) { - var editableContent: String by remember { mutableStateOf(item.content) } - Column(modifier = modifier) { Text( text = item.label, @@ -210,9 +208,8 @@ private fun ValueTalkCard( ) PieceTextInputLong( - value = editableContent, + value = item.content, onValueChange = { - editableContent = it onContentChange(item.copy(content = it)) }, limit = 300, From 5dc85db695d105ed694d35e9ab36020ffd8ff6c2 Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Wed, 29 Jan 2025 18:46:12 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[PC-434]=20stringResource=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/values/strings.xml | 8 ++++ .../graph/valuetalk/ValueTalkScreen.kt | 43 +++++++++---------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index 93506708..091844ac 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -102,4 +102,12 @@ 꿈과 목표, 관심사와 취향, 연애에 관련된\n내 생각을 확인하고 수정할 수 있습니다. 가치관 Pick 퀴즈를 통해 나의 연애 스타일을 파악해보고\n선택한 답변을 수정할 수 있습니다. + + + 가치관 Talk + 가치관 Talk 수정 + 저장 + 수정 + AI 요약 + 도움말 \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt index 84bd7c74..aebfd139 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LocalLifecycleOwner @@ -96,15 +97,25 @@ private fun ValueTalkScreen( ) { PieceSubTopBar( title = when (screenState) { - ScreenState.EDITING -> "가치관 Talk 수정" - ScreenState.SAVED -> "가치관 Talk" + ScreenState.SAVED -> stringResource(R.string.value_talk_profile_topbar_title) + ScreenState.EDITING -> stringResource(R.string.value_talk_edit_profile_topbar_title) }, onNavigationClick = onBackClick, rightComponent = { when (screenState) { - ScreenState.EDITING -> { + ScreenState.SAVED -> Text( - text = "저장", + text = stringResource(R.string.value_talk_profile_topbar_edit), + style = PieceTheme.typography.bodyMM, + color = PieceTheme.colors.primaryDefault, + modifier = Modifier.clickable { + screenState = ScreenState.EDITING + }, + ) + + ScreenState.EDITING -> + Text( + text = stringResource(R.string.value_talk_profile_topbar_save), style = PieceTheme.typography.bodyMM, color = if (isContentEdited) { PieceTheme.colors.primaryDefault @@ -120,18 +131,6 @@ private fun ValueTalkScreen( screenState = ScreenState.SAVED }, ) - } - - ScreenState.SAVED -> { - Text( - text = "수정", - style = PieceTheme.typography.bodyMM, - color = PieceTheme.colors.primaryDefault, - modifier = Modifier.clickable { - screenState = ScreenState.EDITING - }, - ) - } } }, modifier = Modifier @@ -214,20 +213,20 @@ private fun ValueTalkCard( }, limit = 300, readOnly = when (screenState) { - ScreenState.EDITING -> false ScreenState.SAVED -> true + ScreenState.EDITING -> false }, ) when (screenState) { - ScreenState.EDITING -> - HelpMessageContent(helpMessage = item.helpMessage) - ScreenState.SAVED -> AiSummaryContent( item = item, onAiSummarySaveClick = onAiSummarySaveClick, ) + + ScreenState.EDITING -> + HelpMessageContent(helpMessage = item.helpMessage) } } } @@ -246,7 +245,7 @@ private fun AiSummaryContent( .padding(top = 20.dp), ) { Text( - text = "AI 요약", + text = stringResource(R.string.value_talk_profile_aisummary_title), style = PieceTheme.typography.bodySSB, color = PieceTheme.colors.primaryDefault, ) @@ -292,7 +291,7 @@ private fun HelpMessageContent( .height(26.dp), ) { Text( - text = "도움말", + text = stringResource(R.string.value_talk_profile_helpmessage_title), style = PieceTheme.typography.bodySR, color = PieceTheme.colors.subDefault, modifier = Modifier From ff7029fc887baabc381badbe30cd7257c74cea85 Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Wed, 29 Jan 2025 19:24:12 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[PC-434]=20back=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../puzzle/designsystem/component/TextFields.kt | 1 - .../profile/graph/valuetalk/ValueTalkScreen.kt | 17 ++++++++++++----- .../graph/valuetalk/ValueTalkViewModel.kt | 5 ++++- .../graph/valuetalk/contract/ValueTalkIntent.kt | 4 +--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt index 9a72305d..1cb135bb 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt @@ -258,7 +258,6 @@ fun PieceTextInputAI( if (isReadOnly) { isReadOnly = false } else { - isLoading = true isReadOnly = true onSaveClick(value) } diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt index aebfd139..ea3a53be 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -39,6 +40,7 @@ import com.puzzle.designsystem.component.PieceTextInputAI import com.puzzle.designsystem.component.PieceTextInputLong import com.puzzle.designsystem.foundation.PieceTheme import com.puzzle.domain.model.matching.ValueTalk +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkIntent import com.puzzle.profile.graph.valuetalk.contract.ValueTalkSideEffect import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState.ScreenState @@ -63,7 +65,7 @@ internal fun ValueTalkRoute( ValueTalkScreen( state = state, - onBackClick = {}, + onBackClick = { viewModel.onIntent(ValueTalkIntent.OnBackClick) }, onSaveClick = {}, onAiSummarySaveClick = {}, ) @@ -179,6 +181,14 @@ private fun ValueTalkCards( vertical = 24.dp, ) ) + + if (idx < valueTalks.size - 1) { + HorizontalDivider( + thickness = 12.dp, + color = PieceTheme.colors.light3, + modifier = Modifier.fillMaxWidth(), + ) + } } } } @@ -267,10 +277,7 @@ private fun AiSummaryContent( }, onSaveClick = { onAiSummarySaveClick( - item.copy( - content = item.content, - aiSummary = it, - ) + item.copy(aiSummary = it) ) }, modifier = Modifier diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt index 95f281ad..ef45dd3c 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt @@ -5,6 +5,7 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.hilt.AssistedViewModelFactory import com.airbnb.mvrx.hilt.hiltMavericksViewModelFactory import com.puzzle.domain.model.error.ErrorHelper +import com.puzzle.navigation.NavigationEvent import com.puzzle.navigation.NavigationHelper import com.puzzle.profile.graph.valuetalk.contract.ValueTalkIntent import com.puzzle.profile.graph.valuetalk.contract.ValueTalkSideEffect @@ -41,7 +42,9 @@ class ValueTalkViewModel @AssistedInject constructor( private suspend fun processIntent(intent: ValueTalkIntent) { when (intent) { - is ValueTalkIntent.Navigate -> _sideEffects.send(ValueTalkSideEffect.Navigate(intent.navigationEvent)) + ValueTalkIntent.OnBackClick -> _sideEffects.send( + ValueTalkSideEffect.Navigate(NavigationEvent.NavigateUp) + ) } } diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt index eb84a8fa..4130f71b 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt @@ -1,7 +1,5 @@ package com.puzzle.profile.graph.valuetalk.contract -import com.puzzle.navigation.NavigationEvent - sealed class ValueTalkIntent { - data class Navigate(val navigationEvent: NavigationEvent) : ValueTalkIntent() + data object OnBackClick : ValueTalkIntent() } \ No newline at end of file From 53f18f8c021699bde09fbfab4a52acdab08fd992 Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Wed, 29 Jan 2025 20:08:49 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[PC-434]=20ai=20text=20input=20field=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/component/TextFields.kt | 148 +++++++++++------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt index 1cb135bb..89909272 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt @@ -8,6 +8,7 @@ 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -148,9 +149,9 @@ fun PieceTextInputLong( textStyle = PieceTheme.typography.bodyMM, cursorBrush = SolidColor(PieceTheme.colors.primaryDefault), decorationBox = { innerTextField -> - val isTextCountVisible: Boolean = limit != null && (isFocused || value.isNotEmpty()) + val isCharCountVisible: Boolean = limit != null && !readOnly Box { - Box(modifier = Modifier.padding(bottom = if (isTextCountVisible) 36.dp else 0.dp)) { + Box(modifier = Modifier.padding(bottom = if (isCharCountVisible) 36.dp else 0.dp)) { if (value.isEmpty()) { Text( text = hint, @@ -163,7 +164,7 @@ fun PieceTextInputLong( innerTextField() } - if (isTextCountVisible) { + if (isCharCountVisible) { Text( text = buildAnnotatedString { withStyle(style = SpanStyle(color = PieceTheme.colors.primaryDefault)) { @@ -207,71 +208,98 @@ fun PieceTextInputAI( var isReadOnly: Boolean by remember { mutableStateOf(readOnly) } var isLoading: Boolean by remember { mutableStateOf(value.isBlank()) } - BasicTextField( - value = value, - onValueChange = onValueChange, - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions( - onDone = { - val currentTime = System.currentTimeMillis() - if (currentTime - lastDoneTime >= throttleTime) { - keyboardController?.hide() - onDone() - lastDoneTime = currentTime - } - } - ), - textStyle = PieceTheme.typography.bodyMM, - cursorBrush = SolidColor(PieceTheme.colors.primaryDefault), - readOnly = isReadOnly, - decorationBox = { innerTextField -> - Box { - if (isLoading) { - Text( - text = "작성해주신 내용을 AI가 요약하고 있어요", - style = PieceTheme.typography.bodyMM, - color = PieceTheme.colors.dark3, - modifier = Modifier.align(Alignment.CenterStart) - ) + Column( + modifier = modifier + ) { + BasicTextField( + value = value, + onValueChange = onValueChange, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { + val currentTime = System.currentTimeMillis() + if (currentTime - lastDoneTime >= throttleTime) { + keyboardController?.hide() + onDone() + lastDoneTime = currentTime + } } + ), + textStyle = PieceTheme.typography.bodyMM, + cursorBrush = SolidColor(PieceTheme.colors.primaryDefault), + readOnly = isReadOnly, + decorationBox = { innerTextField -> + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + if (isLoading) { + Text( + text = "작성해주신 내용을 AI가 요약하고 있어요", + style = PieceTheme.typography.bodyMM, + color = PieceTheme.colors.dark3, + ) + } else { + innerTextField() + } - innerTextField() - - val imageRes = if (isLoading) { - R.drawable.ic_textinput_3dots - } else { - if (isReadOnly) { - R.drawable.ic_textinput_pencil + val imageRes = if (isLoading) { + R.drawable.ic_textinput_3dots } else { - R.drawable.ic_textinput_check + if (isReadOnly) { + R.drawable.ic_textinput_pencil + } else { + R.drawable.ic_textinput_check + } } + + Image( + painter = painterResource(imageRes), + contentDescription = null, + modifier = Modifier + .size(24.dp) + .clickable { + if (isLoading) return@clickable + + if (isReadOnly) { + isReadOnly = false + } else { + isReadOnly = true + onSaveClick(value) + } + }, + ) } + }, + modifier = Modifier + .onFocusChanged { focusState -> isFocused = focusState.isFocused } + .height(52.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(PieceTheme.colors.primaryLight) + .padding(horizontal = 16.dp, vertical = 14.dp), + ) - Image( - painter = painterResource(imageRes), - contentDescription = null, - modifier = Modifier - .size(24.dp) - .align(Alignment.CenterEnd) - .clickable { - if (isReadOnly) { - isReadOnly = false - } else { - isReadOnly = true - onSaveClick(value) - } - }, + if (!isReadOnly) { + Row { + Spacer(modifier = Modifier.weight(1f)) + + Text( + text = buildAnnotatedString { + withStyle(style = SpanStyle(color = PieceTheme.colors.primaryDefault)) { + append(value.length.toString()) + } + + append("/20") + }, + style = PieceTheme.typography.bodySR, + color = PieceTheme.colors.dark3, + modifier = Modifier.padding(top = 4.dp) ) } - }, - modifier = modifier - .onFocusChanged { focusState -> isFocused = focusState.isFocused } - .height(52.dp) - .clip(RoundedCornerShape(8.dp)) - .background(PieceTheme.colors.primaryLight) - .padding(horizontal = 16.dp, vertical = 14.dp), - ) + } + } } @Composable From c8e0659f494670a434f1605c867071bd969aaa98 Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Sat, 1 Feb 2025 14:21:21 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[PC-434]=20text=20input=20long=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD=EC=83=89=20=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/puzzle/designsystem/component/TextFields.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt index 89909272..0acf26b2 100644 --- a/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt +++ b/core/designsystem/src/main/java/com/puzzle/designsystem/component/TextFields.kt @@ -184,10 +184,7 @@ fun PieceTextInputLong( .onFocusChanged { focusState -> isFocused = focusState.isFocused } .heightIn(min = 160.dp) .clip(RoundedCornerShape(8.dp)) - .background( - if (readOnly) PieceTheme.colors.light2 - else PieceTheme.colors.light3 - ) + .background(PieceTheme.colors.light3) .padding(horizontal = 16.dp, vertical = 14.dp), ) } From b06e2db0fcf38857b18855438e27bf60ebcb7e0a Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Sat, 1 Feb 2025 17:39:47 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[PC-434]=20bottom=20navi=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0,=20=EB=8F=84=EC=9B=80=EB=A7=90=20list=20=EB=AC=B4?= =?UTF-8?q?=ED=95=9C=20=EB=A1=A4=EB=A7=81,=20=EC=88=98=EC=A0=95=EB=90=9C?= =?UTF-8?q?=20ai=20summary=EB=A7=8C=20=EB=A1=9C=EB=94=A9=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../puzzle/domain/model/matching/ValueTalk.kt | 2 +- .../graph/valuetalk/ValueTalkScreen.kt | 136 +++++++++++++++--- .../valuetalk/contract/ValueTalkState.kt | 24 +++- .../java/com/puzzle/presentation/ui/App.kt | 1 + 4 files changed, 137 insertions(+), 26 deletions(-) diff --git a/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt b/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt index 3ae9883b..1ee0e687 100644 --- a/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt +++ b/core/domain/src/main/java/com/puzzle/domain/model/matching/ValueTalk.kt @@ -5,5 +5,5 @@ data class ValueTalk( val title: String = "", val content: String = "", val aiSummary: String = "", - val helpMessage: String = "", + val helpMessages: List = emptyList(), ) \ No newline at end of file diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt index ea3a53be..72654a9c 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt @@ -1,6 +1,11 @@ package com.puzzle.profile.graph.valuetalk import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -13,7 +18,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -26,6 +33,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -44,6 +52,7 @@ import com.puzzle.profile.graph.valuetalk.contract.ValueTalkIntent import com.puzzle.profile.graph.valuetalk.contract.ValueTalkSideEffect import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState.ScreenState +import kotlinx.coroutines.delay @Composable internal fun ValueTalkRoute( @@ -82,6 +91,7 @@ private fun ValueTalkScreen( var screenState: ScreenState by remember { mutableStateOf(ScreenState.SAVED) } var valueTalks: List by remember { mutableStateOf(state.valueTalks) } var isContentEdited: Boolean by remember { mutableStateOf(false) } + var editedValueTalkLabels: List by remember { mutableStateOf(emptyList()) } BackHandler { if (screenState == ScreenState.EDITING) { @@ -126,9 +136,16 @@ private fun ValueTalkScreen( }, modifier = Modifier.clickable { if (isContentEdited) { - valueTalks = valueTalks.map { it.copy(aiSummary = "") } + valueTalks = valueTalks.map { valueTalk -> + if (editedValueTalkLabels.contains(valueTalk.label)) { + valueTalk.copy(aiSummary = "") + } else { + valueTalk + } + } onSaveClick(valueTalks) isContentEdited = false + editedValueTalkLabels = emptyList() } screenState = ScreenState.SAVED }, @@ -149,11 +166,15 @@ private fun ValueTalkScreen( onContentChange = { editedValueTalk -> valueTalks = valueTalks.map { valueTalk -> if (valueTalk.label == editedValueTalk.label) { + if (!editedValueTalkLabels.contains(valueTalk.label)) { + editedValueTalkLabels = editedValueTalkLabels + valueTalk.label + } valueTalk.copy(content = editedValueTalk.content) } else { valueTalk } } + isContentEdited = valueTalks != state.valueTalks }, onAiSummarySaveClick = onAiSummarySaveClick, @@ -236,7 +257,12 @@ private fun ValueTalkCard( ) ScreenState.EDITING -> - HelpMessageContent(helpMessage = item.helpMessage) + HelpMessageRow( + helpMessages = item.helpMessages, + modifier = Modifier + .fillMaxWidth() + .padding(top = 12.dp) + ) } } } @@ -286,36 +312,84 @@ private fun AiSummaryContent( ) } +const val TEXT_DISPLAY_DURATION = 3000L +const val PAGE_TRANSITION_DURATION = 1000 + @Composable -private fun HelpMessageContent( - helpMessage: String, +fun HelpMessageRow( + helpMessages: List, + modifier: Modifier = Modifier, ) { + // 각 Row의 높이는 고정되어 있으므로 고정값 사용 + val rowHeightDp = 26.dp + val density = LocalDensity.current + val rowHeightPx = with(density) { rowHeightDp.toPx() } + // 총 높이 = rowHeightPx * 메시지 개수 + val totalHeightPx = rowHeightPx * helpMessages.size + + // ScrollState를 이용한 자동 스크롤 + val scrollState = rememberScrollState() + var isHelpMessageVisible by remember { mutableStateOf(true) } + + LaunchedEffect(Unit) { + while (true) { + delay(TEXT_DISPLAY_DURATION) + val target = scrollState.value + rowHeightPx.toInt() + + if (target >= totalHeightPx.toInt() - rowHeightPx.toInt()) { + isHelpMessageVisible = false + delay(1000) + scrollState.scrollTo(0) + isHelpMessageVisible = true + } else { + scrollState.animateScrollTo( + value = target, + animationSpec = tween( + durationMillis = PAGE_TRANSITION_DURATION, + easing = LinearEasing + ), + ) + } + } + } + Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(top = 12.dp) - .height(26.dp), + modifier = modifier, ) { Text( - text = stringResource(R.string.value_talk_profile_helpmessage_title), + text = stringResource(id = R.string.value_talk_profile_helpmessage_title), style = PieceTheme.typography.bodySR, color = PieceTheme.colors.subDefault, modifier = Modifier .clip(RoundedCornerShape(4.dp)) .background(PieceTheme.colors.subLight) - .padding( - vertical = 4.dp, - horizontal = 6.dp, - ), + .padding(vertical = 4.dp, horizontal = 6.dp), ) - Text( - text = helpMessage, - style = PieceTheme.typography.bodySR, - color = PieceTheme.colors.dark2, - modifier = Modifier.padding(start = 8.dp), - ) + AnimatedVisibility( + visible = isHelpMessageVisible, + enter = fadeIn(tween(durationMillis = 500, easing = LinearEasing)), + exit = fadeOut(tween(durationMillis = 500, easing = LinearEasing)), + ) { + Column( + modifier = Modifier + .padding(start = 8.dp) + .height(rowHeightDp) + .verticalScroll(state = scrollState, enabled = false), + ) { + helpMessages.forEach { message -> + Text( + text = message, + style = PieceTheme.typography.bodySR, + color = PieceTheme.colors.dark2, + modifier = Modifier + .height(rowHeightDp) + .padding(top = 4.dp), + ) + } + } + } } } @@ -331,21 +405,39 @@ private fun ValueTalkPreview() { title = "어떠한 사람과 어떠한 연애를 하고 싶은지 들려주세요", content = "저는 연애에서 서로의 존중과 신뢰가 가장 중요하다고 생각합니다. 진정한 소통을 통해 서로의 감정을 이해하고, 함께 성장할 수 있는 관계를 원합니다. 일상 속 작은 것에도 감사하며, 서로의 꿈과 목표를 지지하고 응원하는 파트너가 되고 싶습니다. 또한, 유머와 즐거움을 잃지 않으며, 함께하는 순간들을 소중히 여기고 싶습니다. 사랑은 서로를 더 나은 사람으로 만들어주는 힘이 있다고 믿습니다. 서로에게 긍정적인 영향을 주며 행복한 시간을 함께하고 싶습니다!", aiSummary = "신뢰하며, 함께 성장하고 싶어요.", - helpMessage = "어떤 데이트를 즐기고 싶나요?", + helpMessages = listOf( + "함께 하고 싶은 데이트 스타일은 무엇인가요?", + "이상적인 관계의 모습을 적어 보세요", + "연인과 함께 만들고 싶은 추억이 있나요?", + "연애에서 가장 중요시하는 가치는 무엇인가요?", + "연인 관계를 통해 어떤 가치를 얻고 싶나요?", + ), ), ValueTalk( label = "관심사와 취향", title = "무엇을 할 때 가장 행복한가요?\n요즘 어떠한 것에 관심을 두고 있나요?", content = "저는 다양한 취미와 관심사를 가진 사람입니다. 음악을 사랑하여 콘서트에 자주 가고, 특히 인디 음악과 재즈에 매력을 느낍니다. 요리도 좋아해 새로운 레시피에 도전하는 것을 즐깁니다. 여행을 통해 새로운 맛과 문화를 경험하는 것도 큰 기쁨입니다. 또, 자연을 사랑해서 주말마다 하이킹이나 캠핑을 자주 떠납니다. 영화와 책도 좋아해, 좋은 이야기와 감동을 나누는 시간을 소중히 여깁니다. 서로의 취향을 공유하며 즐거운 시간을 보낼 수 있기를 기대합니다!", aiSummary = "음악, 요리, 하이킹을 좋아해요.", - helpMessage = "최근에 가장 행복했던 경험을 공유해 주세요", + helpMessages = listOf( + "당신의 삶을 즐겁게 만드는 것들은 무엇인가요?", + "일상에서 소소한 행복을 느끼는 순간을 적어보세요", + "최근에 몰입했던 취미가 있다면 소개해 주세요", + "최근 마음이 따뜻해졌던 순간을 들려주세요.", + "요즘 마음을 사로잡은 콘텐츠를 공유해 보세요", + ), ), ValueTalk( label = "꿈과 목표", title = "어떤 일을 하며 무엇을 목표로 살아가나요?\n인생에서 이루고 싶은 꿈은 무엇인가요?", content = "안녕하세요! 저는 삶의 매 순간을 소중히 여기며, 꿈과 목표를 이루기 위해 노력하는 사람입니다. 제 가장 큰 꿈은 여행을 통해 다양한 문화와 사람들을 경험하고, 그 과정에서 얻은 지혜를 나누는 것입니다. 또한, LGBTQ+ 커뮤니티를 위한 긍정적인 변화를 이끌어내고 싶습니다. 내가 이루고자 하는 목표는 나 자신을 발전시키고, 사랑하는 사람들과 함께 행복한 순간들을 만드는 것입니다. 서로의 꿈을 지지하며 함께 성장할 수 있는 관계를 기대합니다!", aiSummary = "여행하며 LGBTQ+ 변화를 원해요.", - helpMessage = "당신의 직업은 무엇인가요?", + helpMessages = listOf( + "당신의 직업은 무엇인가요?", + "앞으로 하고 싶은 일에 대해 이야기해주세요", + "어떤 일을 할 때 가장 큰 성취감을 느끼나요?", + "당신의 버킷리스트를 알려주세요", + "당신이 꿈꾸는 삶은 어떤 모습인가요?", + ), ) ) ), diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt index 936174d4..9a7aa767 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt @@ -10,21 +10,39 @@ data class ValueTalkState( title = "어떠한 사람과 어떠한 연애를 하고 싶은지 들려주세요", content = "저는 연애에서 서로의 존중과 신뢰가 가장 중요하다고 생각합니다. 진정한 소통을 통해 서로의 감정을 이해하고, 함께 성장할 수 있는 관계를 원합니다. 일상 속 작은 것에도 감사하며, 서로의 꿈과 목표를 지지하고 응원하는 파트너가 되고 싶습니다. 또한, 유머와 즐거움을 잃지 않으며, 함께하는 순간들을 소중히 여기고 싶습니다. 사랑은 서로를 더 나은 사람으로 만들어주는 힘이 있다고 믿습니다. 서로에게 긍정적인 영향을 주며 행복한 시간을 함께하고 싶습니다!", aiSummary = "신뢰하며, 함께 성장하고 싶어요.", - helpMessage = "어떤 데이트를 즐기고 싶나요?", + helpMessages = listOf( + "함께 하고 싶은 데이트 스타일은 무엇인가요?", + "이상적인 관계의 모습을 적어 보세요", + "연인과 함께 만들고 싶은 추억이 있나요?", + "연애에서 가장 중요시하는 가치는 무엇인가요?", + "연인 관계를 통해 어떤 가치를 얻고 싶나요?", + ), ), ValueTalk( label = "관심사와 취향", title = "무엇을 할 때 가장 행복한가요?\n요즘 어떠한 것에 관심을 두고 있나요?", content = "저는 다양한 취미와 관심사를 가진 사람입니다. 음악을 사랑하여 콘서트에 자주 가고, 특히 인디 음악과 재즈에 매력을 느낍니다. 요리도 좋아해 새로운 레시피에 도전하는 것을 즐깁니다. 여행을 통해 새로운 맛과 문화를 경험하는 것도 큰 기쁨입니다. 또, 자연을 사랑해서 주말마다 하이킹이나 캠핑을 자주 떠납니다. 영화와 책도 좋아해, 좋은 이야기와 감동을 나누는 시간을 소중히 여깁니다. 서로의 취향을 공유하며 즐거운 시간을 보낼 수 있기를 기대합니다!", aiSummary = "음악, 요리, 하이킹을 좋아해요.", - helpMessage = "최근에 가장 행복했던 경험을 공유해 주세요", + helpMessages = listOf( + "당신의 삶을 즐겁게 만드는 것들은 무엇인가요?", + "일상에서 소소한 행복을 느끼는 순간을 적어보세요", + "최근에 몰입했던 취미가 있다면 소개해 주세요", + "최근 마음이 따뜻해졌던 순간을 들려주세요.", + "요즘 마음을 사로잡은 콘텐츠를 공유해 보세요", + ), ), ValueTalk( label = "꿈과 목표", title = "어떤 일을 하며 무엇을 목표로 살아가나요?\n인생에서 이루고 싶은 꿈은 무엇인가요?", content = "안녕하세요! 저는 삶의 매 순간을 소중히 여기며, 꿈과 목표를 이루기 위해 노력하는 사람입니다. 제 가장 큰 꿈은 여행을 통해 다양한 문화와 사람들을 경험하고, 그 과정에서 얻은 지혜를 나누는 것입니다. 또한, LGBTQ+ 커뮤니티를 위한 긍정적인 변화를 이끌어내고 싶습니다. 내가 이루고자 하는 목표는 나 자신을 발전시키고, 사랑하는 사람들과 함께 행복한 순간들을 만드는 것입니다. 서로의 꿈을 지지하며 함께 성장할 수 있는 관계를 기대합니다!", aiSummary = "여행하며 LGBTQ+ 변화를 원해요.", - helpMessage = "당신의 직업은 무엇인가요?", + helpMessages = listOf( + "당신의 직업은 무엇인가요?", + "앞으로 하고 싶은 일에 대해 이야기해주세요", + "어떤 일을 할 때 가장 큰 성취감을 느끼나요?", + "당신의 버킷리스트를 알려주세요", + "당신이 꿈꾸는 삶은 어떤 모습인가요?", + ), ) ) ) : MavericksState { diff --git a/presentation/src/main/java/com/puzzle/presentation/ui/App.kt b/presentation/src/main/java/com/puzzle/presentation/ui/App.kt index 738be96d..327d17bf 100644 --- a/presentation/src/main/java/com/puzzle/presentation/ui/App.kt +++ b/presentation/src/main/java/com/puzzle/presentation/ui/App.kt @@ -158,6 +158,7 @@ private val HIDDEN_BOTTOM_NAV_ROUTES = setOf( AuthGraph::class.qualifiedName, MatchingDetailRoute::class.qualifiedName, ProfileGraphDest.RegisterProfileRoute::class.qualifiedName, + ProfileGraphDest.ValueTalkProfileRoute::class.qualifiedName, SettingGraphDest.WithdrawRoute::class.qualifiedName, ) From 0b16cfce8cf5e4989ab246b351fda424c7eda193 Mon Sep 17 00:00:00 2001 From: sksowk156 Date: Sat, 1 Feb 2025 17:41:18 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[PC-434]=20ui=20=EC=83=81=EC=88=98,=20state?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt | 5 ++--- .../profile/graph/valuetalk/contract/ValueTalkState.kt | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt index 72654a9c..3a64ba34 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt @@ -51,6 +51,8 @@ import com.puzzle.domain.model.matching.ValueTalk import com.puzzle.profile.graph.valuetalk.contract.ValueTalkIntent import com.puzzle.profile.graph.valuetalk.contract.ValueTalkSideEffect import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState.Companion.PAGE_TRANSITION_DURATION +import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState.Companion.TEXT_DISPLAY_DURATION import com.puzzle.profile.graph.valuetalk.contract.ValueTalkState.ScreenState import kotlinx.coroutines.delay @@ -312,9 +314,6 @@ private fun AiSummaryContent( ) } -const val TEXT_DISPLAY_DURATION = 3000L -const val PAGE_TRANSITION_DURATION = 1000 - @Composable fun HelpMessageRow( helpMessages: List, diff --git a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt index 9a7aa767..2cc8e8e1 100644 --- a/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt +++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt @@ -47,6 +47,11 @@ data class ValueTalkState( ) ) : MavericksState { + companion object { + const val TEXT_DISPLAY_DURATION = 3000L + const val PAGE_TRANSITION_DURATION = 1000 + } + enum class ScreenState { EDITING, SAVED