Jetpack Compose for desktop client
This commit is contained in:
parent
b1512a5006
commit
fc3a1136bd
5 changed files with 203 additions and 8 deletions
19
README.md
19
README.md
|
@ -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
2
compose-desktop/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/build
|
||||
*.iml
|
30
compose-desktop/build.gradle.kts
Normal file
30
compose-desktop/build.gradle.kts
Normal 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"
|
||||
}
|
149
compose-desktop/src/main/kotlin/main.kt
Normal file
149
compose-desktop/src/main/kotlin/main.kt
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
Loading…
Reference in a new issue