Move stuff around
This commit is contained in:
parent
818bc419b2
commit
95ea10b305
13 changed files with 250 additions and 190 deletions
|
@ -5,20 +5,15 @@ import androidx.activity.ComponentActivity
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
|
@ -26,17 +21,8 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
|
@ -57,21 +43,21 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.m724.chatapp.R
|
||||
import eu.m724.chatapp.activity.chat.ChatState.Companion.rememberChatState
|
||||
import eu.m724.chatapp.activity.chat.composable.AnimatedChangingText
|
||||
import eu.m724.chatapp.activity.chat.composable.ChatToolBar
|
||||
import eu.m724.chatapp.activity.chat.state.ChatComposerState.Companion.rememberChatComposerState
|
||||
import eu.m724.chatapp.activity.ui.composable.AnimatedChangingText
|
||||
import eu.m724.chatapp.activity.chat.composable.LanguageModelMistakeWarning
|
||||
import eu.m724.chatapp.activity.chat.composable.NestedScrollKeyboardHider
|
||||
import eu.m724.chatapp.activity.chat.composable.SimpleTextFieldWithPadding
|
||||
import eu.m724.chatapp.activity.chat.composable.disableBringIntoViewOnFocus
|
||||
import eu.m724.chatapp.activity.chat.composable.thread.ChatMessageComposer
|
||||
import eu.m724.chatapp.activity.chat.composable.thread.ChatResponseErrorNotice
|
||||
import eu.m724.chatapp.activity.chat.state.ChatComposerState
|
||||
import eu.m724.chatapp.activity.ui.composable.disableBringIntoViewOnFocus
|
||||
import eu.m724.chatapp.activity.ui.composable.hideKeyboardOnScrollUp
|
||||
import eu.m724.chatapp.activity.ui.theme.ChatAppTheme
|
||||
import eu.m724.chatapp.api.data.response.completion.ChatMessage
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -91,10 +77,9 @@ class ChatActivity : ComponentActivity() {
|
|||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
val softwareKeyboardController = LocalSoftwareKeyboardController.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val chatState = rememberChatState()
|
||||
val chatState = rememberChatComposerState()
|
||||
val threadViewLazyListState = rememberLazyListState()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
|
@ -111,7 +96,7 @@ class ChatActivity : ComponentActivity() {
|
|||
ChatScreen(
|
||||
windowSizeClass = windowSizeClass,
|
||||
uiState = uiState,
|
||||
chatState = chatState,
|
||||
chatComposerState = chatState,
|
||||
threadViewLazyListState = threadViewLazyListState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onSend = onSend,
|
||||
|
@ -181,7 +166,7 @@ class ChatActivity : ComponentActivity() {
|
|||
fun ChatScreen(
|
||||
windowSizeClass: WindowSizeClass,
|
||||
uiState: ChatActivityUiState,
|
||||
chatState: ChatState,
|
||||
chatComposerState: ChatComposerState,
|
||||
threadViewLazyListState: LazyListState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onSend: () -> Unit,
|
||||
|
@ -212,7 +197,7 @@ fun ChatScreen(
|
|||
.padding(innerPadding),
|
||||
isTablet = isTablet,
|
||||
uiState = uiState,
|
||||
chatState = chatState,
|
||||
chatComposerState = chatComposerState,
|
||||
threadViewLazyListState = threadViewLazyListState,
|
||||
onSend = onSend,
|
||||
onRequestFocus = onRequestFocus
|
||||
|
@ -226,7 +211,7 @@ fun ChatScreenContent(
|
|||
modifier: Modifier = Modifier,
|
||||
isTablet: Boolean,
|
||||
uiState: ChatActivityUiState,
|
||||
chatState: ChatState,
|
||||
chatComposerState: ChatComposerState,
|
||||
threadViewLazyListState: LazyListState,
|
||||
onSend: () -> Unit,
|
||||
onRequestFocus: () -> Unit
|
||||
|
@ -263,7 +248,7 @@ fun ChatScreenContent(
|
|||
lazyListState = threadViewLazyListState,
|
||||
messages = uiState.messages,
|
||||
uiState = uiState,
|
||||
chatState = chatState
|
||||
chatComposerState = chatComposerState
|
||||
)
|
||||
},
|
||||
{
|
||||
|
@ -275,8 +260,8 @@ fun ChatScreenContent(
|
|||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
ChatToolBar(
|
||||
canSend = (chatState.composerValue.isNotBlank() || uiState.lastResponseError != null) && !uiState.requestInProgress,
|
||||
canRestart = chatState.composerValue.isBlank() && uiState.lastResponseError != null,
|
||||
canSend = (chatComposerState.composerValue.isNotBlank() || uiState.lastResponseError != null) && !uiState.requestInProgress,
|
||||
canRestart = chatComposerState.composerValue.isBlank() && uiState.lastResponseError != null,
|
||||
onSend = onSend,
|
||||
onEmptySpaceClick = onRequestFocus
|
||||
)
|
||||
|
@ -295,16 +280,14 @@ fun ThreadView(
|
|||
lazyListState: LazyListState,
|
||||
messages: List<ChatMessage>,
|
||||
uiState: ChatActivityUiState,
|
||||
chatState: ChatState,
|
||||
chatComposerState: ChatComposerState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val localSoftwareKeyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.nestedScroll( // Hides the keyboard when scrolling
|
||||
NestedScrollKeyboardHider(localSoftwareKeyboardController)
|
||||
),
|
||||
.hideKeyboardOnScrollUp(localSoftwareKeyboardController!!),
|
||||
state = lazyListState
|
||||
) {
|
||||
items(messages) { message ->
|
||||
|
@ -323,7 +306,7 @@ fun ThreadView(
|
|||
|
||||
if (uiState.lastResponseError != null) {
|
||||
item(key = "error") {
|
||||
ResponseErrorDisplay(
|
||||
ChatResponseErrorNotice(
|
||||
error = uiState.lastResponseError
|
||||
)
|
||||
}
|
||||
|
@ -335,10 +318,10 @@ fun ThreadView(
|
|||
modifier = Modifier
|
||||
.fillParentMaxHeight() // so that you can click anywhere on the screen to focus the text field
|
||||
.disableBringIntoViewOnFocus()
|
||||
.focusRequester(chatState.focusRequester),
|
||||
value = chatState.composerValue,
|
||||
.focusRequester(chatComposerState.focusRequester),
|
||||
value = chatComposerState.composerValue,
|
||||
onValueChange = {
|
||||
chatState.composerValue = it
|
||||
chatComposerState.composerValue = it
|
||||
}
|
||||
)
|
||||
} else {
|
||||
|
@ -381,36 +364,6 @@ fun ChatMessagePrompt(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResponseErrorDisplay(
|
||||
error: ChatResponseError,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.height(18.dp)
|
||||
.padding(horizontal = 4.dp),
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = stringResource(R.string.response_error_icon_description),
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
|
||||
val errorMessage = when (error) {
|
||||
is ChatResponseError.LengthLimit -> stringResource(R.string.response_error_length_limit)
|
||||
is ChatResponseError.Error -> stringResource(R.string.response_error_generic)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = errorMessage,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatMessageResponse(
|
||||
content: String,
|
||||
|
@ -422,80 +375,7 @@ fun ChatMessageResponse(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatMessageComposer(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
SimpleTextFieldWithPadding(
|
||||
modifier = modifier,
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.composer_placeholder_type)
|
||||
) // TODO hide when just browsing history?
|
||||
},
|
||||
padding = PaddingValues(vertical = 10.dp),
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatToolBar(
|
||||
modifier: Modifier = Modifier,
|
||||
canSend: Boolean,
|
||||
canRestart: Boolean,
|
||||
onSend: () -> Unit,
|
||||
onEmptySpaceClick: () -> Unit
|
||||
) {
|
||||
val sendButtonColor by animateColorAsState(
|
||||
targetValue = if (canSend) {
|
||||
IconButtonDefaults.iconButtonColors().contentColor
|
||||
} else {
|
||||
IconButtonDefaults.iconButtonColors().disabledContentColor
|
||||
}, label = "sendButtonColor"
|
||||
)
|
||||
|
||||
ElevatedCard(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onEmptySpaceClick),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onSend,
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
.padding(horizontal = 8.dp),
|
||||
enabled = canSend,
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
contentColor = sendButtonColor,
|
||||
disabledContentColor = sendButtonColor
|
||||
)
|
||||
) {
|
||||
if (canRestart) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.outline_restart_alt_24),
|
||||
contentDescription = stringResource(R.string.button_send_restart_icon_description)
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.Send,
|
||||
contentDescription = stringResource(R.string.button_send_icon_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package eu.m724.chatapp.activity.chat
|
||||
|
||||
import eu.m724.chatapp.activity.chat.state.ChatResponseError
|
||||
import eu.m724.chatapp.api.data.response.completion.ChatMessage
|
||||
|
||||
data class ChatActivityUiState(
|
||||
|
@ -19,5 +20,4 @@ data class ChatActivityUiState(
|
|||
* All messages in the chat
|
||||
*/
|
||||
val messages: List<ChatMessage> = emptyList()
|
||||
)
|
||||
|
||||
)
|
|
@ -3,6 +3,7 @@ package eu.m724.chatapp.activity.chat
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.m724.chatapp.activity.chat.state.ChatResponseError
|
||||
import eu.m724.chatapp.api.AiApiService
|
||||
import eu.m724.chatapp.api.data.request.completion.ChatCompletionRequest
|
||||
import eu.m724.chatapp.api.data.response.completion.ChatCompletionResponseEvent
|
||||
|
@ -58,10 +59,12 @@ class ChatActivityViewModel @Inject constructor(
|
|||
messages.removeLast()
|
||||
} // If there was an error and no response was generated, this shouldn't be a follow-up
|
||||
|
||||
messages.add(ChatMessage(
|
||||
role = ChatMessage.Role.User,
|
||||
content = promptContent
|
||||
))
|
||||
messages.add(
|
||||
ChatMessage(
|
||||
role = ChatMessage.Role.User,
|
||||
content = promptContent
|
||||
)
|
||||
)
|
||||
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
|
@ -75,14 +78,16 @@ class ChatActivityViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
aiApiService.getChatCompletion(ChatCompletionRequest(
|
||||
model = "fre-model",
|
||||
messages = messages,
|
||||
temperature = 1.0f,
|
||||
maxTokens = 4,
|
||||
frequencyPenalty = 0.0f,
|
||||
presencePenalty = 0.0f
|
||||
)).onEach { event ->
|
||||
aiApiService.getChatCompletion(
|
||||
ChatCompletionRequest(
|
||||
model = "free-model",
|
||||
messages = messages,
|
||||
temperature = 1.0f,
|
||||
maxTokens = 4,
|
||||
frequencyPenalty = 0.0f,
|
||||
presencePenalty = 0.0f
|
||||
)
|
||||
).onEach { event ->
|
||||
when (event) {
|
||||
is SseEvent.Open -> {
|
||||
// There is nothing to do here
|
||||
|
@ -110,10 +115,12 @@ class ChatActivityViewModel @Inject constructor(
|
|||
is SseEvent.Closed -> {
|
||||
// Closed is not used in case of an error
|
||||
|
||||
messages.add(ChatMessage(
|
||||
role = ChatMessage.Role.Assistant,
|
||||
content = responseContent
|
||||
))
|
||||
messages.add(
|
||||
ChatMessage(
|
||||
role = ChatMessage.Role.Assistant,
|
||||
content = responseContent
|
||||
)
|
||||
)
|
||||
}
|
||||
is SseEvent.Failure -> {
|
||||
// The below should do. More investigation is needed but I believe this should do
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package eu.m724.chatapp.activity.chat.composable
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.m724.chatapp.R
|
||||
|
||||
@Composable
|
||||
fun ChatToolBar(
|
||||
modifier: Modifier = Modifier,
|
||||
canSend: Boolean,
|
||||
canRestart: Boolean,
|
||||
onSend: () -> Unit,
|
||||
onEmptySpaceClick: () -> Unit
|
||||
) {
|
||||
val sendButtonColor by animateColorAsState(
|
||||
targetValue = if (canSend) {
|
||||
IconButtonDefaults.iconButtonColors().contentColor
|
||||
} else {
|
||||
IconButtonDefaults.iconButtonColors().disabledContentColor
|
||||
}, label = "sendButtonColor"
|
||||
)
|
||||
|
||||
ElevatedCard(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onEmptySpaceClick),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onSend,
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
.padding(horizontal = 8.dp),
|
||||
enabled = canSend,
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
contentColor = sendButtonColor,
|
||||
disabledContentColor = sendButtonColor
|
||||
)
|
||||
) {
|
||||
if (canRestart) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.outline_restart_alt_24),
|
||||
contentDescription = stringResource(R.string.button_send_restart_icon_description)
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.Send,
|
||||
contentDescription = stringResource(R.string.button_send_icon_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package eu.m724.chatapp.activity.chat.composable
|
||||
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||
|
||||
class NestedScrollKeyboardHider(
|
||||
private val softwareKeyboardController: SoftwareKeyboardController?
|
||||
) : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
if (available.y > 0) // if scrolling up (content up, finger down)
|
||||
softwareKeyboardController?.hide()
|
||||
return Offset.Companion.Zero
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package eu.m724.chatapp.activity.chat.composable.thread
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.m724.chatapp.R
|
||||
import eu.m724.chatapp.activity.ui.composable.LessBasicTextField
|
||||
|
||||
@Composable
|
||||
fun ChatMessageComposer(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LessBasicTextField(
|
||||
modifier = modifier,
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.composer_placeholder_type)
|
||||
) // TODO hide when just browsing history?
|
||||
},
|
||||
padding = PaddingValues(vertical = 10.dp),
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package eu.m724.chatapp.activity.chat.composable.thread
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.m724.chatapp.R
|
||||
import eu.m724.chatapp.activity.chat.state.ChatResponseError
|
||||
|
||||
@Composable
|
||||
fun ChatResponseErrorNotice(
|
||||
error: ChatResponseError,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.height(18.dp)
|
||||
.padding(horizontal = 4.dp),
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = stringResource(R.string.response_error_icon_description),
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
|
||||
val errorMessage = when (error) {
|
||||
is ChatResponseError.LengthLimit -> stringResource(R.string.response_error_length_limit)
|
||||
is ChatResponseError.Error -> stringResource(R.string.response_error_generic)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = errorMessage,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package eu.m724.chatapp.activity.chat
|
||||
package eu.m724.chatapp.activity.chat.state
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -7,7 +7,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
|
||||
class ChatState(
|
||||
class ChatComposerState(
|
||||
val focusRequester: FocusRequester
|
||||
) {
|
||||
var composerValue by mutableStateOf("")
|
||||
|
@ -18,14 +18,12 @@ class ChatState(
|
|||
|
||||
companion object {
|
||||
@Composable
|
||||
fun rememberChatState(
|
||||
fun rememberChatComposerState(
|
||||
focusRequester: FocusRequester = remember { FocusRequester() }
|
||||
): ChatState {
|
||||
): ChatComposerState {
|
||||
return remember {
|
||||
ChatState(focusRequester = focusRequester)
|
||||
ChatComposerState(focusRequester = focusRequester)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package eu.m724.chatapp.activity.chat
|
||||
package eu.m724.chatapp.activity.chat.state
|
||||
|
||||
sealed interface ChatResponseError { // TODO does this belong here?
|
||||
data object LengthLimit: ChatResponseError
|
|
@ -1,4 +1,4 @@
|
|||
package eu.m724.chatapp.activity.chat.composable
|
||||
package eu.m724.chatapp.activity.ui.composable
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.tween
|
||||
|
@ -11,6 +11,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
||||
/**
|
||||
* A text that transitions nicely when changed.
|
||||
* Not appropriate for completing text.
|
||||
*/
|
||||
@Composable
|
||||
fun AnimatedChangingText(
|
||||
text: String,
|
|
@ -1,4 +1,4 @@
|
|||
package eu.m724.chatapp.activity.chat.composable
|
||||
package eu.m724.chatapp.activity.ui.composable
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
|
@ -40,5 +40,7 @@ private class DisableBringIntoViewOnFocusElement : ModifierNodeElement<DisableBr
|
|||
|
||||
}
|
||||
|
||||
// This is the public Modifier function you will use in your code.
|
||||
/**
|
||||
* Disables bringing this item (scrolling to accommodate it, for instance, in a column) into view when it is focused.
|
||||
*/
|
||||
fun Modifier.disableBringIntoViewOnFocus(): Modifier = this.then(DisableBringIntoViewOnFocusElement())
|
|
@ -1,4 +1,4 @@
|
|||
package eu.m724.chatapp.activity.chat.composable
|
||||
package eu.m724.chatapp.activity.ui.composable
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
@ -14,9 +14,12 @@ import androidx.compose.ui.graphics.SolidColor
|
|||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
|
||||
/**
|
||||
* A text field that's not as basic as a BasicTextField, but not as bloated as the standard TextField.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SimpleTextFieldWithPadding(
|
||||
fun LessBasicTextField(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
placeholder: @Composable () -> Unit,
|
|
@ -0,0 +1,25 @@
|
|||
package eu.m724.chatapp.activity.ui.composable
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||
|
||||
class NestedScrollKeyboardHider(
|
||||
private val softwareKeyboardController: SoftwareKeyboardController
|
||||
) : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
if (available.y > 0) // scroll up (content up, finger down)
|
||||
softwareKeyboardController.hide()
|
||||
return Offset.Companion.Zero
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the keyboard when scrolling up in a column.
|
||||
*/
|
||||
fun Modifier.hideKeyboardOnScrollUp(
|
||||
softwareKeyboardController: SoftwareKeyboardController
|
||||
): Modifier = this.nestedScroll(NestedScrollKeyboardHider(softwareKeyboardController))
|
Loading…
Add table
Add a link
Reference in a new issue