From 38a1dc79cd23d7a019ac52e5915447a2fec76b4d Mon Sep 17 00:00:00 2001 From: Maciej Procyk Date: Sat, 10 Feb 2024 19:14:39 +0100 Subject: [PATCH] cleanup client code --- .../kotlin/in/procyk/shin/MainActivity.kt | 6 +- .../commonMain/kotlin/in/procyk/shin/App.kt | 303 +----------------- .../kotlin/in/procyk/shin/HttpClient.kt | 15 + .../kotlin/in/procyk/shin/QrCode.kt | 35 ++ .../kotlin/in/procyk/shin/ShortenRequest.kt | 159 +++++++++ .../kotlin/in/procyk/shin/ShortenResponse.kt | 102 ++++++ .../in/procyk/shin/ShortenedProtocol.kt | 7 + composeApp/src/desktopMain/kotlin/main.kt | 2 +- .../src/iosMain/kotlin/MainViewController.kt | 2 +- composeApp/src/wasmJsMain/kotlin/main.kt | 2 +- shared/src/commonMain/kotlin/TypeUtil.kt | 1 + 11 files changed, 340 insertions(+), 294 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/in/procyk/shin/HttpClient.kt create mode 100644 composeApp/src/commonMain/kotlin/in/procyk/shin/QrCode.kt create mode 100644 composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenRequest.kt create mode 100644 composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenResponse.kt create mode 100644 composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenedProtocol.kt create mode 100644 shared/src/commonMain/kotlin/TypeUtil.kt diff --git a/composeApp/src/androidMain/kotlin/in/procyk/shin/MainActivity.kt b/composeApp/src/androidMain/kotlin/in/procyk/shin/MainActivity.kt index 42f4c6a..652313d 100644 --- a/composeApp/src/androidMain/kotlin/in/procyk/shin/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/in/procyk/shin/MainActivity.kt @@ -1,6 +1,6 @@ package `in`.procyk.shin -import App +import ShinApp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -12,7 +12,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - App() + ShinApp() } } } @@ -20,5 +20,5 @@ class MainActivity : ComponentActivity() { @Preview @Composable fun AppAndroidPreview() { - App() + ShinApp() } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt index ee69f6b..3c6ae40 100644 --- a/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/App.kt @@ -1,303 +1,30 @@ -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.Image -import androidx.compose.foundation.hoverable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsHoveredAsState -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.text.ClickableText -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.input.key.* -import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.* -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import `in`.procyk.compose.qrcode.QrData -import `in`.procyk.compose.qrcode.options.* -import `in`.procyk.compose.qrcode.rememberQrCodePainter -import io.ktor.client.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.resources.* -import io.ktor.client.statement.* -import io.ktor.http.* -import kotlinx.coroutines.launch +import `in`.procyk.shin.ShortenRequest +import `in`.procyk.shin.ShortenResponse +import `in`.procyk.shin.createHttpClient @Composable -fun App() { - val client = HttpClient { - install(Resources) - defaultRequest { - host = "api-shorten-kotlin.koyeb.app" - port = 443 - url { protocol = URLProtocol.HTTPS } - } - } +fun ShinApp() { + val client = remember { createHttpClient() } MaterialTheme { - var response by remember> { mutableStateOf(null) } + var shortenedUrl by remember> { mutableStateOf(null) } Column( modifier = Modifier .fillMaxSize() - .onKeyEvent { event -> - if (event.run { key == Key.Escape && type == KeyEventType.KeyDown }) { - response = null - true - } else false - }, + .onKeyEvent { event -> event.isEscDown.also { if (it) shortenedUrl = null } }, verticalArrangement = Arrangement.Center, ) { - ShortenRequest(client, onResponse = { response = it }) - - AnimatedVisibility( - visible = response != null, - enter = expandVertically(expandFrom = Alignment.Top), - exit = shrinkVertically(shrinkTowards = Alignment.Top), - ) { - response?.let { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.CenterVertically) - ) { - val uri by remember(it) { mutableStateOf(it.normalizeAsHttpUrl()) } - Spacer(Modifier.height(16.dp)) - val color = MaterialTheme.colors.primary - val annotatedString = remember(uri, color) { - buildAnnotatedString { - append(uri) - addStyle( - style = SpanStyle( - color = color, - fontSize = 18.sp, - ), - start = 0, - end = uri.length, - ) - addStyle( - style = ParagraphStyle(textAlign = TextAlign.Center), - start = 0, - end = uri.length, - ) - addStringAnnotation( - tag = URL_TAG, - annotation = uri, - start = 0, - end = uri.length - ) - } - } - val clipboardManager = LocalClipboardManager.current - val localUriHandler = LocalUriHandler.current - val interactionSource = remember { MutableInteractionSource() } - val isHovered by interactionSource.collectIsHoveredAsState() - ClickableText( - text = annotatedString, - modifier = Modifier - .fillMaxWidth() - .hoverable(interactionSource), - style = if (isHovered) TextStyle(textDecoration = TextDecoration.Underline) else TextStyle.Default, - onClick = { position -> - annotatedString - .getStringAnnotations(URL_TAG, position, position) - .single() - .let { - clipboardManager.setText(AnnotatedString(uri)) - localUriHandler.openUri(uri) - } - } - ) - Spacer(Modifier.height(16.dp)) - QrCode(uri) - Button( - onClick = { clipboardManager.setText(AnnotatedString(uri)) }, - ) { - Text("Copy to Clipboard") - } - } - } - } + ShortenRequest(client, onResponse = { shortenedUrl = it }) + ShortenResponse(shortenedUrl) } } } -@Composable -private fun ShortenRequest( - client: HttpClient, - onResponse: (String) -> Unit, - space: Dp = 8.dp, -) { - BoxWithConstraints { - val maxWidth = maxWidth - if (maxHeight > maxWidth) { - Column( - modifier = Modifier.fillMaxWidth().padding(16.dp), - verticalArrangement = Arrangement.spacedBy( - space = space, - alignment = Alignment.CenterVertically, - ), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - ShortenRequestElements(client, onResponse, fillMaxWidth = true, maxTextFieldWidth = maxWidth / 2) - } - } else { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy( - space = space, - alignment = Alignment.CenterHorizontally, - ), - verticalAlignment = Alignment.CenterVertically, - ) { - ShortenRequestElements(client, onResponse, fillMaxWidth = false, maxTextFieldWidth = maxWidth / 2) - } - } - } -} - -@Composable -private fun QrCode(url: String) { - val primaryColor = MaterialTheme.colors.primary - val secondaryColor = MaterialTheme.colors.secondary - val painter = rememberQrCodePainter( - data = QrData.text(url), - ) { - shapes { - ball = QrBallShape.circle() - darkPixel = QrPixelShape.roundCorners() - frame = QrFrameShape.roundCorners(.25f) - } - colors { - dark = QrBrush.brush { - Brush.linearGradient( - 0f to secondaryColor, - 1f to primaryColor, - end = Offset(it, it) - ) - } - } - } - Image(painter, "QR code") -} - -@Composable -private fun ShortenRequestElements( - client: HttpClient, - onResponse: (String) -> Unit, - fillMaxWidth: Boolean, - maxTextFieldWidth: Dp, -) { - var url by remember { mutableStateOf("") } - val scope = rememberCoroutineScope() - var shortenedProtocol by remember { mutableStateOf(ShortenedProtocol.entries.first()) } - - ProtocolChooser( - protocol = shortenedProtocol, - onChange = { shortenedProtocol = it }, - fillMaxWidth = fillMaxWidth - ) - val focusRequester = remember { FocusRequester() } - OutlinedTextField( - value = url, - onValueChange = { url = it }, - modifier = Modifier - .focusRequester(focusRequester) - .height(50.dp) - .applyIf(fillMaxWidth) { fillMaxWidth() } - .applyIf(!fillMaxWidth) { widthIn(max = maxTextFieldWidth) }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions( - onDone = { - scope.launch { - client.post(Shorten(normalizeUrl(url, shortenedProtocol))) - .takeIf { it.status == HttpStatusCode.OK } - ?.bodyAsText() - ?.let(onResponse) - } - } - ), - singleLine = true, - ) - Button( - modifier = Modifier.height(50.dp).applyIf(fillMaxWidth) { fillMaxWidth() }, - onClick = { - scope.launch { - client.post(Shorten(normalizeUrl(url, shortenedProtocol))) - .takeIf { it.status == HttpStatusCode.OK } - ?.bodyAsText() - ?.let(onResponse) - } - } - ) { - Text(text = "Shorten") - } - LaunchedEffect(focusRequester) { - focusRequester.requestFocus() - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun ProtocolChooser( - protocol: ShortenedProtocol, - onChange: (ShortenedProtocol) -> Unit, - fillMaxWidth: Boolean, -) { - var expanded by remember { mutableStateOf(false) } - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = it }, - ) { - OutlinedTextField( - value = protocol.presentableName, - onValueChange = {}, - readOnly = true, - modifier = Modifier.height(50.dp).applyIf(fillMaxWidth) { fillMaxWidth() }.width(128.dp), - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - ) - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - ShortenedProtocol.entries.forEach { protocol -> - DropdownMenuItem( - content = { Text(protocol.presentableName) }, - onClick = { - onChange(protocol) - expanded = false - }) - } - } - } -} - -private fun normalizeUrl(url: String, protocol: ShortenedProtocol): String { - val protocolDelimiter = "://" - val idx = url.indexOf(protocolDelimiter) - val noProtocolUrl = if (idx != -1) url.drop(idx + protocolDelimiter.length) else url - return protocol.presentableName + protocolDelimiter + noProtocolUrl -} - -private enum class ShortenedProtocol(val presentableName: String) { - HTTPS("https"), - HTTP("http"), - ; -} - -private fun String.normalizeAsHttpUrl() = applyIf(!contains("://")) { "http://$this" } - -private const val URL_TAG: String = "SHORT_URL_TAG" - -private inline fun T.applyIf(condition: Boolean, action: T.() -> T): T = if (condition) action(this) else this +private val KeyEvent.isEscDown: Boolean + get() = key == Key.Escape && type == KeyEventType.KeyDown diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/HttpClient.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/HttpClient.kt new file mode 100644 index 0000000..4586d7d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/HttpClient.kt @@ -0,0 +1,15 @@ +package `in`.procyk.shin + +import io.ktor.client.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.resources.* +import io.ktor.http.* + +internal fun createHttpClient(): HttpClient = HttpClient { + install(Resources) + defaultRequest { + host = "api-shorten-kotlin.koyeb.app" + port = 443 + url { protocol = URLProtocol.HTTPS } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/QrCode.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/QrCode.kt new file mode 100644 index 0000000..9f4268c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/QrCode.kt @@ -0,0 +1,35 @@ +package `in`.procyk.shin + +import androidx.compose.foundation.Image +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import `in`.procyk.compose.qrcode.QrData +import `in`.procyk.compose.qrcode.options.* +import `in`.procyk.compose.qrcode.rememberQrCodePainter + +@Composable +internal fun QrCode(url: String) { + val primaryColor = MaterialTheme.colors.primary + val secondaryColor = MaterialTheme.colors.secondary + val painter = rememberQrCodePainter( + data = QrData.text(url), + ) { + shapes { + ball = QrBallShape.circle() + darkPixel = QrPixelShape.roundCorners() + frame = QrFrameShape.roundCorners(.25f) + } + colors { + dark = QrBrush.brush { + Brush.linearGradient( + 0f to secondaryColor, + 1f to primaryColor, + end = Offset(it, it) + ) + } + } + } + Image(painter, "QR code") +} diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenRequest.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenRequest.kt new file mode 100644 index 0000000..678493d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenRequest.kt @@ -0,0 +1,159 @@ +package `in`.procyk.shin + +import Shorten +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import applyIf +import io.ktor.client.* +import io.ktor.client.plugins.resources.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +@Composable +internal fun ShortenRequest( + client: HttpClient, + onResponse: (String) -> Unit, + space: Dp = 8.dp, +) { + BoxWithConstraints { + val maxWidth = maxWidth + if (maxHeight > maxWidth) { + Column( + modifier = Modifier.fillMaxWidth().padding(16.dp), + verticalArrangement = Arrangement.spacedBy( + space = space, + alignment = Alignment.CenterVertically, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + ShortenRequestElements(client, onResponse, fillMaxWidth = true, maxTextFieldWidth = maxWidth / 2) + } + } else { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy( + space = space, + alignment = Alignment.CenterHorizontally, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + ShortenRequestElements(client, onResponse, fillMaxWidth = false, maxTextFieldWidth = maxWidth / 2) + } + } + } +} + +@Composable +private fun ShortenRequestElements( + client: HttpClient, + onResponse: (String) -> Unit, + fillMaxWidth: Boolean, + maxTextFieldWidth: Dp, +) { + var url by remember { mutableStateOf("") } + val scope = rememberCoroutineScope() + var shortenedProtocol by remember { mutableStateOf(ShortenedProtocol.entries.first()) } + + ProtocolChooser( + protocol = shortenedProtocol, + onChange = { shortenedProtocol = it }, + fillMaxWidth = fillMaxWidth + ) + val focusRequester = remember { FocusRequester() } + OutlinedTextField( + value = url, + onValueChange = { url = it }, + modifier = Modifier + .focusRequester(focusRequester) + .height(50.dp) + .applyIf(fillMaxWidth) { fillMaxWidth() } + .applyIf(!fillMaxWidth) { widthIn(max = maxTextFieldWidth) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { client.askForShortenedUrl(scope, url, shortenedProtocol, onResponse) } + ), + singleLine = true, + ) + Button( + modifier = Modifier.height(50.dp).applyIf(fillMaxWidth) { fillMaxWidth() }, + onClick = { client.askForShortenedUrl(scope, url, shortenedProtocol, onResponse) } + ) { + Text(text = "Shorten") + } + LaunchedEffect(focusRequester) { + focusRequester.requestFocus() + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun ProtocolChooser( + protocol: ShortenedProtocol, + onChange: (ShortenedProtocol) -> Unit, + fillMaxWidth: Boolean, +) { + var expanded by remember { mutableStateOf(false) } + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = it }, + ) { + OutlinedTextField( + value = protocol.presentableName, + onValueChange = {}, + readOnly = true, + modifier = Modifier + .height(50.dp) + .applyIf(fillMaxWidth) { fillMaxWidth() } + .width(128.dp), + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + ShortenedProtocol.entries.forEach { protocol -> + DropdownMenuItem( + content = { Text(protocol.presentableName) }, + onClick = { + onChange(protocol) + expanded = false + }) + } + } + } +} + +private fun normalizeInputUrl(url: String, protocol: ShortenedProtocol): String { + val protocolDelimiter = "://" + val idx = url.indexOf(protocolDelimiter) + val noProtocolUrl = if (idx != -1) url.drop(idx + protocolDelimiter.length) else url + return protocol.presentableName + protocolDelimiter + noProtocolUrl +} + +private fun HttpClient.askForShortenedUrl( + scope: CoroutineScope, + url: String, + shortenedProtocol: ShortenedProtocol, + onResponse: (String) -> Unit, +): Job = scope.launch { + try { + post(Shorten(normalizeInputUrl(url, shortenedProtocol))) + .takeIf { it.status == HttpStatusCode.OK } + ?.bodyAsText() + ?.let(onResponse) + } catch (_: Exception) { + } +} diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenResponse.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenResponse.kt new file mode 100644 index 0000000..cbf65a4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenResponse.kt @@ -0,0 +1,102 @@ +package `in`.procyk.shin + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.* +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import applyIf + +@Composable +internal fun ShortenResponse(shortenedUrl: String?) { + AnimatedVisibility( + visible = shortenedUrl != null, + enter = expandVertically(expandFrom = Alignment.Top), + exit = shrinkVertically(shrinkTowards = Alignment.Top), + ) { + shortenedUrl?.let { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.CenterVertically) + ) { + val uri by remember(it) { mutableStateOf(it.normalizeAsHttpUrl()) } + Spacer(Modifier.height(16.dp)) + val color = MaterialTheme.colors.primary + val annotatedString = remember(uri, color) { + buildAnnotatedString { + append(uri) + addStyle( + style = SpanStyle( + color = color, + fontSize = 18.sp, + ), + start = 0, + end = uri.length, + ) + addStyle( + style = ParagraphStyle(textAlign = TextAlign.Center), + start = 0, + end = uri.length, + ) + addStringAnnotation( + tag = URL_TAG, + annotation = uri, + start = 0, + end = uri.length + ) + } + } + val clipboardManager = LocalClipboardManager.current + val localUriHandler = LocalUriHandler.current + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + ClickableText( + text = annotatedString, + modifier = Modifier + .fillMaxWidth() + .hoverable(interactionSource), + style = if (isHovered) TextStyle(textDecoration = TextDecoration.Underline) else TextStyle.Default, + onClick = { position -> + annotatedString + .getStringAnnotations(URL_TAG, position, position) + .single() + .let { + clipboardManager.setText(AnnotatedString(uri)) + localUriHandler.openUri(uri) + } + } + ) + Spacer(Modifier.height(16.dp)) + QrCode(uri) + Button( + onClick = { clipboardManager.setText(AnnotatedString(uri)) }, + ) { + Text("Copy to Clipboard") + } + } + } + } +} + +private fun String.normalizeAsHttpUrl() = applyIf(!contains("://")) { "http://$this" } + +private const val URL_TAG: String = "SHORT_URL_TAG" diff --git a/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenedProtocol.kt b/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenedProtocol.kt new file mode 100644 index 0000000..8460ede --- /dev/null +++ b/composeApp/src/commonMain/kotlin/in/procyk/shin/ShortenedProtocol.kt @@ -0,0 +1,7 @@ +package `in`.procyk.shin + +internal enum class ShortenedProtocol(val presentableName: String) { + HTTPS("https"), + HTTP("http"), + ; +} diff --git a/composeApp/src/desktopMain/kotlin/main.kt b/composeApp/src/desktopMain/kotlin/main.kt index ff29bed..e33e098 100644 --- a/composeApp/src/desktopMain/kotlin/main.kt +++ b/composeApp/src/desktopMain/kotlin/main.kt @@ -3,6 +3,6 @@ import androidx.compose.ui.window.application fun main() = application { Window(onCloseRequest = ::exitApplication, title = "shin") { - App() + ShinApp() } } diff --git a/composeApp/src/iosMain/kotlin/MainViewController.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt index f483884..cfcde6b 100644 --- a/composeApp/src/iosMain/kotlin/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/MainViewController.kt @@ -1,3 +1,3 @@ import androidx.compose.ui.window.ComposeUIViewController -fun MainViewController() = ComposeUIViewController { App() } +fun MainViewController() = ComposeUIViewController { ShinApp() } diff --git a/composeApp/src/wasmJsMain/kotlin/main.kt b/composeApp/src/wasmJsMain/kotlin/main.kt index b9c0f64..a79783f 100644 --- a/composeApp/src/wasmJsMain/kotlin/main.kt +++ b/composeApp/src/wasmJsMain/kotlin/main.kt @@ -3,5 +3,5 @@ import androidx.compose.ui.window.CanvasBasedWindow @OptIn(ExperimentalComposeUiApi::class) fun main() { - CanvasBasedWindow(canvasElementId = "ComposeTarget") { App() } + CanvasBasedWindow(canvasElementId = "ComposeTarget") { ShinApp() } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/TypeUtil.kt b/shared/src/commonMain/kotlin/TypeUtil.kt new file mode 100644 index 0000000..9736051 --- /dev/null +++ b/shared/src/commonMain/kotlin/TypeUtil.kt @@ -0,0 +1 @@ +inline fun T.applyIf(condition: Boolean, action: T.() -> T): T = if (condition) action(this) else this