diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 032e261..7f85e04 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,17 +30,24 @@ android { ) } } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } + kotlinOptions { jvmTarget = "11" } + buildFeatures { compose = true buildConfig = true } + + androidResources { + generateLocaleConfig = true + } } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a9b1d99..4213431 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,6 @@ @@ -30,7 +29,6 @@ diff --git a/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt b/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt index 5cac2b3..82fe99a 100644 --- a/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt +++ b/app/src/main/java/eu/m724/chatapp/activity/chat/ChatActivity.kt @@ -39,7 +39,9 @@ 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 import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass @@ -59,9 +61,11 @@ 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.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.LanguageModelMistakeWarning @@ -92,6 +96,7 @@ class ChatActivity : ComponentActivity() { val coroutineScope = rememberCoroutineScope() val chatState = rememberChatState() val threadViewLazyListState = rememberLazyListState() + val snackbarHostState = remember { SnackbarHostState() } val onSend = { if (chatState.composerValue.isNotBlank() && !uiState.requestInProgress) { @@ -104,6 +109,7 @@ class ChatActivity : ComponentActivity() { uiState = uiState, chatState = chatState, threadViewLazyListState = threadViewLazyListState, + snackbarHostState = snackbarHostState, onSend = onSend, onRequestFocus = { if (uiState.requestInProgress) return@ChatScreen @@ -128,9 +134,11 @@ class ChatActivity : ComponentActivity() { // scroll to the last user message threadViewLazyListState.animateScrollToItem(uiState.messages.size - 2) - } else if (uiState.messages.isNotEmpty()) { - // scroll to the last user message too - threadViewLazyListState.animateScrollToItem(uiState.messages.size - 2) + } else { + if (uiState.messages.isNotEmpty()) { + // scroll to the last user message too + threadViewLazyListState.animateScrollToItem(uiState.messages.size - 2) + } // if the composer is visible (message is short enough), focus on it // if the message is long, we let the user read it @@ -147,16 +155,12 @@ class ChatActivity : ComponentActivity() { viewModel.uiEvents.collect { event -> when (event) { is ChatActivityUiEvent.Error -> { - Toast.makeText(context, event.error, Toast.LENGTH_SHORT) - .show() // TODO better way of showing this. snackbar? + // TODO add a smart action, for example check api key if unauthorized etc - // the user might have scrolled so this is good - threadViewLazyListState.layoutInfo.visibleItemsInfo.firstOrNull { - it.key == "composer" - }?.let { - chatState.requestFocus() - softwareKeyboardController?.show() // TODO perhaps it's pointless to focus since we can click on the toolbar? maybe make it configurable - } + snackbarHostState.showSnackbar( + message = event.error, // TODO consider a generic message + withDismissAction = true + ) } } } @@ -171,6 +175,7 @@ fun ChatScreen( uiState: ChatActivityUiState, chatState: ChatState, threadViewLazyListState: LazyListState, + snackbarHostState: SnackbarHostState, onSend: () -> Unit, onRequestFocus: () -> Unit ) { @@ -180,7 +185,14 @@ fun ChatScreen( Scaffold( modifier = Modifier.fillMaxSize(), topBar = { - ChatTopAppBar(uiState.chatTitle ?: "Start a new conversation") + ChatTopAppBar( + title = uiState.chatTitle ?: stringResource(R.string.title_new_conversation) + ) + }, + snackbarHost = { + SnackbarHost( + hostState = snackbarHostState + ) } ) { innerPadding -> ChatScreenContent( @@ -370,7 +382,9 @@ fun ChatMessageComposer( value = value, onValueChange = onValueChange, placeholder = { - Text("Type your message...") // TODO hide when just browsing history? + Text( + text = stringResource(R.string.composer_placeholder_type) + ) // TODO hide when just browsing history? }, padding = PaddingValues(vertical = 10.dp), textStyle = LocalTextStyle.current.copy( @@ -417,7 +431,7 @@ fun ChatToolBar( ) { Icon( imageVector = Icons.AutoMirrored.Filled.Send, - contentDescription = "Send" + contentDescription = stringResource(R.string.button_send_icon_description) ) } } diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties new file mode 100644 index 0000000..d5a3ddc --- /dev/null +++ b/app/src/main/res/resources.properties @@ -0,0 +1 @@ +unqualifiedResLocale=en-US \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 279a8d3..8980927 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ - Chat App - MainActivity - ChatActivity + Chat + Start a new conversation + Send message + Type your message… \ No newline at end of file