Skip to content

Commit 98e9bf5

Browse files
authored
Merge pull request #61 from yml-org/feature/CM-1320/completions-screen-android
feature: implements completions screen on android sample
2 parents d521c63 + a834a7a commit 98e9bf5

File tree

7 files changed

+260
-8
lines changed

7 files changed

+260
-8
lines changed

sample/android/src/main/java/co/yml/ychat/android/di/AppModule.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package co.yml.ychat.android.di
33
import co.yml.ychat.YChat
44
import co.yml.ychat.android.BuildConfig
55
import co.yml.ychat.android.presentation.chatcompletions.viewmodel.ChatCompletionsViewModel
6+
import co.yml.ychat.android.presentation.completions.CompletionsViewModel
67
import co.yml.ychat.android.presentation.home.viewmodel.HomeViewModel
78
import co.yml.ychat.android.presentation.models.viewmodel.ModelsViewModel
89
import org.koin.androidx.viewmodel.dsl.viewModelOf
@@ -13,4 +14,5 @@ val appModule = module {
1314
viewModelOf(::HomeViewModel)
1415
viewModelOf(::ChatCompletionsViewModel)
1516
viewModelOf(::ModelsViewModel)
17+
viewModelOf(::CompletionsViewModel)
1618
}
Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,57 @@
11
package co.yml.ychat.android.presentation.completions
22

33
import androidx.compose.foundation.background
4-
import androidx.compose.foundation.layout.Arrangement
54
import androidx.compose.foundation.layout.Column
65
import androidx.compose.foundation.layout.fillMaxHeight
6+
import androidx.compose.foundation.layout.fillMaxWidth
7+
import androidx.compose.foundation.layout.padding
78
import androidx.compose.runtime.Composable
89
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.res.stringResource
911
import androidx.compose.ui.tooling.preview.Preview
10-
import co.yml.ychat.android.presentation.models.ModelsScreen
11-
import co.yml.ychat.android.ui.components.feedback.Feedback
12-
import co.yml.ychat.android.ui.components.feedback.model.FeedbackState
12+
import co.yml.ychat.YChat
13+
import co.yml.ychat.android.BuildConfig
14+
import co.yml.ychat.android.R
15+
import co.yml.ychat.android.ui.components.button.ButtonContained
16+
import co.yml.ychat.android.ui.components.output.OutputBox
17+
import co.yml.ychat.android.ui.components.textfield.StandardTextField
18+
import co.yml.ychat.android.ui.theme.Dimens
1319
import co.yml.ychat.android.ui.theme.YChatTheme
20+
import org.koin.androidx.compose.getViewModel
1421

