Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into setup/fdroid-metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
Taewan-P committed Jun 27, 2024
2 parents 415d6c3 + 7c06dc2 commit 513cb60
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.chungjungsoo.gptmobile.presentation.ui.chat

import android.util.Log
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
Expand All @@ -19,7 +20,6 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
Expand All @@ -37,8 +37,10 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
Expand All @@ -59,6 +61,7 @@ import dev.chungjungsoo.gptmobile.R
import dev.chungjungsoo.gptmobile.data.database.entity.Message
import dev.chungjungsoo.gptmobile.data.model.ApiType
import dev.chungjungsoo.gptmobile.util.collectManagedState
import dev.chungjungsoo.gptmobile.util.multiScrollStateSaver

@OptIn(ExperimentalMaterial3Api::class)
@Composable
Expand Down Expand Up @@ -89,6 +92,22 @@ fun ChatScreen(
val canUseChat = (chatViewModel.enabledPlatformsInChat.toSet() - appEnabledPlatforms.toSet()).isEmpty()
val groupedMessages = remember(messages) { groupMessages(messages) }
val latestMessageIndex = groupedMessages.keys.maxOrNull() ?: 0
val chatBubbleScrollStates = rememberSaveable(saver = multiScrollStateSaver) { MutableList(latestMessageIndex + 2) { ScrollState(0) } }

LaunchedEffect(latestMessageIndex) {
val opponentBubbles = ((latestMessageIndex + 1) / 2) + 1
val scrollStatesToAdd = opponentBubbles - chatBubbleScrollStates.size

if (scrollStatesToAdd > 0) {
repeat(scrollStatesToAdd) {
chatBubbleScrollStates.add(ScrollState(0))
}
}
}

LaunchedEffect(isIdle) {
listState.animateScrollToItem(groupedMessages.keys.size)
}

Scaffold(
modifier = Modifier
Expand Down Expand Up @@ -138,7 +157,7 @@ fun ChatScreen(
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.horizontalScroll(chatBubbleScrollStates[(key - 1) / 2])
) {
Spacer(modifier = Modifier.width(8.dp))
groupedMessages[key]!!.sortedByDescending { it.platformType }.forEach { m ->
Expand Down Expand Up @@ -178,7 +197,7 @@ fun ChatScreen(
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.horizontalScroll(chatBubbleScrollStates[(latestMessageIndex + 1) / 2])
) {
Spacer(modifier = Modifier.width(8.dp))
chatViewModel.enabledPlatformsInChat.sortedDescending().forEach { apiType ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class ChatViewModel @Inject constructor(
private val enabledPlatformString: String = checkNotNull(savedStateHandle["enabledPlatforms"])
val enabledPlatformsInChat = enabledPlatformString.split(',').map { s -> ApiType.valueOf(s) }
private lateinit var chatRoom: ChatRoom
private val currentTimeStamp: Long
get() = System.currentTimeMillis() / 1000

private val _enabledPlatformsInApp = MutableStateFlow(listOf<ApiType>())
val enabledPlatformsInApp = _enabledPlatformsInApp.asStateFlow()
Expand Down Expand Up @@ -84,7 +86,7 @@ class ChatViewModel @Inject constructor(

fun askQuestion() {
Log.d("Question: ", _question.value)
_userMessage.update { it.copy(content = _question.value) }
_userMessage.update { it.copy(content = _question.value, createdAt = currentTimeStamp) }
_question.update { "" }
completeChat()
}
Expand All @@ -109,22 +111,22 @@ class ChatViewModel @Inject constructor(
enabledPlatformsInChat.forEach { apiType ->
when (apiType) {
message.platformType -> {}
else -> restoreMessageState(apiType, previousAnswers)
else -> restoreMessageState(apiType, previousAnswers) // Restore messages that are not retrying
}
}

when (message.platformType) {
ApiType.OPENAI -> {
_openAIMessage.update { it.copy(content = "") }
_openAIMessage.update { it.copy(id = message.id, content = "", createdAt = currentTimeStamp) }
completeOpenAIChat()
}

ApiType.ANTHROPIC -> {
_anthropicMessage.update { it.copy(content = "") }
_anthropicMessage.update { it.copy(id = message.id, content = "", createdAt = currentTimeStamp) }
completeAnthropicChat()
}
ApiType.GOOGLE -> {
_googleMessage.update { it.copy(content = "") }
_googleMessage.update { it.copy(id = message.id, content = "", createdAt = currentTimeStamp) }
completeGoogleChat()
}

Expand All @@ -137,10 +139,10 @@ class ChatViewModel @Inject constructor(
private fun addMessage(message: Message) = _messages.update { it + listOf(message) }

private fun clearQuestionAndAnswers() {
_userMessage.update { it.copy(content = "") }
_openAIMessage.update { it.copy(content = "") }
_anthropicMessage.update { it.copy(content = "") }
_googleMessage.update { it.copy(content = "") }
_userMessage.update { it.copy(id = 0, content = "") }
_openAIMessage.update { it.copy(id = 0, content = "") }
_anthropicMessage.update { it.copy(id = 0, content = "") }
_googleMessage.update { it.copy(id = 0, content = "") }
}

private fun completeChat() {
Expand Down Expand Up @@ -183,8 +185,16 @@ class ChatViewModel @Inject constructor(

private fun fetchMessages() {
viewModelScope.launch {
// If the room isn't new
if (chatRoomId != 0) {
_messages.update { chatRepository.fetchMessages(chatRoomId) }
return@launch
}

// When message id should sync after saving chats
if (chatRoom.id != 0) {
_messages.update { chatRepository.fetchMessages(chatRoom.id) }
return@launch
}
}
}
Expand Down Expand Up @@ -212,9 +222,12 @@ class ChatViewModel @Inject constructor(
openAIFlow.collect { chunk ->
when (chunk) {
is ApiState.Success -> _openAIMessage.update { it.copy(content = it.content + chunk.textChunk) }
ApiState.Done -> updateLoadingState(ApiType.OPENAI, LoadingState.Idle)
ApiState.Done -> {
_openAIMessage.update { it.copy(createdAt = currentTimeStamp) }
updateLoadingState(ApiType.OPENAI, LoadingState.Idle)
}
is ApiState.Error -> {
_openAIMessage.update { it.copy(content = "Error: ${chunk.message}") }
_openAIMessage.update { it.copy(content = "Error: ${chunk.message}", createdAt = currentTimeStamp) }
updateLoadingState(ApiType.OPENAI, LoadingState.Idle)
}

Expand All @@ -227,9 +240,12 @@ class ChatViewModel @Inject constructor(
anthropicFlow.collect { chunk ->
when (chunk) {
is ApiState.Success -> _anthropicMessage.update { it.copy(content = it.content + chunk.textChunk) }
ApiState.Done -> updateLoadingState(ApiType.ANTHROPIC, LoadingState.Idle)
ApiState.Done -> {
_anthropicMessage.update { it.copy(createdAt = currentTimeStamp) }
updateLoadingState(ApiType.ANTHROPIC, LoadingState.Idle)
}
is ApiState.Error -> {
_anthropicMessage.update { it.copy(content = "Error: ${chunk.message}") }
_anthropicMessage.update { it.copy(content = "Error: ${chunk.message}", createdAt = currentTimeStamp) }
updateLoadingState(ApiType.ANTHROPIC, LoadingState.Idle)
}

Expand All @@ -242,9 +258,12 @@ class ChatViewModel @Inject constructor(
googleFlow.collect { chunk ->
when (chunk) {
is ApiState.Success -> _googleMessage.update { it.copy(content = it.content + chunk.textChunk) }
ApiState.Done -> updateLoadingState(ApiType.GOOGLE, LoadingState.Idle)
ApiState.Done -> {
_googleMessage.update { it.copy(createdAt = currentTimeStamp) }
updateLoadingState(ApiType.GOOGLE, LoadingState.Idle)
}
is ApiState.Error -> {
_googleMessage.update { it.copy(content = "Error: ${chunk.message}") }
_googleMessage.update { it.copy(content = "Error: ${chunk.message}", createdAt = currentTimeStamp) }
updateLoadingState(ApiType.GOOGLE, LoadingState.Idle)
}

Expand All @@ -261,6 +280,7 @@ class ChatViewModel @Inject constructor(
syncQuestionAndAnswers()
Log.d("message", "${_messages.value}")
chatRoom = chatRepository.saveChat(chatRoom, _messages.value)
fetchMessages() // For syncing message ids
}
clearQuestionAndAnswers()
}
Expand All @@ -274,9 +294,9 @@ class ChatViewModel @Inject constructor(
if (message == null) return

when (apiType) {
ApiType.OPENAI -> _openAIMessage.update { it.copy(content = message.content) }
ApiType.ANTHROPIC -> _anthropicMessage.update { it.copy(content = message.content) }
ApiType.GOOGLE -> _googleMessage.update { it.copy(content = message.content) }
ApiType.OPENAI -> _openAIMessage.update { message }
ApiType.ANTHROPIC -> _anthropicMessage.update { message }
ApiType.GOOGLE -> _googleMessage.update { message }
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.chungjungsoo.gptmobile.util

import androidx.compose.foundation.ScrollState
import androidx.compose.runtime.saveable.Saver

val multiScrollStateSaver: Saver<MutableList<ScrollState>, *> = Saver(
save = { it.map { scrollState -> scrollState.value } },
restore = { it.map { i -> ScrollState(i) }.toMutableList() }
)

0 comments on commit 513cb60

Please sign in to comment.