Jetpack Compose for desktop client

This commit is contained in:
John O'Reilly 2020-11-17 16:58:10 +00:00
parent b1512a5006
commit fc3a1136bd
5 changed files with 203 additions and 8 deletions

View file

@ -1,24 +1,25 @@
# PeopleInSpace
Minimal **Kotlin Multiplatform** project using Jetpack Compose and SwiftUI. Currently running on
* Android
* iOS
* watchOS
* macOS
* Web
* Android (Jetpack Compose)
* iOS (SwiftUI)
* watchOS (SwiftUI)
* macOS (SwiftUI)
* Desktop (Jetpack Compose for Desktop)
* Web (Kotlin/JS + React Wrapper)
* JVM (small Ktor back end service)
It makes use of basic API (http://open-notify.org/Open-Notify-API/People-In-Space/) to show list of people currently in
space (inspired by https://kousenit.org/2019/12/19/a-few-astronomical-examples-in-kotlin/)! The list is shown on Android
using **Jetpack Compose**, on iOS using **SwiftUI** and on Web using Kotlin/JS React wrapper.
Related posts (please be aware that these were written pre Kotlin 1.4...I plan on updating them soon
to reflect 1.4 changes):
Related posts:
* [Minimal Kotlin Multiplatform project using Compose and SwiftUI](https://johnoreilly.dev/posts/minimal-kotlin-platform-compose-swiftui/)
* [Adding some Storage (to) Space](https://johnoreilly.dev/posts/adding-sqldelight-to-peopleinspace/)
* [Kotlin Multiplatform running on macOS](https://johnoreilly.dev/posts/kotlinmultiplatform-macos/)
* [PeopleInSpace hits the web with Kotlin/JS and React](https://johnoreilly.dev/posts/peopleinspace-kotlinjs/)
* [Using Koin in a Kotlin Multiplatform Project](https://johnoreilly.dev/posts/kotlinmultiplatform-koin/)
* [Jetpack Compose for the Desktop!](https://johnoreilly.dev/posts/jetpack-compose-desktop/)
Note that this repository very much errs on the side of mimimalism to help more clearly illustrate key moving parts of a Koltin
@ -31,6 +32,10 @@ beyond this then I'd definitely recommend checking out [KaMPKit](https://github.
You need to use Android Studio v4.2 (currently preview/canary version). Have tested on XCode v11 and v12. When opening
iOS/watchOS/macOS projects remember to open `.xcworkspace` file (and not `.xcodeproj` one). To exercise web client run `./gradlew :web:browserDevelopmentRun`.
### Jetpack Compose for Desktop client
This client is available in `compose-desktop` module. Note that you currently need to use EAP version of kotlin
plugin and also use appropriate JVM when running (works for example with Java 11)
### Languages, libraries and tools used

2
compose-desktop/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/build
*.iml

View file

@ -0,0 +1,30 @@
import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
id("org.jetbrains.compose") version "0.1.0-build63"
application
}
group = "me.joreilly"
version = "1.0-SNAPSHOT"
repositories {
jcenter()
mavenCentral()
maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
dependencies {
implementation(compose.desktop.all)
implementation(project(":common"))
}
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClassName = "MainKt"
}

View file

@ -0,0 +1,149 @@
import androidx.compose.desktop.Window
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageAsset
import androidx.compose.ui.graphics.asImageAsset
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.surrus.common.model.personBios
import com.surrus.common.model.personImages
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.PeopleInSpaceApi
import org.jetbrains.skija.Image
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.lang.Exception
import java.net.HttpURLConnection
import java.net.URL
import javax.imageio.ImageIO
fun main() = Window {
var peopleState by remember { mutableStateOf(emptyList<Assignment>()) }
var selectedPerson by remember { mutableStateOf("") }
val peopleInSpaceApi = PeopleInSpaceApi()
launchInComposition {
peopleState = peopleInSpaceApi.fetchPeople().people
}
MaterialTheme {
Scaffold(
topBar = {
TopAppBar(title = { Text("People In Space") })
},
bodyContent = {
Row(Modifier.fillMaxSize()) {
Box(Modifier.width(250.dp).fillMaxHeight(),
backgroundColor = Color.LightGray)
{
PersonList(peopleState) {
selectedPerson = it.name
}
}
Spacer(modifier = Modifier.preferredWidth(1.dp).fillMaxHeight())
Box(Modifier.fillMaxHeight()) {
PersonDetailsView(selectedPerson)
}
}
}
)
}
}
@Composable
fun PersonList(people: List<Assignment>, personSelected : (person : Assignment) -> Unit) {
LazyColumnFor(items = people, itemContent = { person ->
PersonView(person, personSelected)
})
}
@Composable
fun PersonView(person: Assignment, personSelected : (person : Assignment) -> Unit) {
Row(
modifier = Modifier.fillMaxWidth() + Modifier.clickable(onClick = { personSelected(person) })
+ Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(text = person.name, style = TextStyle(fontSize = 18.sp))
Text(text = person.craft, style = TextStyle(color = Color.DarkGray, fontSize = 14.sp))
}
}
}
@Composable
fun PersonDetailsView(personName: String) {
ScrollableColumn(modifier = Modifier.padding(16.dp) + Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(personName, style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.preferredSize(12.dp))
val imageUrl = personImages[personName]
imageUrl?.let {
val imageAsset = fetchImage(it)
imageAsset?.let {
Image(it, modifier = Modifier.preferredSize(240.dp))
}
}
Spacer(modifier = Modifier.preferredSize(24.dp))
val bio = personBios[personName] ?: ""
Text(bio, style = MaterialTheme.typography.body1)
}
}
@Composable
fun fetchImage(url: String): ImageAsset? {
var image by remember(url) { mutableStateOf<ImageAsset?>(null) }
launchInComposition(url) {
loadFullImage(url)?.let {
image = Image.makeFromEncoded(toByteArray(it)).asImageAsset()
}
}
return image
}
fun toByteArray(bitmap: BufferedImage) : ByteArray {
val baos = ByteArrayOutputStream()
ImageIO.write(bitmap, "png", baos)
return baos.toByteArray()
}
fun loadFullImage(source: String): BufferedImage? {
return try {
val url = URL(source)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.connect()
val input: InputStream = connection.inputStream
val bitmap: BufferedImage? = ImageIO.read(input)
bitmap
} catch (e: Exception) {
e.printStackTrace()
null
}
}

View file

@ -1,7 +1,16 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
include(":web")
include(":backend")
rootProject.name = "PeopleInSpace"
enableFeaturePreview("GRADLE_METADATA")
include(":app", ":common")
include(":app", ":common", ":compose-desktop")