1522
@Composable
16-
internal fun CompletionsScreen() {
23+
internal fun CompletionsScreen(viewModel: CompletionsViewModel = getViewModel()) {
1724
Column(
1825
modifier = Modifier
1926
.background(YChatTheme.colors.background)
27+
.padding(Dimens.MD)
2028
.fillMaxHeight(),
21-
verticalArrangement = Arrangement.Center,
2229
) {
23-
Feedback(feedbackState = FeedbackState.CONSTRUCTION)
30+
StandardTextField(
31+
value = viewModel.message.value,
32+
hint = stringResource(id = R.string.completions_hint),
33+
modifier = Modifier.fillMaxWidth(),
34+
onTextChanged = { viewModel.onMessage(it) },
35+
enabled = viewModel.onEnableTextField.value,
36+
)
37+
ButtonContained(
38+
text = stringResource(id = R.string.completions_action),
39+
modifier = Modifier
40+
.fillMaxWidth()
41+
.padding(top = Dimens.MD, bottom = Dimens.XM),
42+
enabled = viewModel.onEnableButton.value,
43+
onClick = { viewModel.requestCompletions() }
44+
)
45+
OutputBox(outputBoxStates = viewModel.outputBoxStates)
2446
}
2547
}
2648

2749
@Preview
2850
@Composable
2951
private fun CompletionsScreenPreview() {
3052
YChatTheme {
31-
ModelsScreen()
53+
val yChat = YChat.create(BuildConfig.API_KEY)
54+
val viewModel = CompletionsViewModel(yChat)
55+
CompletionsScreen(viewModel)
3256
}
3357
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package co.yml.ychat.android.presentation.completions
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.ui.components.output.OutputBoxState
9+
import kotlinx.coroutines.launch
10+
11+
internal class CompletionsViewModel(private val yChat: YChat): ViewModel() {
12+
13+
val message = mutableStateOf("")
14+
15+
val outputBoxStates = mutableStateListOf<OutputBoxState>()
16+
17+
val onEnableButton = mutableStateOf(message.value.isNotEmpty())
18+
19+
val onEnableTextField = mutableStateOf(true)
20+
21+
fun requestCompletions() = viewModelScope.launch {
22+
val messageToSend = message.value
23+
outputBoxStates.clear()
24+
outputBoxStates.add(OutputBoxState.Text(messageToSend))
25+
onLoading(true)
26+
onMessage("")
27+
val completions = yChat.completion()
28+
.setMaxTokens(MAX_TOKENS)
29+
.setInput(messageToSend)
30+
runCatching { completions.execute() }
31+
.also { onLoading(false) }
32+
.onSuccess { outputBoxStates.add(OutputBoxState.Text(it, true)) }
33+
.onFailure { onError(true) }
34+
}
35+
36+
fun onMessage(message: String) {
37+
this.message.value = message
38+
onEnableButton.value = message.isNotEmpty()
39+
}
40+
41+
private fun onLoading(isLoading: Boolean) {
42+
if (isLoading) {
43+
onError(false)
44+
onEnableButton.value = false
45+
onEnableTextField.value = false
46+
outputBoxStates.add(OutputBoxState.Loading)
47+
} else {
48+
onEnableTextField.value = true
49+
outputBoxStates.remove(OutputBoxState.Loading)
50+
}
51+
}
52+
53+
private fun onError(isError: Boolean) {
54+
if (isError) {
55+
outputBoxStates.add(OutputBoxState.Error)
56+
} else {
57+
outputBoxStates.remove(OutputBoxState.Error)
58+
}
59+
}
60+
61+
companion object {
62+
private const val MAX_TOKENS = 240
63+
}
64+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package co.yml.ychat.android.ui.components.button
2+
3+
import androidx.compose.foundation.layout.PaddingValues
4+
import androidx.compose.foundation.shape.RoundedCornerShape
5+
import androidx.compose.material.Button
6+
import androidx.compose.material.ButtonDefaults
7+
import androidx.compose.material.Text
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.tooling.preview.Preview
11+
import co.yml.ychat.android.ui.theme.Dimens
12+
import co.yml.ychat.android.ui.theme.YChatTheme
13+
14+
@Composable
15+
internal fun ButtonContained(
16+
text: String,
17+
modifier: Modifier = Modifier,
18+
enabled: Boolean = true,
19+
onClick: () -> Unit = {},
20+
) {
21+
Button(
22+
modifier = modifier,
23+
enabled = enabled,
24+
shape = RoundedCornerShape(Dimens.XS),
25+
contentPadding = PaddingValues(Dimens.MD),
26+
colors = ButtonDefaults.buttonColors(
27+
backgroundColor = YChatTheme.colors.accent,
28+
contentColor = YChatTheme.colors.onAccent,
29+
disabledBackgroundColor = YChatTheme.colors.primary5,
30+
disabledContentColor = YChatTheme.colors.primary4
31+
),
32+
onClick = onClick
33+
) { Text(text = text) }
34+
}
35+
36+
@Preview
37+
@Composable
38+
private fun ButtonContainedPreview() {
39+
YChatTheme(darkTheme = false) {
40+
ButtonContained(
41+
text = "Preview",
42+
enabled = true
43+
)
44+
}
45+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package co.yml.ychat.android.ui.components.output
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
5+
import androidx.compose.foundation.layout.Arrangement
6+
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.heightIn
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.size
14+
import androidx.compose.foundation.lazy.LazyColumn
15+
import androidx.compose.foundation.lazy.items
16+
import androidx.compose.foundation.shape.RoundedCornerShape
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.ui.Alignment
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.draw.clip
21+
import androidx.compose.ui.graphics.Color
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.tooling.preview.Preview
24+
import androidx.compose.ui.unit.dp
25+
import co.yml.ychat.android.R
26+
import co.yml.ychat.android.ui.components.loading.TypingTextLoading
27+
import co.yml.ychat.android.ui.theme.Black1
28+
import co.yml.ychat.android.ui.theme.Dimens
29+
import co.yml.ychat.android.ui.theme.Green2
30+
import co.yml.ychat.android.ui.theme.Icons
31+
import co.yml.ychat.android.ui.theme.Red1
32+
import co.yml.ychat.android.ui.theme.TypographyStyle
33+
import co.yml.ychat.android.ui.theme.YChatTheme
34+
35+
@Composable
36+
internal fun OutputBox(
37+
outputBoxStates: List<OutputBoxState>,
38+
modifier: Modifier = Modifier,
39+
) {
40+
Box(
41+
modifier = modifier
42+
.fillMaxWidth()
43+
.background(YChatTheme.colors.primary5)
44+
.clip(RoundedCornerShape(Dimens.XS))
45+
.border(1.dp, YChatTheme.colors.primary4)
46+
.padding(vertical = 20.dp, horizontal = Dimens.MD)
47+
.heightIn(min = 200.dp),
48+
) {
49+
LazyColumn(verticalArrangement = Arrangement.spacedBy(Dimens.MD)) {
50+
items(outputBoxStates) {
51+
when (it) {
52+
is OutputBoxState.Error -> ErrorRow()
53+
is OutputBoxState.Text -> TextRow(it)
54+
is OutputBoxState.Loading -> TypingTextLoading()
55+
}
56+
}
57+
}
58+
}
59+
}
60+
61+
@Composable
62+
private fun TextRow(textState: OutputBoxState.Text) {
63+
val backgroundColor = if (textState.isMarked) Color.Green2 else Color.Transparent
64+
val textColor = if (textState.isMarked) Color.Black1 else YChatTheme.colors.text1
65+
TypographyStyle.MediumBody.Text(
66+
text = textState.text,
67+
modifier = Modifier.background(backgroundColor),
68+
color = textColor,
69+
)
70+
}
71+
72+
@Composable
73+
private fun ErrorRow() {
74+
Row(
75+
horizontalArrangement = Arrangement.spacedBy(Dimens.XS),
76+
verticalAlignment = Alignment.CenterVertically,
77+
) {
78+
Icons.Warning.Icon(tint = Color.Red1, modifier = Modifier.size(Dimens.MD))
79+
TypographyStyle.MediumBody.Text(
80+
text = stringResource(id = R.string.output_box_error_message)
81+
)
82+
}
83+
}
84+
85+
@Preview
86+
@Composable
87+
private fun ContainedButtonPreview() {
88+
val stateList = listOf(
89+
OutputBoxState.Text("Write a tagline for an ice cream shop."),
90+
OutputBoxState.Loading,
91+
OutputBoxState.Text("We serve up smiles with every scoop!", true),
92+
OutputBoxState.Error,
93+
)
94+
YChatTheme(darkTheme = false) {
95+
Column(
96+
modifier = Modifier
97+
.background(YChatTheme.colors.background)
98+
.fillMaxSize()
99+
) {
100+
OutputBox(stateList, Modifier.padding(Dimens.MD))
101+
}
102+
}
103+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package co.yml.ychat.android.ui.components.output
2+
3+
internal sealed class OutputBoxState {
4+
data class Text(val text: String, val isMarked: Boolean = false) : OutputBoxState()
5+
object Error : OutputBoxState()
6+
object Loading : OutputBoxState()
7+
}

sample/android/src/main/res/values/strings.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
<string name="home_chat_audio_title">Audio</string>
1616
<string name="home_chat_settings_title">Settings</string>
1717

18+
<!-- CompletionsScreen -->
19+
<string name="completions_hint">Write a tagline for an ice cream shop.</string>
20+
<string name="completions_action">Submit</string>
21+
1822
<!-- FeedbackState.ERROR -->
1923
<string name="feedback_state_error_title">Something went wrong</string>
2024
<string name="feedback_state_error_message">There was a problem with the request. Please try again in a few moments.</string>
@@ -28,4 +32,7 @@
2832
<string name="typing_loading_one">Typing.</string>
2933
<string name="typing_loading_two">Typing..</string>
3034
<string name="typing_loading_three">Typing…</string>
35+
36+
<!-- OutputBox -->
37+
<string name="output_box_error_message">Something went wrong. Try again.</string>
3138
</resources>

0 commit comments

Comments
 (0)