Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.kikidan.designsystem.component

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
Expand All @@ -20,7 +19,6 @@ fun TodakunProgressIndicator(modifier: Modifier = Modifier) {
modifier =
modifier
.fillMaxSize()
.background(TodakunColor.gray900.copy(alpha = 0.5f))
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalRippleConfiguration
import androidx.compose.material3.Text
Expand Down Expand Up @@ -64,6 +65,7 @@ fun PrimaryButton(
size: TodakunButtonSize,
modifier: Modifier = Modifier,
enabled: Boolean = true,
isLoading: Boolean = false,
painter: Painter? = null,
contentDescription: String? = null,
iconPosition: TodakunButtonIconPosition = TodakunButtonIconPosition.Start,
Expand All @@ -75,6 +77,7 @@ fun PrimaryButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
isLoading = isLoading,
painter = painter,
contentDescription = contentDescription,
size = size,
Expand Down Expand Up @@ -116,12 +119,14 @@ private fun TodakunButton(
enabled: Boolean,
size: TodakunButtonSize,
modifier: Modifier = Modifier,
isLoading: Boolean = false,
painter: Painter? = null,
contentDescription: String? = null,
iconPosition: TodakunButtonIconPosition = TodakunButtonIconPosition.Start,
) {
val isEnabled = enabled && !isLoading
val textStyle =
if (enabled) TodakunTypography.body2SemiBold else TodakunTypography.body2Medium
if (isEnabled) TodakunTypography.body2SemiBold else TodakunTypography.body2Medium
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()

Expand All @@ -135,7 +140,7 @@ private fun TodakunButton(
Button(
modifier = modifier.height(size.height),
onClick = onClick,
enabled = enabled,
enabled = isEnabled,
shape = RoundedCornerShape(12.dp),
colors =
ButtonDefaults.buttonColors(
Expand All @@ -146,27 +151,35 @@ private fun TodakunButton(
),
interactionSource = interactionSource,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.iconSpacing, Alignment.CenterHorizontally),
) {
if (painter != null && iconPosition == TodakunButtonIconPosition.Start) {
Icon(
painter = painter,
contentDescription = contentDescription,
modifier = Modifier.size(size.iconSize),
)
}
Text(
text = text,
style = textStyle,
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(size.iconSize),
color = TodakunColor.gray400,
strokeWidth = 2.dp,
)
if (painter != null && iconPosition == TodakunButtonIconPosition.End) {
Icon(
painter = painter,
contentDescription = contentDescription,
modifier = Modifier.size(size.iconSize),
} else {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(size.iconSpacing, Alignment.CenterHorizontally),
) {
if (painter != null && iconPosition == TodakunButtonIconPosition.Start) {
Icon(
painter = painter,
contentDescription = contentDescription,
modifier = Modifier.size(size.iconSize),
)
}
Text(
text = text,
style = textStyle,
)
if (painter != null && iconPosition == TodakunButtonIconPosition.End) {
Icon(
painter = painter,
contentDescription = contentDescription,
modifier = Modifier.size(size.iconSize),
)
}
}
}
}
Expand Down
42 changes: 24 additions & 18 deletions feature/chat/src/main/java/com/kikidan/chat/ChatRoute.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.kikidan.chat.model.ChatEntryState
import com.kikidan.chat.model.ChatSideEffect
import com.kikidan.chat.screen.ChatScreen
import com.kikidan.chat.screen.ChatSplashScreen
Expand Down Expand Up @@ -40,23 +41,28 @@ fun ChatRoute(
}
}

if (state.isLoading && conversationId == null && !skipSplash) {
ChatSplashScreen()
} else {
ChatScreen(
state = state,
onInputChange = viewModel::onInputChange,
onSendClick = viewModel::onSendClick,
onSuggestionClick = viewModel::onSuggestionClick,
onNewConversationClick = viewModel::startNewConversation,
onCloseClick = onCloseClick,
onHistoryClick = onNavigateToHistory,
onCalendarLaunchFail = {
scope.launch {
snackbarHostState.showSnackbar(calendarErrorMessage)
}
},
modifier = modifier,
)
val showSplash = state.entryState is ChatEntryState.Loading && conversationId == null && !skipSplash
when {
showSplash -> {
ChatSplashScreen()
}

else -> {
ChatScreen(
state = state,
onInputChange = viewModel::onInputChange,
onSendClick = viewModel::onSendClick,
onSuggestionClick = viewModel::onSuggestionClick,
onNewConversationClick = viewModel::startNewConversation,
onCloseClick = onCloseClick,
onHistoryClick = onNavigateToHistory,
onCalendarLaunchFail = {
scope.launch {
snackbarHostState.showSnackbar(calendarErrorMessage)
}
},
modifier = modifier,
)
}
}
}
8 changes: 5 additions & 3 deletions feature/chat/src/main/java/com/kikidan/chat/ChatViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.kikidan.chat

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.kikidan.chat.model.ChatEntryState
import com.kikidan.chat.model.ChatSideEffect
import com.kikidan.chat.model.ChatState
import com.kikidan.chat.model.StreamingChatState
Expand Down Expand Up @@ -44,13 +45,14 @@ class ChatViewModel
),
)

/** 화면 진입 시 1회. conversationId가 있으면 과거 대화를 먼저 채운다. */
fun load(
conversationId: String?,
skipSplash: Boolean = false,
) = intent {
if (state.entryState == ChatEntryState.Success && state.conversationId == conversationId) return@intent

savedStateHandle[KEY_CONVERSATION_ID] = conversationId
reduce { state.copy(conversationId = conversationId, isLoading = true) }
reduce { state.copy(conversationId = conversationId, entryState = ChatEntryState.Loading) }
val startedAt = System.currentTimeMillis()

getChatEntry()
Expand All @@ -77,7 +79,7 @@ class ChatViewModel
delay(MIN_LOADING_DURATION_MILLIS - elapsed)
}
}
reduce { state.copy(isLoading = false) }
reduce { state.copy(entryState = ChatEntryState.Success) }
}

