Skip to content

Commit dbfaf9a

Browse files
authored
Add desktop target (#2)
1 parent 3d63988 commit dbfaf9a

File tree

10 files changed

+235
-7
lines changed

10 files changed

+235
-7
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ root = true
44
ktlint_code_style = intellij_idea
55
ij_kotlin_allow_trailing_comma = false
66
ij_kotlin_allow_trailing_comma_on_call_site = false
7+
ktlint_function_naming_ignore_when_annotated_with = Composable

desktop/build.gradle.kts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
kotlin("jvm")
3+
alias(libs.plugins.compose.compiler)
4+
alias(libs.plugins.compose.multiplatform)
5+
}
6+
7+
dependencies {
8+
implementation(project(":shared"))
9+
implementation(compose.desktop.currentOs)
10+
implementation(libs.compose.viewmodel)
11+
implementation(libs.coroutines.swing)
12+
implementation(libs.koin)
13+
}
14+
15+
compose.desktop {
16+
application {
17+
mainClass = "com.jdamcd.arrivals.desktop.MainKt"
18+
}
19+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.jdamcd.arrivals.desktop
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.Row
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.material.CircularProgressIndicator
12+
import androidx.compose.material.Text
13+
import androidx.compose.runtime.Composable
14+
import androidx.compose.ui.Alignment
15+
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.text.TextStyle
17+
import androidx.compose.ui.unit.dp
18+
import androidx.compose.ui.unit.sp
19+
import com.jdamcd.arrivals.Arrival
20+
21+
@Composable
22+
fun ArrivalsList(
23+
state: ArrivalsState
24+
) {
25+
Column(
26+
modifier = Modifier
27+
.background(color = Background)
28+
.fillMaxSize(),
29+
verticalArrangement = Arrangement.SpaceBetween
30+
) {
31+
when (state) {
32+
is ArrivalsState.Loading -> Loading()
33+
is ArrivalsState.Data -> Data(state)
34+
is ArrivalsState.Error -> Error(state.message)
35+
}
36+
}
37+
}
38+
39+
@Composable
40+
private fun Loading() {
41+
Box(
42+
modifier = Modifier.fillMaxSize(),
43+
contentAlignment = Alignment.Center
44+
) {
45+
CircularProgressIndicator(color = Text)
46+
}
47+
}
48+
49+
@Composable
50+
private fun Data(state: ArrivalsState.Data) {
51+
Column(
52+
modifier = Modifier
53+
.padding(32.dp)
54+
.fillMaxWidth(),
55+
verticalArrangement = Arrangement.spacedBy(32.dp)
56+
) {
57+
state.result.arrivals.forEach {
58+
ArrivalRow(it)
59+
}
60+
}
61+
Row(
62+
modifier = Modifier
63+
.background(color = Footer)
64+
.padding(16.dp)
65+
.fillMaxWidth()
66+
) {
67+
Text(
68+
text = state.result.station,
69+
color = Text,
70+
style = TextStyle(
71+
fontSize = 32.sp
72+
)
73+
)
74+
}
75+
}
76+
77+
@Composable
78+
fun Error(message: String) {
79+
Column(
80+
modifier = Modifier
81+
.padding(32.dp)
82+
.fillMaxWidth(),
83+
verticalArrangement = Arrangement.spacedBy(32.dp)
84+
) {
85+
LedText(message)
86+
}
87+
}
88+
89+
@Composable
90+
fun ArrivalRow(arrival: Arrival) {
91+
Row(
92+
horizontalArrangement = Arrangement.SpaceBetween,
93+
modifier = Modifier.fillMaxWidth()
94+
) {
95+
LedText(arrival.destination)
96+
LedText(arrival.time)
97+
}
98+
}
99+
100+
@Composable
101+
fun LedText(string: String) {
102+
Text(
103+
text = string,
104+
color = LedYellow,
105+
style = TextStyle(
106+
fontFamily = LurFontFamily,
107+
fontSize = 52.sp
108+
)
109+
)
110+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.jdamcd.arrivals.desktop
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import com.jdamcd.arrivals.Arrivals
6+
import com.jdamcd.arrivals.ArrivalsInfo
7+
import kotlinx.coroutines.delay
8+
import kotlinx.coroutines.flow.MutableStateFlow
9+
import kotlinx.coroutines.flow.StateFlow
10+
import kotlinx.coroutines.flow.asStateFlow
11+
import kotlinx.coroutines.launch
12+
13+
class ArrivalsViewModel(arrivals: Arrivals) : ViewModel() {
14+
15+
private val _uiState = MutableStateFlow<ArrivalsState>(ArrivalsState.Loading)
16+
val uiState: StateFlow<ArrivalsState> = _uiState.asStateFlow()
17+
18+
init {
19+
viewModelScope.launch {
20+
while (true) {
21+
try {
22+
val result = arrivals.latest()
23+
_uiState.value = ArrivalsState.Data(result)
24+
} catch (e: Exception) {
25+
if (_uiState.value !is ArrivalsState.Data) {
26+
_uiState.value = ArrivalsState.Error(e.message ?: "Unknown error")
27+
}
28+
}
29+
delay(60_000L) // 60 seconds
30+
}
31+
}
32+
}
33+
}
34+
35+
sealed class ArrivalsState {
36+
data object Loading : ArrivalsState()
37+
data class Data(val result: ArrivalsInfo) : ArrivalsState()
38+
data class Error(val message: String) : ArrivalsState()
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.jdamcd.arrivals.desktop
2+
3+
import androidx.compose.runtime.collectAsState
4+
import androidx.compose.runtime.getValue
5+
import androidx.compose.ui.Alignment
6+
import androidx.compose.ui.unit.DpSize
7+
import androidx.compose.ui.unit.dp
8+
import androidx.compose.ui.window.Window
9+
import androidx.compose.ui.window.WindowPosition
10+
import androidx.compose.ui.window.application
11+
import androidx.compose.ui.window.rememberWindowState
12+
import com.jdamcd.arrivals.Arrivals
13+
import com.jdamcd.arrivals.initKoin
14+
15+
private val koin = initKoin().koin
16+
17+
fun main() = application {
18+
val windowState = rememberWindowState(
19+
position = WindowPosition(Alignment.Center),
20+
size = DpSize(1280.dp, 400.dp)
21+
)
22+
23+
val viewModel = ArrivalsViewModel(koin.get<Arrivals>())
24+
val state: ArrivalsState by viewModel.uiState.collectAsState(ArrivalsState.Loading)
25+
26+
Window(
27+
onCloseRequest = ::exitApplication,
28+
state = windowState,
29+
title = "Arrivals"
30+
) {
31+
ArrivalsList(state)
32+
}
33+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.jdamcd.arrivals.desktop
2+
3+
import androidx.compose.ui.graphics.Color
4+
import androidx.compose.ui.text.font.FontFamily
5+
import androidx.compose.ui.text.font.FontStyle
6+
import androidx.compose.ui.text.font.FontWeight
7+
import androidx.compose.ui.text.platform.Font
8+
9+
val LurFontFamily = FontFamily(
10+
fonts = listOf(
11+
Font(
12+
resource = "font/LUR.ttf",
13+
weight = FontWeight.W400,
14+
style = FontStyle.Normal
15+
)
16+
)
17+
)
18+
19+
val LedYellow = Color(0xFFFFDD00)
20+
val Background = Color.Black
21+
val Footer = Color(0xFF121218)
22+
val Text = Color(0xFF707070)
34.6 KB
Binary file not shown.

gradle/libs.versions.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,38 @@ mockk = "1.13.14"
1313
kotest = "5.9.1"
1414
logging = "2.0.16"
1515
clikt = "5.0.2"
16+
composeMP = "1.7.3"
17+
viewModel = "2.8.2"
1618

1719
[libraries]
1820
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
1921
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
2022
ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
2123
ktor-client-macos = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" }
2224
ktor-client-jvm = { group = "io.ktor", name = "ktor-client-java", version.ref = "ktor" }
23-
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
25+
ktor-serialization-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
2426
kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
2527
kotlin-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "dateTime" }
2628
wire-runtime = { group = "com.squareup.wire", name = "wire-runtime", version.ref = "wire" }
2729
okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" }
2830
koin = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" }
2931
logging-nop = { group = "org.slf4j", name = "slf4j-nop", version.ref = "logging" }
3032
clikt = { group = "com.github.ajalt.clikt", name = "clikt", version.ref = "clikt" }
33+
compose-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "viewModel" }
34+
coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "coroutines" }
3135

3236
coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
3337
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
3438
kotest = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" }
3539

3640
[bundles]
37-
ktor-common = ["ktor-client-core", "ktor-client-content-negotiation", "ktor-client-logging", "ktor-serialization-kotlinx-json"]
41+
ktor-common = ["ktor-client-core", "ktor-client-content-negotiation", "ktor-client-logging", "ktor-serialization-json"]
3842

3943
[plugins]
4044
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
4145
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
4246
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
4347
buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildKonfig" }
4448
wire = { id = "com.squareup.wire", version.ref = "wire" }
49+
compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMP" }
50+
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ dependencyResolutionManagement {
1414
}
1515

1616
rootProject.name = "Arrivals"
17-
include(":shared", ":cli")
17+
include(":shared", ":cli", ":desktop")

shared/src/commonMain/kotlin/com/jdamcd/arrivals/Arrivals.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ import kotlin.coroutines.cancellation.CancellationException
2121
import kotlin.math.roundToInt
2222

2323
@Suppress("Unused")
24-
fun initKoin() {
25-
startKoin {
26-
modules(commonModule())
27-
}
24+
fun initKoin() = startKoin {
25+
modules(commonModule())
2826
}
2927

3028
@Suppress("Unused")

0 commit comments

Comments
 (0)