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 45fe41bf..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
@@ -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
@@ -129,7 +130,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,
@@ -148,19 +149,22 @@ fun PieceTextInputLong(
textStyle = PieceTheme.typography.bodyMM,
cursorBrush = SolidColor(PieceTheme.colors.primaryDefault),
decorationBox = { innerTextField ->
+ val isCharCountVisible: Boolean = limit != null && !readOnly
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 (isCharCountVisible) 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 (isCharCountVisible) {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(color = PieceTheme.colors.primaryDefault)) {
@@ -169,7 +173,9 @@ fun PieceTextInputLong(
append("/${limit}")
},
style = PieceTheme.typography.bodySM,
- modifier = Modifier.align(Alignment.BottomEnd),
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .height(20.dp),
)
}
}
@@ -178,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),
)
}
@@ -190,64 +193,110 @@ 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,
- 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
+ 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),
- decorationBox = { innerTextField ->
- Box {
- if (value.isEmpty()) {
- Text(
- text = "작성해주신 내용을 AI가 요약하고 있어요",
- style = PieceTheme.typography.bodyMM,
- color = PieceTheme.colors.dark3,
- modifier = Modifier.align(Alignment.CenterStart)
+ ),
+ 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()
+ }
+
+ 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)
+ .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),
+ )
- innerTextField()
+ if (!isReadOnly) {
+ Row {
+ Spacer(modifier = Modifier.weight(1f))
- val imageRes = if (value.isEmpty()) R.drawable.ic_textinput_3dots
- else if (isFocused) R.drawable.ic_textinput_check
- else R.drawable.ic_textinput_pencil
+ Text(
+ text = buildAnnotatedString {
+ withStyle(style = SpanStyle(color = PieceTheme.colors.primaryDefault)) {
+ append(value.length.toString())
+ }
- Image(
- painter = painterResource(imageRes),
- contentDescription = null,
- modifier = Modifier
- .size(24.dp)
- .align(Alignment.CenterEnd),
+ 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
@@ -408,6 +457,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),
@@ -416,6 +477,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/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/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..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
@@ -4,4 +4,6 @@ data class ValueTalk(
val label: String = "",
val title: String = "",
val content: String = "",
+ val aiSummary: String = "",
+ val helpMessages: List = emptyList(),
)
\ 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..3a64ba34
--- /dev/null
+++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkScreen.kt
@@ -0,0 +1,448 @@
+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
+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.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
+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.platform.LocalDensity
+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
+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.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
+
+@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 = { viewModel.onIntent(ValueTalkIntent.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) }
+ var editedValueTalkLabels: List by remember { mutableStateOf(emptyList()) }
+
+ BackHandler {
+ if (screenState == ScreenState.EDITING) {
+ // TODO : 데이터 초기화
+ screenState = ScreenState.SAVED
+ } else {
+ onBackClick()
+ }
+ }
+
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .background(PieceTheme.colors.white),
+ ) {
+ PieceSubTopBar(
+ title = when (screenState) {
+ 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.SAVED ->
+ 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
+ } else {
+ PieceTheme.colors.dark3
+ },
+ modifier = Modifier.clickable {
+ if (isContentEdited) {
+ valueTalks = valueTalks.map { valueTalk ->
+ if (editedValueTalkLabels.contains(valueTalk.label)) {
+ valueTalk.copy(aiSummary = "")
+ } else {
+ valueTalk
+ }
+ }
+ onSaveClick(valueTalks)
+ isContentEdited = false
+ editedValueTalkLabels = emptyList()
+ }
+ screenState = ScreenState.SAVED
+ },
+ )
+ }
+ },
+ 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) {
+ if (!editedValueTalkLabels.contains(valueTalk.label)) {
+ editedValueTalkLabels = editedValueTalkLabels + valueTalk.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,
+ )
+ )
+
+ if (idx < valueTalks.size - 1) {
+ HorizontalDivider(
+ thickness = 12.dp,
+ color = PieceTheme.colors.light3,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ValueTalkCard(
+ item: ValueTalk,
+ screenState: ScreenState,
+ onContentChange: (ValueTalk) -> Unit,
+ onAiSummarySaveClick: (ValueTalk) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ 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 = item.content,
+ onValueChange = {
+ onContentChange(item.copy(content = it))
+ },
+ limit = 300,
+ readOnly = when (screenState) {
+ ScreenState.SAVED -> true
+ ScreenState.EDITING -> false
+ },
+ )
+
+ when (screenState) {
+ ScreenState.SAVED ->
+ AiSummaryContent(
+ item = item,
+ onAiSummarySaveClick = onAiSummarySaveClick,
+ )
+
+ ScreenState.EDITING ->
+ HelpMessageRow(
+ helpMessages = item.helpMessages,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp)
+ )
+ }
+ }
+}
+
+@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 = stringResource(R.string.value_talk_profile_aisummary_title),
+ 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(aiSummary = it)
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp),
+ )
+}
+
+@Composable
+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,
+ ) {
+ Text(
+ 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),
+ )
+
+ 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),
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ValueTalkPreview() {
+ PieceTheme {
+ ValueTalkScreen(
+ state = ValueTalkState(
+ valueTalks = listOf(
+ ValueTalk(
+ label = "연애관",
+ title = "어떠한 사람과 어떠한 연애를 하고 싶은지 들려주세요",
+ content = "저는 연애에서 서로의 존중과 신뢰가 가장 중요하다고 생각합니다. 진정한 소통을 통해 서로의 감정을 이해하고, 함께 성장할 수 있는 관계를 원합니다. 일상 속 작은 것에도 감사하며, 서로의 꿈과 목표를 지지하고 응원하는 파트너가 되고 싶습니다. 또한, 유머와 즐거움을 잃지 않으며, 함께하는 순간들을 소중히 여기고 싶습니다. 사랑은 서로를 더 나은 사람으로 만들어주는 힘이 있다고 믿습니다. 서로에게 긍정적인 영향을 주며 행복한 시간을 함께하고 싶습니다!",
+ aiSummary = "신뢰하며, 함께 성장하고 싶어요.",
+ helpMessages = listOf(
+ "함께 하고 싶은 데이트 스타일은 무엇인가요?",
+ "이상적인 관계의 모습을 적어 보세요",
+ "연인과 함께 만들고 싶은 추억이 있나요?",
+ "연애에서 가장 중요시하는 가치는 무엇인가요?",
+ "연인 관계를 통해 어떤 가치를 얻고 싶나요?",
+ ),
+ ),
+ ValueTalk(
+ label = "관심사와 취향",
+ title = "무엇을 할 때 가장 행복한가요?\n요즘 어떠한 것에 관심을 두고 있나요?",
+ content = "저는 다양한 취미와 관심사를 가진 사람입니다. 음악을 사랑하여 콘서트에 자주 가고, 특히 인디 음악과 재즈에 매력을 느낍니다. 요리도 좋아해 새로운 레시피에 도전하는 것을 즐깁니다. 여행을 통해 새로운 맛과 문화를 경험하는 것도 큰 기쁨입니다. 또, 자연을 사랑해서 주말마다 하이킹이나 캠핑을 자주 떠납니다. 영화와 책도 좋아해, 좋은 이야기와 감동을 나누는 시간을 소중히 여깁니다. 서로의 취향을 공유하며 즐거운 시간을 보낼 수 있기를 기대합니다!",
+ aiSummary = "음악, 요리, 하이킹을 좋아해요.",
+ helpMessages = listOf(
+ "당신의 삶을 즐겁게 만드는 것들은 무엇인가요?",
+ "일상에서 소소한 행복을 느끼는 순간을 적어보세요",
+ "최근에 몰입했던 취미가 있다면 소개해 주세요",
+ "최근 마음이 따뜻해졌던 순간을 들려주세요.",
+ "요즘 마음을 사로잡은 콘텐츠를 공유해 보세요",
+ ),
+ ),
+ ValueTalk(
+ label = "꿈과 목표",
+ title = "어떤 일을 하며 무엇을 목표로 살아가나요?\n인생에서 이루고 싶은 꿈은 무엇인가요?",
+ content = "안녕하세요! 저는 삶의 매 순간을 소중히 여기며, 꿈과 목표를 이루기 위해 노력하는 사람입니다. 제 가장 큰 꿈은 여행을 통해 다양한 문화와 사람들을 경험하고, 그 과정에서 얻은 지혜를 나누는 것입니다. 또한, LGBTQ+ 커뮤니티를 위한 긍정적인 변화를 이끌어내고 싶습니다. 내가 이루고자 하는 목표는 나 자신을 발전시키고, 사랑하는 사람들과 함께 행복한 순간들을 만드는 것입니다. 서로의 꿈을 지지하며 함께 성장할 수 있는 관계를 기대합니다!",
+ aiSummary = "여행하며 LGBTQ+ 변화를 원해요.",
+ helpMessages = listOf(
+ "당신의 직업은 무엇인가요?",
+ "앞으로 하고 싶은 일에 대해 이야기해주세요",
+ "어떤 일을 할 때 가장 큰 성취감을 느끼나요?",
+ "당신의 버킷리스트를 알려주세요",
+ "당신이 꿈꾸는 삶은 어떤 모습인가요?",
+ ),
+ )
+ )
+ ),
+ 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..ef45dd3c
--- /dev/null
+++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/ValueTalkViewModel.kt
@@ -0,0 +1,59 @@
+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.NavigationEvent
+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) {
+ ValueTalkIntent.OnBackClick -> _sideEffects.send(
+ ValueTalkSideEffect.Navigate(NavigationEvent.NavigateUp)
+ )
+ }
+ }
+
+ @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..4130f71b
--- /dev/null
+++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkIntent.kt
@@ -0,0 +1,5 @@
+package com.puzzle.profile.graph.valuetalk.contract
+
+sealed class ValueTalkIntent {
+ data object OnBackClick : 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..2cc8e8e1
--- /dev/null
+++ b/feature/profile/src/main/java/com/puzzle/profile/graph/valuetalk/contract/ValueTalkState.kt
@@ -0,0 +1,59 @@
+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 = "신뢰하며, 함께 성장하고 싶어요.",
+ helpMessages = listOf(
+ "함께 하고 싶은 데이트 스타일은 무엇인가요?",
+ "이상적인 관계의 모습을 적어 보세요",
+ "연인과 함께 만들고 싶은 추억이 있나요?",
+ "연애에서 가장 중요시하는 가치는 무엇인가요?",
+ "연인 관계를 통해 어떤 가치를 얻고 싶나요?",
+ ),
+ ),
+ ValueTalk(
+ label = "관심사와 취향",
+ title = "무엇을 할 때 가장 행복한가요?\n요즘 어떠한 것에 관심을 두고 있나요?",
+ content = "저는 다양한 취미와 관심사를 가진 사람입니다. 음악을 사랑하여 콘서트에 자주 가고, 특히 인디 음악과 재즈에 매력을 느낍니다. 요리도 좋아해 새로운 레시피에 도전하는 것을 즐깁니다. 여행을 통해 새로운 맛과 문화를 경험하는 것도 큰 기쁨입니다. 또, 자연을 사랑해서 주말마다 하이킹이나 캠핑을 자주 떠납니다. 영화와 책도 좋아해, 좋은 이야기와 감동을 나누는 시간을 소중히 여깁니다. 서로의 취향을 공유하며 즐거운 시간을 보낼 수 있기를 기대합니다!",
+ aiSummary = "음악, 요리, 하이킹을 좋아해요.",
+ helpMessages = listOf(
+ "당신의 삶을 즐겁게 만드는 것들은 무엇인가요?",
+ "일상에서 소소한 행복을 느끼는 순간을 적어보세요",
+ "최근에 몰입했던 취미가 있다면 소개해 주세요",
+ "최근 마음이 따뜻해졌던 순간을 들려주세요.",
+ "요즘 마음을 사로잡은 콘텐츠를 공유해 보세요",
+ ),
+ ),
+ ValueTalk(
+ label = "꿈과 목표",
+ title = "어떤 일을 하며 무엇을 목표로 살아가나요?\n인생에서 이루고 싶은 꿈은 무엇인가요?",
+ content = "안녕하세요! 저는 삶의 매 순간을 소중히 여기며, 꿈과 목표를 이루기 위해 노력하는 사람입니다. 제 가장 큰 꿈은 여행을 통해 다양한 문화와 사람들을 경험하고, 그 과정에서 얻은 지혜를 나누는 것입니다. 또한, LGBTQ+ 커뮤니티를 위한 긍정적인 변화를 이끌어내고 싶습니다. 내가 이루고자 하는 목표는 나 자신을 발전시키고, 사랑하는 사람들과 함께 행복한 순간들을 만드는 것입니다. 서로의 꿈을 지지하며 함께 성장할 수 있는 관계를 기대합니다!",
+ aiSummary = "여행하며 LGBTQ+ 변화를 원해요.",
+ helpMessages = listOf(
+ "당신의 직업은 무엇인가요?",
+ "앞으로 하고 싶은 일에 대해 이야기해주세요",
+ "어떤 일을 할 때 가장 큰 성취감을 느끼나요?",
+ "당신의 버킷리스트를 알려주세요",
+ "당신이 꿈꾸는 삶은 어떤 모습인가요?",
+ ),
+ )
+ )
+) : MavericksState {
+
+ companion object {
+ const val TEXT_DISPLAY_DURATION = 3000L
+ const val PAGE_TRANSITION_DURATION = 1000
+ }
+
+ 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..327d17bf 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)
}
},
@@ -155,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,
)