fun onInputChange(value: String) =
Expand Down
3 changes: 1 addition & 2 deletions feature/chat/src/main/java/com/kikidan/chat/HistoryRoute.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.kikidan.chat.model.HistorySideEffect
import com.kikidan.chat.model.toUiModel
import com.kikidan.chat.screen.HistoryScreen
import com.kikidan.designsystem.R
import org.orbitmvi.orbit.compose.collectAsState
Expand All @@ -35,7 +34,7 @@ fun HistoryRoute(
}

HistoryScreen(
conversations = state.conversations.map { it.toUiModel() },
state = state,
onBackClick = onBackClick,
onConversationClick = onNavigateToChat,
onDeleteClick = viewModel::onDeleteClick,
Expand Down
16 changes: 7 additions & 9 deletions feature/chat/src/main/java/com/kikidan/chat/HistoryViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,28 @@ class HistoryViewModel
private val deleteConversation: DeleteConversationUseCase,
) : ViewModel(),
ContainerHost<HistoryState, HistorySideEffect> {
override val container = container<HistoryState, HistorySideEffect>(HistoryState())
override val container = container<HistoryState, HistorySideEffect>(HistoryState.Loading)

fun load() =
intent {
reduce { state.copy(isLoading = true) }
reduce { HistoryState.Loading }
getConversations()
.onSuccess { conversations ->
reduce { state.copy(conversations = conversations.toPersistentList(), isLoading = false) }
reduce { HistoryState.Success(conversations.toPersistentList()) }
}.onFailure {
reduce { state.copy(isLoading = false) }
reduce { HistoryState.Failure }
postSideEffect(HistorySideEffect.Error(it))
}
}

fun onDeleteClick(conversationId: String) =
intent {
val current = state as? HistoryState.Success ?: return@intent
deleteConversation(conversationId)
.onSuccess {
reduce {
state.copy(
conversations =
state.conversations
.filterNot { it.id == conversationId }
.toPersistentList(),
HistoryState.Success(
current.conversations.filterNot { it.id == conversationId }.toPersistentList(),
)
}
}.onFailure { postSideEffect(HistorySideEffect.Error(it)) }
Expand Down
10 changes: 9 additions & 1 deletion feature/chat/src/main/java/com/kikidan/chat/model/ChatState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlinx.collections.immutable.persistentListOf

data class ChatState(
val conversationId: String? = null,
val isLoading: Boolean = true,
val entryState: ChatEntryState = ChatEntryState.Loading,
val greeting: String = "",
val suggestions: PersistentList<ChatSuggestion> = persistentListOf(),
val quota: ChatQuota? = null,
Expand All @@ -17,6 +17,14 @@ data class ChatState(
val streamingChatState: StreamingChatState = StreamingChatState.Idle,
)

sealed interface ChatEntryState {
data object Loading : ChatEntryState

data object Success : ChatEntryState

data object Failure : ChatEntryState
}

sealed interface StreamingChatState {
data object Idle : StreamingChatState

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package com.kikidan.chat.model

import com.kikidan.domain.model.chat.ConversationSummary
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf

data class HistoryState(
val isLoading: Boolean = true,
val conversations: PersistentList<ConversationSummary> = persistentListOf(),
)
sealed interface HistoryState {
data object Loading : HistoryState

data class Success(
val conversations: PersistentList<ConversationSummary>,
) : HistoryState

data object Failure : HistoryState
}
Loading
Loading