Compare commits
No commits in common. "main" and "js_apple_silicon" have entirely different histories.
main
...
js_apple_s
40 changed files with 222 additions and 407 deletions
2
.github/workflows/android.yml
vendored
2
.github/workflows/android.yml
vendored
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -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
|
||||||
|
|
1
graphql-server/.gitignore
vendored
1
graphql-server/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
build
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
runtime: java11
|
|
||||||
|
|
||||||
entrypoint: java -Xmx64m -jar graphql-server.jar
|
|
|
@ -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>()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package com.surrus.peopleinspace
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
runServer()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
graphql:
|
|
||||||
packages: "com.surrus"
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
22
ios/PeopleInSpaceSwiftUI/Pods/Manifest.lock
generated
22
ios/PeopleInSpaceSwiftUI/Pods/Manifest.lock
generated
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"))
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue