From cc5d19a4055197a62fbaf6922b0f8c55895a5486 Mon Sep 17 00:00:00 2001 From: Anand Bose Date: Tue, 31 Dec 2024 09:03:56 +0530 Subject: [PATCH] Updates * Refactor code with LazyVerticalGrid and LazyColumn * Add MIT License --- LICENSE | 21 ++ .../commonMain/kotlin/dev/anandbose/App.kt | 251 ------------------ .../kotlin/dev/anandbose/data/Links.kt | 53 ++++ .../commonMain/kotlin/dev/anandbose/main.kt | 1 + .../kotlin/dev/anandbose/ui/app/App.kt | 160 +++++++++++ .../dev/anandbose/ui/util/HyperlinkBuilder.kt | 38 +++ .../dev/anandbose/ui/widget/UrlButtonLarge.kt | 51 ++++ .../dev/anandbose/ui/widget/UrlButtonSmall.kt | 44 +++ 8 files changed, 368 insertions(+), 251 deletions(-) create mode 100644 LICENSE delete mode 100644 composeApp/src/commonMain/kotlin/dev/anandbose/App.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/anandbose/data/Links.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/anandbose/ui/app/App.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/anandbose/ui/util/HyperlinkBuilder.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonLarge.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonSmall.kt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a0f9ed6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Anand Bose + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/anandbose/App.kt b/composeApp/src/commonMain/kotlin/dev/anandbose/App.kt deleted file mode 100644 index ce879d7..0000000 --- a/composeApp/src/commonMain/kotlin/dev/anandbose/App.kt +++ /dev/null @@ -1,251 +0,0 @@ -package dev.anandbose - -import anandbose.composeapp.generated.resources.Res -import anandbose.composeapp.generated.resources.compose_multiplatform -import anandbose.composeapp.generated.resources.social_bluesky -import anandbose.composeapp.generated.resources.social_github -import anandbose.composeapp.generated.resources.social_linkedin -import anandbose.composeapp.generated.resources.social_mastodon -import anandbose.composeapp.generated.resources.social_medium -import anandbose.composeapp.generated.resources.social_twitter -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import coil3.compose.AsyncImage -import kotlinx.browser.window -import org.jetbrains.compose.resources.painterResource - -@Composable -fun App() { - MaterialTheme( - colorScheme = if (isSystemInDarkTheme()) { - darkColorScheme() - } else { - lightColorScheme() - } - ) { - Scaffold(modifier = Modifier.fillMaxSize()) { - BoxWithConstraints( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.TopCenter, - ) { - val scrollState = rememberScrollState() - Column( - modifier = Modifier - .widthIn(max = 760.dp) - .padding(horizontal = 32.dp) - .verticalScroll(state = scrollState), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - AsyncImage( - model = "https://avatars.githubusercontent.com/u/64779880?v=4", - modifier = Modifier - .padding(top = 32.dp) - .clip(CircleShape) - .size(size = 128.dp), - contentDescription = null - ) - Text( - modifier = Modifier.padding(top = 16.dp), - text = "Anand Bose", - style = MaterialTheme.typography.titleLarge, - ) - Text( - modifier = Modifier.padding(top = 8.dp), - text = "I'm passionate about building software with Kotlin and Compose.", - style = MaterialTheme.typography.labelLarge, - textAlign = TextAlign.Center, - ) - if (this@BoxWithConstraints.maxWidth > 480.dp) { - Row( - modifier = Modifier.padding(top = 48.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - UrlButtonLarge( - modifier = Modifier.weight(1f), - icon = painterResource(Res.drawable.social_medium), - text = "Medium", - url = "https://medium.com/@anandbose", - ) - UrlButtonLarge( - modifier = Modifier.weight(1f), - icon = painterResource(Res.drawable.social_linkedin), - text = "LinkedIn", - url = "https://linkedin.com/in/anandbosedev", - ) - } - Row( - modifier = Modifier.padding(top = 16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - UrlButtonLarge( - modifier = Modifier.weight(1f), - icon = painterResource(Res.drawable.social_github), - text = "GitHub", - url = "https://github.com/anandbosedev", - ) - UrlButtonLarge( - modifier = Modifier.weight(1f), - icon = painterResource(Res.drawable.social_mastodon), - text = "Mastodon", - url = "https://mastodon.social/@anandbosedev", - ) - } - Row( - modifier = Modifier.padding(top = 16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - UrlButtonLarge( - modifier = Modifier.weight(1f), - icon = painterResource(Res.drawable.social_bluesky), - text = "Bluesky", - url = "https://bsky.app/profile/anandbose.dev", - ) - UrlButtonLarge( - modifier = Modifier.weight(1f), - icon = painterResource(Res.drawable.social_twitter), - text = "Twitter", - url = "https://x.com/@anandbosedev", - ) - } - } else { - UrlButtonSmall( - modifier = Modifier.fillMaxWidth().padding(top = 24.dp), - icon = painterResource(Res.drawable.social_medium), - text = "Medium", - url = "https://medium.com/@anandbose", - ) - UrlButtonSmall( - modifier = Modifier.fillMaxWidth().padding(top = 16.dp), - icon = painterResource(Res.drawable.social_linkedin), - text = "LinkedIn", - url = "https://linkedin.com/in/anandbosedev", - ) - UrlButtonSmall( - modifier = Modifier.fillMaxWidth().padding(top = 16.dp), - icon = painterResource(Res.drawable.social_github), - text = "GitHub", - url = "https://github.com/anandbosedev", - ) - UrlButtonSmall( - modifier = Modifier.fillMaxWidth().padding(top = 16.dp), - icon = painterResource(Res.drawable.social_mastodon), - text = "Mastodon", - url = "https://mastodon.social/@anandbosedev", - ) - UrlButtonSmall( - modifier = Modifier.fillMaxWidth().padding(top = 16.dp), - icon = painterResource(Res.drawable.social_bluesky), - text = "Bluesky", - url = "https://bsky.app/profile/anandbose.dev", - ) - UrlButtonSmall( - modifier = Modifier.fillMaxWidth().padding(top = 16.dp), - icon = painterResource(Res.drawable.social_twitter), - text = "Twitter", - url = "https://x.com/@anandbosedev", - ) - } - Text( - modifier = Modifier.padding(vertical = 32.dp), - text = "This website is built with Compose Multiplatform and Kotlin/WASM. The icons are sourced from icons8.com", - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - } - } -} - -@Composable -fun UrlButtonLarge( - modifier: Modifier = Modifier, - icon: Painter, - text: String, - url: String, -) { - Button( - modifier = modifier, - shape = MaterialTheme.shapes.small, - content = { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Image( - modifier = Modifier.size(48.dp), - painter = icon, - contentDescription = null, - colorFilter = ColorFilter.tint( - color = MaterialTheme.colorScheme.onPrimary, - ), - ) - Text( - text = text, - style = MaterialTheme.typography.labelLarge, - ) - } - }, - onClick = { - window.open(url, "_blank") - } - ) -} - -@Composable -fun UrlButtonSmall( - modifier: Modifier = Modifier, - icon: Painter, - text: String, - url: String, -) { - Button( - modifier = modifier, - content = { - Image( - modifier = Modifier.size(32.dp), - painter = icon, - contentDescription = null, - colorFilter = ColorFilter.tint( - color = MaterialTheme.colorScheme.onPrimary, - ), - ) - Text( - modifier = Modifier.padding(start = 4.dp), - text = text, - style = MaterialTheme.typography.labelLarge, - ) - }, - onClick = { - window.open(url, "_blank") - } - ) -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/anandbose/data/Links.kt b/composeApp/src/commonMain/kotlin/dev/anandbose/data/Links.kt new file mode 100644 index 0000000..9f69f0c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/anandbose/data/Links.kt @@ -0,0 +1,53 @@ +package dev.anandbose.data + +import anandbose.composeapp.generated.resources.Res +import anandbose.composeapp.generated.resources.social_bluesky +import anandbose.composeapp.generated.resources.social_github +import anandbose.composeapp.generated.resources.social_linkedin +import anandbose.composeapp.generated.resources.social_mastodon +import anandbose.composeapp.generated.resources.social_medium +import anandbose.composeapp.generated.resources.social_twitter +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import org.jetbrains.compose.resources.DrawableResource + +@Stable +@Immutable +data class LinkData( + val icon: DrawableResource, + val title: String, + val url: String, +) + +val Links = listOf( + LinkData( + icon = Res.drawable.social_medium, + title = "Medium", + url = "https://medium.com/@anandbose" + ), + LinkData( + icon = Res.drawable.social_linkedin, + title = "LinkedIn", + url = "https://linkedin.com/in/anandbosedev", + ), + LinkData( + icon = Res.drawable.social_github, + title = "GitHub", + url = "https://github.com/anandbosedev", + ), + LinkData( + icon = Res.drawable.social_mastodon, + title = "Mastodon", + url = "https://mastodon.social/@anandbosedev", + ), + LinkData( + icon = Res.drawable.social_bluesky, + title = "Bluesky", + url = "https://bsky.app/profile/anandbose.dev", + ), + LinkData( + icon = Res.drawable.social_twitter, + title = "Twitter", + url = "https://x.com/@anandbosedev", + ), +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/anandbose/main.kt b/composeApp/src/commonMain/kotlin/dev/anandbose/main.kt index c5cb5ee..ad85e06 100644 --- a/composeApp/src/commonMain/kotlin/dev/anandbose/main.kt +++ b/composeApp/src/commonMain/kotlin/dev/anandbose/main.kt @@ -2,6 +2,7 @@ package dev.anandbose import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.ComposeViewport +import dev.anandbose.ui.app.App import kotlinx.browser.document @OptIn(ExperimentalComposeUiApi::class) diff --git a/composeApp/src/commonMain/kotlin/dev/anandbose/ui/app/App.kt b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/app/App.kt new file mode 100644 index 0000000..cd89671 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/app/App.kt @@ -0,0 +1,160 @@ +package dev.anandbose.ui.app + +import anandbose.composeapp.generated.resources.Res +import anandbose.composeapp.generated.resources.social_bluesky +import anandbose.composeapp.generated.resources.social_github +import anandbose.composeapp.generated.resources.social_linkedin +import anandbose.composeapp.generated.resources.social_mastodon +import anandbose.composeapp.generated.resources.social_medium +import anandbose.composeapp.generated.resources.social_twitter +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import dev.anandbose.data.Links +import dev.anandbose.ui.util.styledLink +import dev.anandbose.ui.widget.UrlButtonLarge +import dev.anandbose.ui.widget.UrlButtonSmall +import org.jetbrains.compose.resources.painterResource + +@Composable +fun App() { + MaterialTheme( + colorScheme = if (isSystemInDarkTheme()) { + darkColorScheme() + } else { + lightColorScheme() + } + ) { + Scaffold(modifier = Modifier.fillMaxSize()) { + BoxWithConstraints( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter, + ) { + //val scrollState = rememberScrollState() + Column( + modifier = Modifier + .widthIn(max = 760.dp) + .padding(horizontal = 32.dp), + //.verticalScroll(state = scrollState), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + AsyncImage( + model = "https://avatars.githubusercontent.com/u/64779880?v=4", + modifier = Modifier + .padding(top = 32.dp) + .clip(CircleShape) + .size(size = 128.dp), + contentDescription = null + ) + Text( + modifier = Modifier.padding(top = 16.dp), + text = "Anand Bose", + style = MaterialTheme.typography.titleLarge, + ) + Text( + modifier = Modifier.padding(top = 8.dp), + text = "I'm passionate about building software with Kotlin and Compose.", + style = MaterialTheme.typography.labelLarge, + textAlign = TextAlign.Center, + ) + if (this@BoxWithConstraints.maxWidth > 480.dp) { + LazyVerticalGrid( + modifier = Modifier.padding(top = 32.dp), + columns = GridCells.Adaptive(minSize = 200.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + items(items = Links) { + UrlButtonLarge( + modifier = Modifier.fillMaxWidth(), + icon = painterResource(it.icon), + text = it.title, + url = it.url, + ) + } + } + } else { + LazyColumn( + modifier = Modifier.padding(top = 32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + items(items = Links) { + UrlButtonSmall( + modifier = Modifier.fillMaxWidth(), + icon = painterResource(it.icon), + text = it.title, + url = it.url, + ) + } + } + } + Text( + modifier = Modifier.padding(vertical = 32.dp), + text = buildAnnotatedString { + append("This website is built with ") + styledLink( + url = "https://www.jetbrains.com/compose-multiplatform/", + text = "Compose Multiplatform", + ) + append(" and ") + styledLink( + url = "https://kotlinlang.org/docs/wasm-overview.html", + text = "Kotlin/WASM", + ) + append(". The icons are sourced from ") + styledLink( + url = "https://icons8.com", + text = "icons8.com", + ) + append(" and converted to Vector Drawable using ") + styledLink( + url = "https://shapeshifter.design", + text = "ShapeShifter", + ) + append(". Source code is available on ") + styledLink( + url = "https://github.com/anandbosedev/anandbosedev.github.io", + text = "GitHub", + ) + append(".") + }, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + } +} + + + diff --git a/composeApp/src/commonMain/kotlin/dev/anandbose/ui/util/HyperlinkBuilder.kt b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/util/HyperlinkBuilder.kt new file mode 100644 index 0000000..ac03999 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/util/HyperlinkBuilder.kt @@ -0,0 +1,38 @@ +package dev.anandbose.ui.util + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink + +@Composable +fun AnnotatedString.Builder.styledLink(url: String, text: String) { + withLink( + link = LinkAnnotation.Url( + url = url, + styles = TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + ), + focusedStyle = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + ), + hoveredStyle = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + ), + pressedStyle = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + ) + ) + ), + ) { + append(text) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonLarge.kt b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonLarge.kt new file mode 100644 index 0000000..1d03956 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonLarge.kt @@ -0,0 +1,51 @@ +package dev.anandbose.ui.widget + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +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.graphics.ColorFilter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.dp +import kotlinx.browser.window + +@Composable +fun UrlButtonLarge( + modifier: Modifier = Modifier, + icon: Painter, + text: String, + url: String, +) { + Button( + modifier = modifier, + shape = MaterialTheme.shapes.small, + content = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Image( + modifier = Modifier.size(48.dp), + painter = icon, + contentDescription = null, + colorFilter = ColorFilter.tint( + color = MaterialTheme.colorScheme.onPrimary, + ), + ) + Text( + text = text, + style = MaterialTheme.typography.labelLarge, + ) + } + }, + onClick = { + window.open(url, "_blank") + } + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonSmall.kt b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonSmall.kt new file mode 100644 index 0000000..78f752f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/anandbose/ui/widget/UrlButtonSmall.kt @@ -0,0 +1,44 @@ +package dev.anandbose.ui.widget + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.dp +import kotlinx.browser.window + +@Composable +fun UrlButtonSmall( + modifier: Modifier = Modifier, + icon: Painter, + text: String, + url: String, +) { + Button( + modifier = modifier, + content = { + Image( + modifier = Modifier.size(32.dp), + painter = icon, + contentDescription = null, + colorFilter = ColorFilter.tint( + color = MaterialTheme.colorScheme.onPrimary, + ), + ) + Text( + modifier = Modifier.padding(start = 4.dp), + text = text, + style = MaterialTheme.typography.labelLarge, + ) + }, + onClick = { + window.open(url, "_blank") + } + ) +} \ No newline at end of file