diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt index e0d56d4..cc37064 100644 --- a/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt @@ -1,6 +1,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.* @@ -8,23 +12,47 @@ import `in`.procyk.shin.createHttpClient import `in`.procyk.shin.ui.ShortenRequest import `in`.procyk.shin.ui.ShortenResponse import `in`.procyk.shin.ui.theme.ShinTheme +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @Composable fun ShinApp() { val client = remember { createHttpClient() } ShinTheme { - var shortenedUrl by remember> { mutableStateOf(null) } - Column( - modifier = Modifier - .fillMaxSize() - .onKeyEvent { event -> event.isEscDown.also { if (it) shortenedUrl = null } }, - verticalArrangement = Arrangement.Center, + val snackbarHostState = remember { SnackbarHostState() } + val snackbarHostStateScope = rememberCoroutineScope() + Scaffold( + snackbarHost = { SnackbarHost(hostState = snackbarHostState) } ) { - ShortenRequest(client, onResponse = { shortenedUrl = it }) - ShortenResponse(shortenedUrl) + var shortenedUrl by remember> { mutableStateOf(null) } + Column( + modifier = Modifier + .fillMaxSize() + .onKeyEvent { event -> event.isEscDown.also { if (it) shortenedUrl = null } }, + verticalArrangement = Arrangement.Center, + ) { + ShortenRequest( + client = client, + onResponse = { shortenedUrl = it }, + onError = { snackbarHostStateScope.showErrorSnackbarNotification(snackbarHostState, it) }) + ShortenResponse(shortenedUrl) + } } } } +private fun CoroutineScope.showErrorSnackbarNotification( + snackbarHostState: SnackbarHostState, + message: String, +) { + launch { + snackbarHostState.showSnackbar( + message = message, + withDismissAction = true, + duration = SnackbarDuration.Short, + ) + } +} + private val KeyEvent.isEscDown: Boolean get() = key == Key.Escape && type == KeyEventType.KeyDown diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/ui/ShortenRequest.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/ui/ShortenRequest.kt index 8251861..d0197c6 100644 --- a/composeApp/src/commonMain/kotlin/in/procyk/shin/ui/ShortenRequest.kt +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/ui/ShortenRequest.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.launch internal fun ShortenRequest( client: HttpClient, onResponse: (String) -> Unit, + onError: (String) -> Unit, space: Dp = 8.dp, ) { BoxWithConstraints { @@ -42,7 +43,13 @@ internal fun ShortenRequest( ), horizontalAlignment = Alignment.CenterHorizontally, ) { - ShortenRequestElements(client, onResponse, fillMaxWidth = true, maxTextFieldWidth = maxWidth / 2) + ShortenRequestElements( + client = client, + onResponse = onResponse, + onError = onError, + fillMaxWidth = true, + maxTextFieldWidth = maxWidth / 2 + ) } } else { Row( @@ -53,7 +60,13 @@ internal fun ShortenRequest( ), verticalAlignment = Alignment.Bottom, ) { - ShortenRequestElements(client, onResponse, fillMaxWidth = false, maxTextFieldWidth = maxWidth / 2) + ShortenRequestElements( + client = client, + onResponse = onResponse, + onError = onError, + fillMaxWidth = false, + maxTextFieldWidth = maxWidth / 2 + ) } } } @@ -63,6 +76,7 @@ internal fun ShortenRequest( private fun ShortenRequestElements( client: HttpClient, onResponse: (String) -> Unit, + onError: (String) -> Unit, fillMaxWidth: Boolean, maxTextFieldWidth: Dp, ) { @@ -91,7 +105,7 @@ private fun ShortenRequestElements( .applyIf(!fillMaxWidth) { widthIn(max = maxTextFieldWidth) }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions( - onDone = { client.askForShortenedUrl(scope, url, shortenedProtocol, onResponse) } + onDone = { client.askForShortenedUrl(scope, url, shortenedProtocol, onResponse, onError) } ), singleLine = true, shape = RoundedCornerShape(12.dp), @@ -101,7 +115,7 @@ private fun ShortenRequestElements( .height(56.dp) .applyIf(fillMaxWidth) { fillMaxWidth() }, shape = RoundedCornerShape(12.dp), - onClick = { client.askForShortenedUrl(scope, url, shortenedProtocol, onResponse) } + onClick = { client.askForShortenedUrl(scope, url, shortenedProtocol, onResponse, onError) } ) { Text( text = "Shorten", @@ -164,6 +178,7 @@ private fun HttpClient.askForShortenedUrl( url: String, shortenedProtocol: ShortenedProtocol, onResponse: (String) -> Unit, + onError: (String) -> Unit, ): Job = scope.launch { try { post(Shorten(shortenedProtocol.buildUrl(url))) @@ -171,5 +186,6 @@ private fun HttpClient.askForShortenedUrl( ?.bodyAsText() ?.let(onResponse) } catch (_: Exception) { + onError("Cannot connect to Shin. Try again later…") } }