Skip to content

Commit 79218bf

Browse files
committed
refactor: Refactor chat completions screen with new design system
1 parent 6089340 commit 79218bf

File tree

3 files changed

+210
-0
lines changed

3 files changed

+210
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package co.yml.ychat.android.presentation.chatcompletions.model
2+
3+
internal sealed class MessageType {
4+
data class Sender(val text: String, var isError: Boolean = false): MessageType()
5+
data class Bot(val text: String): MessageType()
6+
object Loading: MessageType()
7+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package co.yml.ychat.android.presentation.chatcompletions.screen
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxHeight
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.lazy.LazyColumn
12+
import androidx.compose.foundation.lazy.items
13+
import androidx.compose.foundation.lazy.rememberLazyListState
14+
import androidx.compose.foundation.shape.CircleShape
15+
import androidx.compose.foundation.shape.RoundedCornerShape
16+
import androidx.compose.foundation.text.KeyboardOptions
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.LaunchedEffect
19+
import androidx.compose.ui.Alignment
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.draw.clip
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.text.input.KeyboardCapitalization
24+
import androidx.compose.ui.text.style.TextAlign
25+
import androidx.compose.ui.tooling.preview.Preview
26+
import co.yml.ychat.YChat
27+
import co.yml.ychat.android.BuildConfig
28+
import co.yml.ychat.android.R
29+
import co.yml.ychat.android.presentation.chatcompletions.model.MessageType
30+
import co.yml.ychat.android.presentation.chatcompletions.viewmodel.ChatCompletionsViewModel
31+
import co.yml.ychat.android.ui.components.ballonmessage.BalloonBotMessage
32+
import co.yml.ychat.android.ui.components.ballonmessage.BalloonSenderMessage
33+
import co.yml.ychat.android.ui.components.ballonmessage.BalloonTyping
34+
import co.yml.ychat.android.ui.components.textfield.StandardTextField
35+
import co.yml.ychat.android.ui.theme.Dimens
36+
import co.yml.ychat.android.ui.theme.Icons
37+
import co.yml.ychat.android.ui.theme.TypographyStyle
38+
import co.yml.ychat.android.ui.theme.YChatTheme
39+
import org.koin.androidx.compose.getViewModel
40+
41+
@Composable
42+
internal fun ChatCompletionsScreen(
43+
viewModel: ChatCompletionsViewModel = getViewModel(),
44+
) {
45+
Column(
46+
Modifier
47+
.background(YChatTheme.colors.background)
48+
.fillMaxHeight()
49+
) {
50+
val scrollState = rememberLazyListState()
51+
val messages = viewModel.messages
52+
LazyColumn(
53+
state = scrollState,
54+
verticalArrangement = Arrangement.spacedBy(Dimens.MD),
55+
modifier = Modifier
56+
.weight(1F)
57+
.padding(horizontal = Dimens.XS),
58+
) {
59+
item { Spacer(modifier = Modifier.padding(top = Dimens.MD)) }
60+
if (messages.isEmpty()) item { EmptyMessage() }
61+
items(messages) { item ->
62+
when (item) {
63+
is MessageType.Sender ->
64+
BalloonSenderMessage(text = item.text, isError = item.isError)
65+
is MessageType.Bot ->
66+
BalloonBotMessage(text = item.text)
67+
is MessageType.Loading ->
68+
BalloonTyping()
69+
}
70+
}
71+
}
72+
SendMessageSection(viewModel)
73+
LaunchedEffect(messages.size) {
74+
scrollState.animateScrollToItem(messages.size)
75+
}
76+
}
77+
}
78+
79+
@Composable
80+
private fun EmptyMessage() {
81+
TypographyStyle.SmallTitle.Text(
82+
text = stringResource(id = R.string.chat_completions_empty_message),
83+
modifier = Modifier
84+
.padding(horizontal = Dimens.XM)
85+
.clip(RoundedCornerShape(Dimens.MD))
86+
.background(YChatTheme.colors.accentLight)
87+
.padding(vertical = Dimens.SM)
88+
.fillMaxWidth(),
89+
textAlign = TextAlign.Center,
90+
)
91+
}
92+
93+
@Composable
94+
private fun SendMessageSection(viewModel: ChatCompletionsViewModel) {
95+
Row(
96+
horizontalArrangement = Arrangement.spacedBy(Dimens.XS),
97+
verticalAlignment = Alignment.CenterVertically,
98+
modifier = Modifier.padding(Dimens.SM)
99+
) {
100+
StandardTextField(
101+
viewModel.message.value,
102+
hint = stringResource(id = R.string.chat_completions_hint),
103+
enabled = viewModel.onEnableTextField.value,
104+
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
105+
onTextChanged = { viewModel.onMessage(it) }
106+
)
107+
SendButton(viewModel)
108+
}
109+
}
110+
111+
@Composable
112+
private fun SendButton(viewModel: ChatCompletionsViewModel) {
113+
val background =
114+
if (viewModel.onEnableButton.value) YChatTheme.colors.accent
115+
else YChatTheme.colors.primary4
116+
Icons.Send.IconButton(
117+
onClick = { viewModel.sendMessage() },
118+
modifier = Modifier
119+
.padding(Dimens.XS)
120+
.clip(CircleShape)
121+
.background(background),
122+
tint = YChatTheme.colors.onAccent,
123+
enabled = viewModel.onEnableButton.value
124+
)
125+
}
126+
127+
@Preview
128+
@Composable
129+
private fun StandardTextFieldPreview() {
130+
YChatTheme {
131+
val yChat = YChat.create(BuildConfig.API_KEY)
132+
val viewModel = ChatCompletionsViewModel(yChat)
133+
ChatCompletionsScreen(viewModel)
134+
}
135+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package co.yml.ychat.android.presentation.chatcompletions.viewmodel
2+
3+
import androidx.compose.runtime.mutableStateListOf
4+
import androidx.compose.runtime.mutableStateOf
5+
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.viewModelScope
7+
import co.yml.ychat.YChat
8+
import co.yml.ychat.android.presentation.chatcompletions.model.MessageType
9+
import kotlinx.coroutines.launch
10+
11+
internal class ChatCompletionsViewModel(private val yChat: YChat) : ViewModel() {
12+
13+
val message = mutableStateOf("")
14+
15+
val messages = mutableStateListOf<MessageType>()
16+
17+
val onEnableButton = mutableStateOf(message.value.isNotEmpty())
18+
19+
val onEnableTextField = mutableStateOf(true)
20+
21+
private val chatCompletions by lazy {
22+
yChat.chatCompletions()
23+
.addMessage("assistant", "You are helpful assistant")
24+
.setMaxTokens(MAX_TOKENS)
25+
}
26+
27+
fun sendMessage() = viewModelScope.launch {
28+
val messageToSend = message.value
29+
messages.add(MessageType.Sender(message.value))
30+
onLoading(true)
31+
onMessage("")
32+
runCatching { chatCompletions.execute(messageToSend) }
33+
.also { onLoading(false) }
34+
.onSuccess { messages.add(MessageType.Bot(it.first().content)) }
35+
.onFailure { onError(true) }
36+
}
37+
38+
fun onMessage(message: String) {
39+
this.message.value = message
40+
onEnableButton.value = message.isNotEmpty()
41+
}
42+
43+
private fun onLoading(isLoading: Boolean) {
44+
if (isLoading) {
45+
onError(false)
46+
onEnableButton.value = false
47+
onEnableTextField.value = false
48+
messages.add(MessageType.Loading)
49+
} else {
50+
onEnableTextField.value = true
51+
messages.remove(MessageType.Loading)
52+
}
53+
}
54+
55+
private fun onError(isError: Boolean) {
56+
if (isError) {
57+
val error = messages.removeLast() as? MessageType.Sender ?: return
58+
error.isError = true
59+
messages.add(error)
60+
} else {
61+
messages.removeIf { it is MessageType.Sender && it.isError }
62+
}
63+
}
64+
65+
companion object {
66+
private const val MAX_TOKENS = 1024
67+
}
68+
}

0 commit comments

Comments
 (0)