Compare commits

..

No commits in common. "main" and "js_apple_silicon" have entirely different histories.

40 changed files with 222 additions and 407 deletions

View file

@ -59,4 +59,4 @@ jobs:
with: with:
api-level: 29 api-level: 29
target: google_apis target: google_apis
script: ./gradlew app:connectedAndroidTest script: ./gradlew app:connectedAndroidTest common:connectedAndroidTest

View file

@ -29,7 +29,6 @@ Related posts:
* [Wrapping Kotlin Flow with Swift Combine Publisher in a Kotlin Multiplatform project](https://johnoreilly.dev/posts/kotlinmultiplatform-swift-combine_publisher-flow/) * [Wrapping Kotlin Flow with Swift Combine Publisher in a Kotlin Multiplatform project](https://johnoreilly.dev/posts/kotlinmultiplatform-swift-combine_publisher-flow/)
* [Using Swift Packages in a Kotlin Multiplatform project](https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/) * [Using Swift Packages in a Kotlin Multiplatform project](https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/)
* [Using Swift's new async/await when invoking Kotlin Multiplatform code](https://johnoreilly.dev/posts/swift_async_await_kotlin_coroutines/) * [Using Swift's new async/await when invoking Kotlin Multiplatform code](https://johnoreilly.dev/posts/swift_async_await_kotlin_coroutines/)
* [Exploring new AWS SDK for Kotlin](https://johnoreilly.dev/posts/aws-sdk-kotlin/)
Note that this repository very much errs on the side of minimalism to help more clearly illustrate key moving parts of a Kotlin Note that this repository very much errs on the side of minimalism to help more clearly illustrate key moving parts of a Kotlin
@ -67,7 +66,7 @@ invoking `./gradlew :compose-web:jsBrowserDevelopmentRun`
This client is available in `compose-desktop` module. Note that you need to use appropriate version of JVM when running (works for example with Java 11) This client is available in `compose-desktop` module. Note that you need to use appropriate version of JVM when running (works for example with Java 11)
### Backend code ### Deploying backend code
Have tested this out in Google App Engine deployment. Using shadowJar plugin to create an "uber" jar and then deploying it as shown below. Should be possible to deploy this jar to other services as well. Have tested this out in Google App Engine deployment. Using shadowJar plugin to create an "uber" jar and then deploying it as shown below. Should be possible to deploy this jar to other services as well.
@ -76,11 +75,6 @@ Have tested this out in Google App Engine deployment. Using shadowJar plugin to
gcloud app deploy backend/build/libs/backend-all.jar gcloud app deploy backend/build/libs/backend-all.jar
``` ```
### GraphQL backend
There's a GraphQL module (`graphql-server`) which can be run locally using `./gradlew :graphql-server:bootRun` with "playground" then available at http://localhost:8080/playground
### Screenshots ### Screenshots

View file

@ -64,9 +64,8 @@ dependencies {
implementation(activityCompose) implementation(activityCompose)
} }
with(Deps.Glance) { implementation("androidx.glance:glance-appwidget:1.0.0-SNAPSHOT")
implementation(appwidget)
}
with(Deps.Compose) { with(Deps.Compose) {
implementation(compiler) implementation(compiler)
@ -80,6 +79,8 @@ dependencies {
implementation(uiTooling) implementation(uiTooling)
} }
with(Deps.Koin) { with(Deps.Koin) {
implementation(core) implementation(core)
implementation(android) implementation(android)

View file

@ -7,14 +7,13 @@ import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.TextUnitType
import androidx.glance.GlanceModifier import androidx.glance.GlanceModifier
import androidx.glance.Image import androidx.glance.action.actionLaunchActivity
import androidx.glance.ImageProvider
import androidx.glance.action.actionStartActivity
import androidx.glance.action.clickable import androidx.glance.action.clickable
import androidx.glance.background import androidx.glance.background
import androidx.glance.layout.Box import androidx.glance.layout.Box
import androidx.glance.layout.ImageProvider
import androidx.glance.layout.Text
import androidx.glance.layout.fillMaxSize import androidx.glance.layout.fillMaxSize
import androidx.glance.text.Text
import androidx.glance.text.TextStyle import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider import androidx.glance.unit.ColorProvider
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
@ -82,12 +81,12 @@ class ISSMapWidget : BaseGlanceAppWidget<ISSMapWidget.Data>() {
override fun Content(data: Data?) { override fun Content(data: Data?) {
Box( Box(
modifier = GlanceModifier.background(Color.DarkGray).fillMaxSize().clickable( modifier = GlanceModifier.background(Color.DarkGray).fillMaxSize().clickable(
actionStartActivity<MainActivity>() actionLaunchActivity<MainActivity>()
) )
) { ) {
val bitmap = data?.bitmap val bitmap = data?.bitmap
if (bitmap != null) { if (bitmap != null) {
Image( androidx.glance.layout.Image(
modifier = GlanceModifier.fillMaxSize(), modifier = GlanceModifier.fillMaxSize(),
provider = ImageProvider(bitmap), provider = ImageProvider(bitmap),
contentDescription = "ISS Location" contentDescription = "ISS Location"

View file

@ -7,12 +7,13 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier import androidx.glance.GlanceModifier
import androidx.glance.appwidget.lazy.LazyColumn import androidx.glance.appwidget.layout.LazyColumn
import androidx.glance.appwidget.layout.items
import androidx.glance.background import androidx.glance.background
import androidx.glance.layout.Row import androidx.glance.layout.Row
import androidx.glance.layout.Text
import androidx.glance.layout.padding import androidx.glance.layout.padding
import androidx.glance.text.FontWeight import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider import androidx.glance.unit.ColorProvider
import com.surrus.common.remote.Assignment import com.surrus.common.remote.Assignment
@ -48,10 +49,10 @@ class PeopleInSpaceWidget : BaseGlanceAppWidget<PeopleInSpaceWidget.Data>() {
) )
} }
if (data != null) { if (data != null) {
items(data.people.size) { items(data.people) {
Row { Row {
Text( Text(
text = data.people[it].name, text = it.name,
style = TextStyle( style = TextStyle(
color = ColorProvider(Color.White), color = ColorProvider(Color.White),
fontSize = TextUnit(10f, TextUnitType.Sp) fontSize = TextUnit(10f, TextUnitType.Sp)

View file

@ -7,10 +7,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.glance.GlanceId
import androidx.glance.LocalGlanceId
import androidx.glance.LocalSize import androidx.glance.LocalSize
import androidx.glance.appwidget.GlanceAppWidget import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceId
import androidx.glance.appwidget.LocalGlanceId
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull

View file

@ -3,8 +3,12 @@ package com.surrus.peopleinspace.ui
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.animation.* import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon import androidx.compose.material.Icon
@ -95,11 +99,11 @@ fun MainLayout() {
AnimatedNavHost(navController, startDestination = Screen.PersonList.title) { AnimatedNavHost(navController, startDestination = Screen.PersonList.title) {
composable( composable(
route = Screen.PersonList.title, route = Screen.PersonList.title,
exitTransition = { exitTransition = { _, target ->
slideOutHorizontally() + slideOutHorizontally() +
fadeOut(animationSpec = tween(1000)) fadeOut(animationSpec = tween(1000))
}, },
popEnterTransition = { popEnterTransition = { _, _ ->
slideInHorizontally() slideInHorizontally()
} }
) { ) {
@ -112,11 +116,11 @@ fun MainLayout() {
} }
composable( composable(
route = Screen.PersonDetails.title + "/{person}", route = Screen.PersonDetails.title + "/{person}",
enterTransition = { enterTransition = { _, _ ->
slideInHorizontally() + slideInHorizontally() +
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(1000))
}, },
popExitTransition = { popExitTransition = { _, _ ->
slideOutHorizontally() slideOutHorizontally()
} }
) { backStackEntry -> ) { backStackEntry ->

View file

@ -11,7 +11,7 @@ buildscript {
dependencies { dependencies {
// keeping this here to allow AS to automatically update // keeping this here to allow AS to automatically update
classpath("com.android.tools.build:gradle:7.1.0") classpath("com.android.tools.build:gradle:7.0.4")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-serialization:${kotlinVersion}") classpath("org.jetbrains.kotlin:kotlin-serialization:${kotlinVersion}")
@ -20,7 +20,8 @@ buildscript {
classpath(shadow) classpath(shadow)
classpath(kotlinter) classpath(kotlinter)
classpath(gradleVersionsPlugin) classpath(gradleVersionsPlugin)
classpath("com.rickclephas.kmp:kmp-nativecoroutines-gradle-plugin:${Versions.kmpNativeCoroutinesVersion}") val kmpNativeCoroutinesVersion = if (kotlinVersion == "1.6.10") "0.10.0-new-mm" else "0.8.0"
classpath("com.rickclephas.kmp:kmp-nativecoroutines-gradle-plugin:$kmpNativeCoroutinesVersion")
} }
} }
} }
@ -33,7 +34,9 @@ allprojects {
mavenCentral() mavenCentral()
maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers") maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers")
maven(url = "https://jitpack.io") maven(url = "https://jitpack.io")
maven(url = "https://androidx.dev/snapshots/builds/7888785/artifacts/repository")
maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven") maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven")
maven(url = "https://maven.pkg.jetbrains.space/public/p/ktor/eap")
} }
} }

View file

@ -3,21 +3,17 @@ object Versions {
const val androidCompileSdk = 31 const val androidCompileSdk = 31
const val androidTargetSdk = androidCompileSdk const val androidTargetSdk = androidCompileSdk
const val kotlinCoroutines = "1.6.0" const val kotlinCoroutines = "1.6.0-RC3"
const val koin = "3.1.4" const val koin = "3.1.4"
const val ktor = "2.0.0-beta-1" const val ktor = "2.0.0-eap-278"
const val kotlinxSerialization = "1.3.2" const val kotlinxSerialization = "1.3.1"
const val kotlinxHtmlJs = "0.7.3" const val kotlinxHtmlJs = "0.7.3"
const val kmpNativeCoroutinesVersion = "0.11.1-new-mm"
const val compose = "1.1.0-rc01" const val compose = "1.1.0-rc01"
const val composeCompiler = "1.1.0-rc02" const val composeCompiler = "1.1.0-rc02"
const val wearCompose = "1.0.0-alpha13" const val wearCompose = "1.0.0-alpha13"
const val navCompose = "2.4.0-rc01" const val navCompose = "2.4.0-rc01"
const val accompanist = "0.22.0-rc" const val accompanist = "0.21.0-beta"
const val composeDesktopWeb = "1.0.1"
const val junit = "4.12" const val junit = "4.12"
const val androidXTestJUnit = "1.1.3" const val androidXTestJUnit = "1.1.3"
@ -59,7 +55,6 @@ object Deps {
object Kotlinx { object Kotlinx {
const val serializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.kotlinxSerialization}" const val serializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.kotlinxSerialization}"
const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}" const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
const val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutines}"
const val htmlJs = "org.jetbrains.kotlinx:kotlinx-html-js:${Versions.kotlinxHtmlJs}" const val htmlJs = "org.jetbrains.kotlinx:kotlinx-html-js:${Versions.kotlinxHtmlJs}"
} }
@ -156,9 +151,4 @@ object Deps {
const val logback = "ch.qos.logback:logback-classic:${Versions.logback}" const val logback = "ch.qos.logback:logback-classic:${Versions.logback}"
const val kermit = "co.touchlab:kermit:${Versions.kermit}" const val kermit = "co.touchlab:kermit:${Versions.kermit}"
} }
object Glance {
const val tiles = "androidx.glance:glance-wear-tiles:1.0.0-alpha02"
const val appwidget = "androidx.glance:glance-appwidget:1.0.0-alpha02"
}
} }

View file

@ -71,7 +71,7 @@ kotlin {
} }
with(Deps.Kotlinx) { with(Deps.Kotlinx) {
implementation(coroutinesCore) implementation(Deps.Kotlinx.coroutinesCore)
implementation(serializationCore) implementation(serializationCore)
} }
@ -90,10 +90,6 @@ kotlin {
} }
} }
sourceSets["commonTest"].dependencies { sourceSets["commonTest"].dependencies {
implementation(Deps.Koin.test)
implementation(Deps.Kotlinx.coroutinesTest)
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
} }
sourceSets["androidMain"].dependencies { sourceSets["androidMain"].dependencies {

View file

@ -0,0 +1,18 @@
package com.surrus.peopleinspace
import com.surrus.common.di.initKoin
import com.surrus.common.remote.PeopleInSpaceApi
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Test
class PeopleInSpaceTest {
@Test
fun testGetPeople() = runBlocking {
val koin = initKoin(enableNetworkLogs = true).koin
val peopleInSpaceApi = koin.get<PeopleInSpaceApi>()
val result = peopleInSpaceApi.fetchPeople()
println(result)
assertTrue(result.people.isNotEmpty())
}
}

View file

@ -75,9 +75,12 @@ class PeopleInSpaceRepository : KoinComponent, PeopleInSpaceRepositoryInterface
override suspend fun fetchPeople(): List<Assignment> = peopleInSpaceApi.fetchPeople().people override suspend fun fetchPeople(): List<Assignment> = peopleInSpaceApi.fetchPeople().people
override fun pollISSPosition(): Flow<IssPosition> { override fun pollISSPosition(): Flow<IssPosition> {
// The returned will be frozen in Kotlin Native. We can't freeze the Koin internals
// so we'll use local variables to prevent the Koin internals from freezing.
val api = peopleInSpaceApi
return flow { return flow {
while (true) { while (true) {
val position = peopleInSpaceApi.fetchISSPosition().iss_position val position = api.fetchISSPosition().iss_position
emit(position) emit(position)
logger.d { position.toString() } logger.d { position.toString() }
delay(POLL_INTERVAL) delay(POLL_INTERVAL)

View file

@ -1,43 +0,0 @@
package com.surrus.peopleinspace
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.surrus.common.di.commonModule
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.common.repository.platformModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.koin.core.context.startKoin
import org.koin.dsl.module
import org.koin.test.KoinTest
import org.koin.test.inject
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertTrue
class PeopleInSpaceTest: KoinTest {
private val repo : PeopleInSpaceRepositoryInterface by inject()
@BeforeTest
fun setUp() {
Dispatchers.setMain(StandardTestDispatcher())
startKoin{
modules(
commonModule(true),
platformModule(),
module {
single { PeopleInSpaceDatabaseWrapper(null) }
}
)
}
}
@Test
fun testGetPeople() = runTest {
val result = repo.fetchPeople()
println(result)
assertTrue(result.isNotEmpty())
}
}

View file

@ -0,0 +1,18 @@
package com.surrus.peopleinspace
import com.surrus.common.di.initKoin
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertTrue
class PeopleInSpaceTest {
@Test
fun testGetPeople() = runBlocking {
val koin = initKoin(enableNetworkLogs = true).koin
val repo = koin.get<PeopleInSpaceRepositoryInterface>()
val result = repo.fetchPeople()
println(result)
assertTrue(result.isNotEmpty())
}
}

View file

@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") kotlin("jvm")
id("org.jetbrains.compose") version Versions.composeDesktopWeb id("org.jetbrains.compose") version "1.0.1-rc2"
application application
} }

View file

@ -1,6 +1,6 @@
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
id("org.jetbrains.compose") version Versions.composeDesktopWeb id("org.jetbrains.compose") version "1.0.1-rc2"
} }
version = "1.0" version = "1.0"

View file

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip

View file

@ -1 +0,0 @@
build

View file

@ -1,41 +0,0 @@
plugins {
id("kotlin-platform-jvm")
id("org.jetbrains.kotlin.plugin.spring") version("1.6.10")
id("org.jetbrains.kotlin.plugin.serialization")
id("org.springframework.boot") version("2.5.6")
id("com.google.cloud.tools.appengine") version("2.4.2")
id("com.github.johnrengelman.shadow")
}
dependencies {
implementation("com.expediagroup:graphql-kotlin-spring-server:5.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.3.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
testImplementation("com.squareup.okhttp3:okhttp:4.9.3")
with(Deps.Log) {
implementation(logback)
}
implementation(project(":common"))
}
kotlin {
sourceSets.all {
languageSettings {
optIn("kotlin.RequiresOptIn")
}
}
}
appengine {
stage {
setArtifact(tasks.named("bootJar").flatMap { (it as Jar).archiveFile })
}
deploy {
projectId = "peopleinspace-graphql"
version = "GCLOUD_CONFIG"
}
}

View file

@ -1,3 +0,0 @@
runtime: java11
entrypoint: java -Xmx64m -jar graphql-server.jar

View file

@ -1,20 +0,0 @@
package com.surrus.peopleinspace
import com.surrus.common.di.initKoin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.ConfigurableApplicationContext
val koin = initKoin(enableNetworkLogs = true).koin
@SpringBootApplication
class DefaultApplication {
}
fun runServer(): ConfigurableApplicationContext {
return runApplication<DefaultApplication>()
}

View file

@ -1,37 +0,0 @@
package com.surrus.peopleinspace
import com.expediagroup.graphql.server.operations.Subscription
import com.surrus.common.remote.IssPosition
import com.surrus.common.remote.PeopleInSpaceApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.reactive.asPublisher
import org.reactivestreams.Publisher
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class IssPositionSubscription : Subscription {
private val logger: Logger = LoggerFactory.getLogger(IssPositionSubscription::class.java)
private var peopleInSpaceApi: PeopleInSpaceApi = koin.get()
fun issPosition(): Publisher<IssPosition> {
return flow {
while (true) {
val position = peopleInSpaceApi.fetchISSPosition().iss_position
logger.info("ISS position = $position")
emit(position)
delay(POLL_INTERVAL)
}
}.asPublisher()
}
companion object {
private const val POLL_INTERVAL = 10000L
}
}

View file

@ -1,15 +0,0 @@
package com.surrus.peopleinspace
import com.expediagroup.graphql.server.operations.Query
import com.surrus.common.remote.PeopleInSpaceApi
import com.surrus.common.remote.Assignment
import org.springframework.stereotype.Component
data class People(val people: List<Assignment>)
@Component
class RootQuery : Query {
private var peopleInSpaceApi: PeopleInSpaceApi = koin.get()
suspend fun allPeople(): People = People(peopleInSpaceApi.fetchPeople().people)
}

View file

@ -1,6 +0,0 @@
package com.surrus.peopleinspace
fun main(args: Array<String>) {
runServer()
}

View file

@ -1,2 +0,0 @@
graphql:
packages: "com.surrus"

View file

@ -1,10 +1,10 @@
target 'PeopleInSpaceSwiftUI' do target 'PeopleInSpaceSwiftUI' do
pod 'common', :path => '../../common' pod 'common', :path => '../../common'
pod 'KMPNativeCoroutinesAsync', '0.11.1' pod 'KMPNativeCoroutinesAsync', '0.10.0'
end end
target 'PeopleInSpaceWidgetExtension' do target 'PeopleInSpaceWidgetExtension' do
pod 'common', :path => '../../common' pod 'common', :path => '../../common'
pod 'KMPNativeCoroutinesCombine', '0.11.1' pod 'KMPNativeCoroutinesCombine', '0.10.0'
end end

View file

@ -1,15 +1,15 @@
PODS: PODS:
- common (1.0) - common (1.0)
- KMPNativeCoroutinesAsync (0.11.1): - KMPNativeCoroutinesAsync (0.10.0):
- KMPNativeCoroutinesCore (= 0.11.1) - KMPNativeCoroutinesCore (= 0.10.0)
- KMPNativeCoroutinesCombine (0.11.1): - KMPNativeCoroutinesCombine (0.10.0):
- KMPNativeCoroutinesCore (= 0.11.1) - KMPNativeCoroutinesCore (= 0.10.0)
- KMPNativeCoroutinesCore (0.11.1) - KMPNativeCoroutinesCore (0.10.0)
DEPENDENCIES: DEPENDENCIES:
- common (from `../../common`) - common (from `../../common`)
- KMPNativeCoroutinesAsync (= 0.11.1) - KMPNativeCoroutinesAsync (= 0.10.0)
- KMPNativeCoroutinesCombine (= 0.11.1) - KMPNativeCoroutinesCombine (= 0.10.0)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
@ -23,10 +23,10 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
common: 5def32d6e7131f79a49997cca9bbcc9cbd11e08a common: 5def32d6e7131f79a49997cca9bbcc9cbd11e08a
KMPNativeCoroutinesAsync: 1e6e09efe1fb04a9412483680829dbd35b55ef18 KMPNativeCoroutinesAsync: 66512d0bf4933d0b160416795284beb8ecf089b8
KMPNativeCoroutinesCombine: 43a442a5e50ee8fcbb8633a361d12907fe933920 KMPNativeCoroutinesCombine: 04db768364187c30210303b3bdf0731e89ccfeb5
KMPNativeCoroutinesCore: ed98a12d8337f861088f6b79636ffb29ff9bb2ad KMPNativeCoroutinesCore: 2e2573a75f27178d4cbd7be385f0f0a54416a47a
PODFILE CHECKSUM: d51c9a2ba0fb9109f719acf8878fb54882154ace PODFILE CHECKSUM: d3402fa215303d8817450a84072c6d7e3d30d094
COCOAPODS: 1.11.2 COCOAPODS: 1.11.2

View file

@ -22,22 +22,14 @@ This library solves both of these limitations 😄.
## Compatibility ## Compatibility
> **NOTE:** at the moment the [new Kotlin Native memory model][new-mm] is still experimental.
> The regular versions of this library are therefore currently using the [`-native-mt`][native-mt] versions
> of the kotlinx.coroutines library.
> If you would like to try the new memory model, please use the `-new-mm` versions instead.
[new-mm]: https://github.com/JetBrains/kotlin/blob/0b871d7534a9c8e90fb9ad61cd5345716448d08c/kotlin-native/NEW_MM.md
[native-mt]: https://github.com/kotlin/kotlinx.coroutines/issues/462
As of version `0.10.0` the library uses Kotlin version `1.6.10`. As of version `0.10.0` the library uses Kotlin version `1.6.10`.
Compatibility versions for older Kotlin versions are also available: Compatibility versions for older and early access Kotlin versions are also available:
| Version | Version suffix | Kotlin | Coroutines | | Version | Version suffix | Kotlin | Coroutines |
|--------------|-----------------|:----------:|:-------------------:| |--------------|-----------------|:----------:|:-------------------:|
| _latest_ | -new-mm | 1.6.10 | 1.6.0 | | _latest_ | -new-mm | 1.6.10 | 1.6.0-RC3 |
| **_latest_** | **_no suffix_** | **1.6.10** | **1.6.0-native-mt** | | **_latest_** | **_no suffix_** | **1.6.10** | **1.5.2-native-mt** |
| _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.6.0-native-mt | | _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.5.2-native-mt |
| 0.9.0 | -new-mm-3 | 1.6.0 | 1.6.0-RC2 | | 0.9.0 | -new-mm-3 | 1.6.0 | 1.6.0-RC2 |
| 0.8.0 | _no suffix_ | 1.5.30 | 1.5.2-native-mt | | 0.8.0 | _no suffix_ | 1.5.30 | 1.5.2-native-mt |
| 0.8.0 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt | | 0.8.0 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt |

View file

@ -22,22 +22,14 @@ This library solves both of these limitations 😄.
## Compatibility ## Compatibility
> **NOTE:** at the moment the [new Kotlin Native memory model][new-mm] is still experimental.
> The regular versions of this library are therefore currently using the [`-native-mt`][native-mt] versions
> of the kotlinx.coroutines library.
> If you would like to try the new memory model, please use the `-new-mm` versions instead.
[new-mm]: https://github.com/JetBrains/kotlin/blob/0b871d7534a9c8e90fb9ad61cd5345716448d08c/kotlin-native/NEW_MM.md
[native-mt]: https://github.com/kotlin/kotlinx.coroutines/issues/462
As of version `0.10.0` the library uses Kotlin version `1.6.10`. As of version `0.10.0` the library uses Kotlin version `1.6.10`.
Compatibility versions for older Kotlin versions are also available: Compatibility versions for older and early access Kotlin versions are also available:
| Version | Version suffix | Kotlin | Coroutines | | Version | Version suffix | Kotlin | Coroutines |
|--------------|-----------------|:----------:|:-------------------:| |--------------|-----------------|:----------:|:-------------------:|
| _latest_ | -new-mm | 1.6.10 | 1.6.0 | | _latest_ | -new-mm | 1.6.10 | 1.6.0-RC3 |
| **_latest_** | **_no suffix_** | **1.6.10** | **1.6.0-native-mt** | | **_latest_** | **_no suffix_** | **1.6.10** | **1.5.2-native-mt** |
| _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.6.0-native-mt | | _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.5.2-native-mt |
| 0.9.0 | -new-mm-3 | 1.6.0 | 1.6.0-RC2 | | 0.9.0 | -new-mm-3 | 1.6.0 | 1.6.0-RC2 |
| 0.8.0 | _no suffix_ | 1.5.30 | 1.5.2-native-mt | | 0.8.0 | _no suffix_ | 1.5.30 | 1.5.2-native-mt |
| 0.8.0 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt | | 0.8.0 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt |

View file

@ -22,22 +22,14 @@ This library solves both of these limitations 😄.
## Compatibility ## Compatibility
> **NOTE:** at the moment the [new Kotlin Native memory model][new-mm] is still experimental.
> The regular versions of this library are therefore currently using the [`-native-mt`][native-mt] versions
> of the kotlinx.coroutines library.
> If you would like to try the new memory model, please use the `-new-mm` versions instead.
[new-mm]: https://github.com/JetBrains/kotlin/blob/0b871d7534a9c8e90fb9ad61cd5345716448d08c/kotlin-native/NEW_MM.md
[native-mt]: https://github.com/kotlin/kotlinx.coroutines/issues/462
As of version `0.10.0` the library uses Kotlin version `1.6.10`. As of version `0.10.0` the library uses Kotlin version `1.6.10`.
Compatibility versions for older Kotlin versions are also available: Compatibility versions for older and early access Kotlin versions are also available:
| Version | Version suffix | Kotlin | Coroutines | | Version | Version suffix | Kotlin | Coroutines |
|--------------|-----------------|:----------:|:-------------------:| |--------------|-----------------|:----------:|:-------------------:|
| _latest_ | -new-mm | 1.6.10 | 1.6.0 | | _latest_ | -new-mm | 1.6.10 | 1.6.0-RC3 |
| **_latest_** | **_no suffix_** | **1.6.10** | **1.6.0-native-mt** | | **_latest_** | **_no suffix_** | **1.6.10** | **1.5.2-native-mt** |
| _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.6.0-native-mt | | _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.5.2-native-mt |
| 0.9.0 | -new-mm-3 | 1.6.0 | 1.6.0-RC2 | | 0.9.0 | -new-mm-3 | 1.6.0 | 1.6.0-RC2 |
| 0.8.0 | _no suffix_ | 1.5.30 | 1.5.2-native-mt | | 0.8.0 | _no suffix_ | 1.5.30 | 1.5.2-native-mt |
| 0.8.0 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt | | 0.8.0 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt |

View file

@ -1,15 +1,15 @@
PODS: PODS:
- common (1.0) - common (1.0)
- KMPNativeCoroutinesAsync (0.11.1): - KMPNativeCoroutinesAsync (0.10.0):
- KMPNativeCoroutinesCore (= 0.11.1) - KMPNativeCoroutinesCore (= 0.10.0)
- KMPNativeCoroutinesCombine (0.11.1): - KMPNativeCoroutinesCombine (0.10.0):
- KMPNativeCoroutinesCore (= 0.11.1) - KMPNativeCoroutinesCore (= 0.10.0)
- KMPNativeCoroutinesCore (0.11.1) - KMPNativeCoroutinesCore (0.10.0)
DEPENDENCIES: DEPENDENCIES:
- common (from `../../common`) - common (from `../../common`)
- KMPNativeCoroutinesAsync (= 0.11.1) - KMPNativeCoroutinesAsync (= 0.10.0)
- KMPNativeCoroutinesCombine (= 0.11.1) - KMPNativeCoroutinesCombine (= 0.10.0)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
@ -23,10 +23,10 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
common: 5def32d6e7131f79a49997cca9bbcc9cbd11e08a common: 5def32d6e7131f79a49997cca9bbcc9cbd11e08a
KMPNativeCoroutinesAsync: 1e6e09efe1fb04a9412483680829dbd35b55ef18 KMPNativeCoroutinesAsync: 66512d0bf4933d0b160416795284beb8ecf089b8
KMPNativeCoroutinesCombine: 43a442a5e50ee8fcbb8633a361d12907fe933920 KMPNativeCoroutinesCombine: 04db768364187c30210303b3bdf0731e89ccfeb5
KMPNativeCoroutinesCore: ed98a12d8337f861088f6b79636ffb29ff9bb2ad KMPNativeCoroutinesCore: 2e2573a75f27178d4cbd7be385f0f0a54416a47a
PODFILE CHECKSUM: d51c9a2ba0fb9109f719acf8878fb54882154ace PODFILE CHECKSUM: d3402fa215303d8817450a84072c6d7e3d30d094
COCOAPODS: 1.11.2 COCOAPODS: 1.11.2

View file

@ -2,21 +2,14 @@ pluginManagement {
repositories { repositories {
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
} maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
resolutionStrategy {
eachPlugin {
if (requested.id.id.startsWith("com.google.cloud.tools.appengine")) {
useModule("com.google.cloud.tools:appengine-gradle-plugin:${requested.version}")
}
}
} }
} }
rootProject.name = "PeopleInSpace" rootProject.name = "PeopleInSpace"
include(":app", ":common", ":compose-desktop") include(":app", ":common", ":compose-desktop")
include(":wearApp")
include(":web") include(":web")
include(":compose-web") include(":compose-web")
include(":backend") include(":backend")
include(":graphql-server") include(":wearApp")

View file

@ -85,9 +85,7 @@ dependencies {
debugImplementation(composeUiTestManifest) debugImplementation(composeUiTestManifest)
} }
with(Deps.Glance) { implementation("androidx.glance:glance-wear:1.0.0-SNAPSHOT")
implementation(tiles)
}
implementation(project(":common")) implementation(project(":common"))
} }

View file

@ -34,11 +34,16 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
val rotaryEventDispatcher = RotaryEventDispatcher()
CompositionLocalProvider( CompositionLocalProvider(
LocalImageLoader provides imageLoader, LocalImageLoader provides imageLoader,
LocalRotaryEventDispatcher provides rotaryEventDispatcher,
) { ) {
val navController = rememberSwipeDismissableNavController() val navController = rememberSwipeDismissableNavController()
RotaryEventHandlerSetup(rotaryEventDispatcher)
SwipeDismissableNavHost( SwipeDismissableNavHost(
navController = navController, navController = navController,
startDestination = Screen.PersonList.route startDestination = Screen.PersonList.route

View file

@ -23,7 +23,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -51,6 +50,7 @@ fun PersonDetailsScreen(personName: String) {
} }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
RotaryEventState(scrollState)
PersonDetailsScreen(person, scrollState) PersonDetailsScreen(person, scrollState)
} }
@ -67,15 +67,10 @@ private fun PersonDetailsScreen(
Vignette(vignettePosition = VignettePosition.Bottom) Vignette(vignettePosition = VignettePosition.Bottom)
} }
}, },
positionIndicator = { positionIndicator = { PositionIndicator(scrollState = scrollState) }
if (person != null) {
PositionIndicator(scrollState = scrollState)
}
}
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.rotaryEventHandler(scrollState)
.fillMaxSize() .fillMaxSize()
.padding(horizontal = if (LocalConfiguration.current.isScreenRound) 18.dp else 8.dp) .padding(horizontal = if (LocalConfiguration.current.isScreenRound) 18.dp else 8.dp)
.verticalScroll(scrollState), .verticalScroll(scrollState),

View file

@ -13,13 +13,11 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -37,13 +35,9 @@ import androidx.wear.compose.material.Button
import androidx.wear.compose.material.ButtonDefaults import androidx.wear.compose.material.ButtonDefaults
import androidx.wear.compose.material.Card import androidx.wear.compose.material.Card
import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.PositionIndicator
import androidx.wear.compose.material.Scaffold
import androidx.wear.compose.material.ScalingLazyColumn import androidx.wear.compose.material.ScalingLazyColumn
import androidx.wear.compose.material.ScalingLazyListState import androidx.wear.compose.material.ScalingLazyListState
import androidx.wear.compose.material.Text import androidx.wear.compose.material.Text
import androidx.wear.compose.material.Vignette
import androidx.wear.compose.material.VignettePosition
import androidx.wear.compose.material.rememberScalingLazyListState import androidx.wear.compose.material.rememberScalingLazyListState
import coil.annotation.ExperimentalCoilApi import coil.annotation.ExperimentalCoilApi
import coil.compose.rememberImagePainter import coil.compose.rememberImagePainter
@ -61,7 +55,9 @@ fun PersonListScreen(
peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel() peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel()
) { ) {
val peopleState by peopleInSpaceViewModel.peopleInSpace.collectAsState() val peopleState by peopleInSpaceViewModel.peopleInSpace.collectAsState()
PersonListScreen(peopleState, personSelected, issMapClick) val scrollState = rememberScalingLazyListState()
RotaryEventState(scrollState)
PersonListScreen(peopleState, personSelected, issMapClick, scrollState)
} }
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@ -77,43 +73,24 @@ fun PersonListScreen(
visible = people != null, visible = people != null,
enter = slideInVertically() enter = slideInVertically()
) { ) {
Scaffold( if (people != null) {
vignette = { if (people.isNotEmpty()) {
if (!people.isNullOrEmpty()) {
Vignette(VignettePosition.Bottom)
}
},
positionIndicator = {
if (!people.isNullOrEmpty()) {
PositionIndicator(scrollState)
}
}
) {
if (people.isNullOrEmpty()) {
EmptyPersonList()
} else {
PersonList(people, personSelected, issMapClick, scrollState) PersonList(people, personSelected, issMapClick, scrollState)
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Card(
onClick = { },
modifier = Modifier.testTag(NoPeopleTag)
) {
Text("No people in space!")
}
}
} }
} }
} }
} }
} }
@Composable
fun EmptyPersonList() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Card(
onClick = {},
modifier = Modifier.testTag(NoPeopleTag)
) {
Text("No people in space!")
}
}
}
@Composable @Composable
fun PersonList( fun PersonList(
people: List<Assignment>, people: List<Assignment>,
@ -121,26 +98,15 @@ fun PersonList(
issMapClick: () -> Unit, issMapClick: () -> Unit,
scrollState: ScalingLazyListState = rememberScalingLazyListState(), scrollState: ScalingLazyListState = rememberScalingLazyListState(),
) { ) {
val configuration = LocalConfiguration.current
// extra content padding to prevent bottom item from crop
val extraBottomPadding = remember {
if (configuration.isScreenRound) 40.dp else 0.dp
}
ScalingLazyColumn( ScalingLazyColumn(
modifier = Modifier contentPadding = PaddingValues(8.dp),
.testTag(PersonListTag) modifier = Modifier.testTag(PersonListTag),
.rotaryEventHandler(scrollState)
.padding(horizontal = 4.dp),
contentPadding = PaddingValues(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 8.dp + extraBottomPadding),
state = scrollState, state = scrollState,
) { ) {
item { item {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button( Button(
modifier = Modifier modifier = Modifier.size(ButtonDefaults.SmallButtonSize).wrapContentSize(),
.size(ButtonDefaults.SmallButtonSize)
.wrapContentSize(),
onClick = issMapClick onClick = issMapClick
) { ) {
// https://www.svgrepo.com/svg/170716/international-space-station // https://www.svgrepo.com/svg/170716/international-space-station

View file

@ -0,0 +1,75 @@
package com.surrus.peopleinspace
import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.InputDeviceCompat
import androidx.core.view.MotionEventCompat
import androidx.core.view.ViewConfigurationCompat
import kotlinx.coroutines.launch
val LocalRotaryEventDispatcher = staticCompositionLocalOf<RotaryEventDispatcher> {
noLocalProvidedFor("LocalRotaryEventDispatcher")
}
/**
* Dispatcher to link rotary event to [ScrollableState].
* The instance should be set up by calling [RotaryEventHandlerSetup] function.
*/
class RotaryEventDispatcher(
var scrollState: ScrollableState? = null
) {
suspend fun onRotate(delta: Float): Float? =
scrollState?.scrollBy(delta)
}
/**
* Custom rotary event setup (Currently, Column / LazyColumn doesn't handle rotary event.)
* Refer to https://developer.android.com/training/wearables/user-input/rotary-input
*/
@Composable
fun RotaryEventHandlerSetup(rotaryEventDispatcher: RotaryEventDispatcher) {
val view = LocalView.current
val context = LocalContext.current
val scope = rememberCoroutineScope()
view.requestFocus()
view.setOnGenericMotionListener { _, event ->
if (event?.action != MotionEvent.ACTION_SCROLL ||
!event.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER)
) {
return@setOnGenericMotionListener false
}
val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) *
ViewConfigurationCompat.getScaledVerticalScrollFactor(
ViewConfiguration.get(context), context
)
scope.launch {
rotaryEventDispatcher.onRotate(delta)
}
true
}
}
/**
* Register a [ScrollableState] to [LocalRotaryEventDispatcher]
*/
@Composable
fun RotaryEventState(scrollState: ScrollableState?) {
val dispatcher = LocalRotaryEventDispatcher.current
SideEffect {
dispatcher.scrollState = scrollState
}
}
private fun noLocalProvidedFor(name: String): Nothing {
error("CompositionLocal $name not present")
}

View file

@ -1,47 +0,0 @@
package com.surrus.peopleinspace
import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.input.pointer.RequestDisallowInterceptTouchEvent
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.InputDeviceCompat
import androidx.core.view.MotionEventCompat
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.rotaryEventHandler(scrollState: ScrollableState): Modifier = composed {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val scaledVerticalScrollFactor =
remember { ViewConfiguration.get(context).scaledVerticalScrollFactor }
val view = LocalView.current
SideEffect {
// Activate rotary scrolling
view.requestFocus()
}
pointerInteropFilter(RequestDisallowInterceptTouchEvent()) { event ->
if (event.action != MotionEvent.ACTION_SCROLL ||
!event.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER)
) {
false
} else {
val delta = -event.getAxisValue(MotionEventCompat.AXIS_SCROLL) *
scaledVerticalScrollFactor
scope.launch {
scrollState.scrollBy(delta)
}
true
}
}
}

View file

@ -8,16 +8,16 @@ import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier import androidx.glance.GlanceModifier
import androidx.glance.layout.Column import androidx.glance.layout.Column
import androidx.glance.layout.Text
import androidx.glance.layout.padding import androidx.glance.layout.padding
import androidx.glance.text.FontWeight import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider import androidx.glance.unit.ColorProvider
import com.surrus.common.remote.Assignment import com.surrus.common.remote.Assignment
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.peopleinspace.tile.util.BaseGlanceTileService import com.surrus.peopleinspace.tile.util.BaseGlanceTileService
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import org.koin.core.component.inject import org.koin.android.ext.android.inject
class PeopleInSpaceTile : BaseGlanceTileService<PeopleInSpaceTile.Data>() { class PeopleInSpaceTile : BaseGlanceTileService<PeopleInSpaceTile.Data>() {
val repository: PeopleInSpaceRepositoryInterface by inject() val repository: PeopleInSpaceRepositoryInterface by inject()

View file

@ -1,16 +1,11 @@
package com.surrus.peopleinspace.tile.util package com.surrus.peopleinspace.tile.util
import android.content.Context
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.glance.wear.tiles.GlanceTileService import androidx.glance.wear.GlanceTileService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
abstract class BaseGlanceTileService<T> : GlanceTileService(), KoinComponent {
val context: Context by inject()
abstract class BaseGlanceTileService<T> : GlanceTileService() {
@Composable @Composable
override fun Content() { override fun Content() {
// Terrible hack for lack of suspend load function // Terrible hack for lack of suspend load function