Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
81f2450422 |
225 changed files with 907 additions and 8268 deletions
2
.github/workflows/android.yml
vendored
2
.github/workflows/android.yml
vendored
|
@ -59,4 +59,4 @@ jobs:
|
|||
with:
|
||||
api-level: 29
|
||||
target: google_apis
|
||||
script: ./gradlew app:connectedAndroidTest
|
||||
script: ./gradlew app:connectedAndroidTest common:connectedAndroidTest
|
42
README.md
42
README.md
|
@ -2,10 +2,8 @@
|
|||
|
||||
Minimal **Kotlin Multiplatform** project with SwiftUI, Jetpack Compose, Compose for Wear OS, Compose for Desktop, Compose for Web, and Kotlin/JS + React clients along with Ktor backend. Currently running on
|
||||
* Android (Jetpack Compose)
|
||||
* Android App Widget (Compose based Glance API - contributed by https://github.com/yschimke)
|
||||
* Wear OS (Compose for Wear OS - primarily developed by https://github.com/yschimke)
|
||||
* iOS (SwiftUI)
|
||||
* iOS App Widget (SwiftUI)
|
||||
* watchOS (SwiftUI) (contributed by https://github.com/nealsanche)
|
||||
* macOS (SwiftUI)
|
||||
* Desktop (Compose for Desktop)
|
||||
|
@ -29,12 +27,11 @@ Related posts:
|
|||
* [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'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
|
||||
Multiplatform project and also to hopefully help someone just starting to explore KMP to get up and running for first time (and is of course
|
||||
primarily focused on use of Jetpack Compose and SwiftUI). If you're at the stage of moving
|
||||
primarily focussed on use of Jetpack Compose and SwiftUI). If you're at stage of moving
|
||||
beyond this then I'd definitely recommend checking out [KaMPKit](https://github.com/touchlab/KaMPKit) from Touchlab.
|
||||
I also have the following samples that demonstrate the use of a variety of Kotlin Multiplatform libraries (and also use Jetpack Compose and SwiftUI).
|
||||
|
||||
|
@ -47,7 +44,7 @@ I also have the following samples that demonstrate the use of a variety of Kotli
|
|||
|
||||
|
||||
### Building
|
||||
You need to use Android Studio Arctic Fox (**note: Java 11 is now the minimum version required**). Requires XCode 13.2 or later (due to use of new Swift 5.5 concurrnecy APIs).
|
||||
You need to use Android Studio Arctic Fox (**note: Java 11 is now the minimum version required**). Most recently tested with XCode v12 and v13.
|
||||
|
||||
When opening iOS/watchOS/macOS projects remember to open `.xcworkspace` file (and not `.xcodeproj` one).
|
||||
|
||||
|
@ -67,7 +64,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)
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
|
@ -76,11 +73,6 @@ Have tested this out in Google App Engine deployment. Using shadowJar plugin to
|
|||
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
|
||||
|
||||
|
@ -88,30 +80,19 @@ There's a GraphQL module (`graphql-server`) which can be run locally using `./gr
|
|||
<br/>
|
||||
<img width="546" alt="Screenshot 2021-02-27 at 12 09 02" src="https://user-images.githubusercontent.com/6302/109386736-ac1f0700-78f4-11eb-812e-4bf971a8c2a7.png">
|
||||
|
||||
**Android (Jetpack Compose)**
|
||||
<br/>
|
||||
<img width="555" alt="Screenshot 2021-03-07 at 17 03 46" src="https://user-images.githubusercontent.com/6302/110248059-2ab81c00-7f67-11eb-9b3a-2b04d1be43ef.png">
|
||||
|
||||
|
||||
**watchOS (SwiftUI)**
|
||||
<br/>
|
||||
<img width="250" alt="watchOS Screenshot 1" src="https://user-images.githubusercontent.com/6302/139499100-dc5112b0-04b9-4bdc-9c30-9975f3608eb3.png">
|
||||
<img width="250" alt="watch0S Screenshot 2" src="https://user-images.githubusercontent.com/6302/139499115-944b241d-8e92-428b-b86c-f599b456c4bf.png">
|
||||
|
||||
|
||||
|
||||
**Wear OS (Wear Compose)**
|
||||
<br/>
|
||||
<img width="250" alt="Wear Compose Screenshot 1" src="https://user-images.githubusercontent.com/6302/137623548-ac51ca72-572e-4009-8b34-315defdf93a5.png">
|
||||
<img width="250" alt="Wear Compose Screenshot 2" src="https://user-images.githubusercontent.com/6302/137640396-851489bb-e41d-47ef-badb-e2d22454eee4.png">
|
||||
<img width="250" alt="Wear Compose Screenshot 3" src="https://user-images.githubusercontent.com/6302/139468900-16ad4e95-41dc-427f-977c-b893b1751c78.png">
|
||||
|
||||
|
||||
**macOS (SwiftUI)**
|
||||
<br/>
|
||||
<img width="937" alt="Screenshot 2021-06-01 at 20 02 31" src="https://user-images.githubusercontent.com/6302/120376983-6ec37e80-c314-11eb-8279-7acc0c2d5206.png">
|
||||
|
||||
|
||||
**Android (Jetpack Compose)**
|
||||
<br/>
|
||||
<img width="555" alt="Screenshot 2021-03-07 at 17 03 46" src="https://user-images.githubusercontent.com/6302/110248059-2ab81c00-7f67-11eb-9b3a-2b04d1be43ef.png">
|
||||
|
||||
**Wear OS (Jetpack Compose)**
|
||||
<br/>
|
||||
<img width="300" alt="Screenshot 2021-07-02 at 18 30 03" src="https://user-images.githubusercontent.com/6302/137623548-ac51ca72-572e-4009-8b34-315defdf93a5.png">
|
||||
<img width="300" alt="Screenshot 2021-07-02 at 18 30 03" src="https://user-images.githubusercontent.com/6302/137640396-851489bb-e41d-47ef-badb-e2d22454eee4.png">
|
||||
|
||||
|
||||
|
||||
|
@ -141,4 +122,3 @@ There's a GraphQL module (`graphql-server`) which can be run locally using `./gr
|
|||
* [SQLDelight](https://github.com/cashapp/sqldelight)
|
||||
* [Jetpack Compose](https://developer.android.com/jetpack/compose)
|
||||
* [SwiftUI](https://developer.apple.com/documentation/swiftui)
|
||||
* [KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines)
|
||||
|
|
|
@ -22,7 +22,7 @@ android {
|
|||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = Versions.composeCompiler
|
||||
kotlinCompilerExtensionVersion = Versions.compose
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -64,12 +64,7 @@ dependencies {
|
|||
implementation(activityCompose)
|
||||
}
|
||||
|
||||
with(Deps.Glance) {
|
||||
implementation(appwidget)
|
||||
}
|
||||
|
||||
with(Deps.Compose) {
|
||||
implementation(compiler)
|
||||
implementation(ui)
|
||||
implementation(uiGraphics)
|
||||
implementation(foundationLayout)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.surrus.peopleinspace">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
|
@ -23,31 +24,6 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name="com.surrus.peopleinspace.glance.PeopleInSpaceWidgetReceiver"
|
||||
android:label="People in Space"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.surrus.peopleinspace.glance.ISSMapWidgetReceiver"
|
||||
android:label="ISS Map"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/iss_widget_info" />
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,33 +1,26 @@
|
|||
package com.surrus.peopleinspace
|
||||
|
||||
import android.app.Application
|
||||
import co.touchlab.kermit.Logger
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.surrus.common.di.initKoin
|
||||
import com.surrus.peopleinspace.di.appModule
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import org.koin.core.logger.Level
|
||||
import org.osmdroid.config.Configuration
|
||||
import java.io.File
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class PeopleInSpaceApplication : Application() {
|
||||
class PeopleInSpaceApplication : Application(), KoinComponent {
|
||||
private val logger: Kermit by inject()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// needed for osmandroid
|
||||
Configuration.getInstance().userAgentValue = BuildConfig.APPLICATION_ID
|
||||
Configuration.getInstance().osmdroidTileCache = File(cacheDir, "osm").also {
|
||||
it.mkdir()
|
||||
}
|
||||
|
||||
initKoin {
|
||||
// https://github.com/InsertKoinIO/koin/issues/1188
|
||||
androidLogger(if (BuildConfig.DEBUG) Level.ERROR else Level.NONE)
|
||||
androidLogger()
|
||||
androidContext(this@PeopleInSpaceApplication)
|
||||
modules(appModule)
|
||||
}
|
||||
|
||||
Logger.d { "PeopleInSpaceApplication" }
|
||||
logger.d { "PeopleInSpaceApplication" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
package com.surrus.peopleinspace.glance
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.ExperimentalUnitApi
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.glance.GlanceModifier
|
||||
import androidx.glance.Image
|
||||
import androidx.glance.ImageProvider
|
||||
import androidx.glance.action.actionStartActivity
|
||||
import androidx.glance.action.clickable
|
||||
import androidx.glance.background
|
||||
import androidx.glance.layout.Box
|
||||
import androidx.glance.layout.fillMaxSize
|
||||
import androidx.glance.text.Text
|
||||
import androidx.glance.text.TextStyle
|
||||
import androidx.glance.unit.ColorProvider
|
||||
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
|
||||
import com.surrus.peopleinspace.R
|
||||
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidget
|
||||
import com.surrus.peopleinspace.ui.MainActivity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.inject
|
||||
import org.osmdroid.tileprovider.MapTileProviderBasic
|
||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.Projection
|
||||
import org.osmdroid.views.drawing.MapSnapshot
|
||||
import org.osmdroid.views.overlay.IconOverlay
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class ISSMapWidget : BaseGlanceAppWidget<ISSMapWidget.Data>() {
|
||||
val repository: PeopleInSpaceRepositoryInterface by inject()
|
||||
|
||||
data class Data(val bitmap: Bitmap?)
|
||||
|
||||
override suspend fun loadData(): Data {
|
||||
val issPosition = repository.pollISSPosition().first()
|
||||
|
||||
val issPositionPoint = GeoPoint(issPosition.latitude, issPosition.longitude)
|
||||
|
||||
val stationMarker = IconOverlay(
|
||||
issPositionPoint,
|
||||
context.resources.getDrawable(R.drawable.ic_iss, context.theme)
|
||||
)
|
||||
|
||||
val source = TileSourceFactory.DEFAULT_TILE_SOURCE
|
||||
val projection = Projection(5.0, 480, 240, issPositionPoint, 0f, true, false, 0, 0)
|
||||
|
||||
val bitmap = withContext(Dispatchers.Main) {
|
||||
suspendCoroutine<Bitmap> { cont ->
|
||||
val mapSnapshot = MapSnapshot(
|
||||
{
|
||||
if (it.status == MapSnapshot.Status.CANVAS_OK) {
|
||||
val bitmap = Bitmap.createBitmap(it.bitmap)
|
||||
cont.resume(bitmap)
|
||||
}
|
||||
},
|
||||
MapSnapshot.INCLUDE_FLAG_UPTODATE or MapSnapshot.INCLUDE_FLAG_SCALED,
|
||||
MapTileProviderBasic(context, source, null),
|
||||
listOf(stationMarker),
|
||||
projection
|
||||
)
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
mapSnapshot.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Data(bitmap)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnitApi::class)
|
||||
@Composable
|
||||
override fun Content(data: Data?) {
|
||||
Box(
|
||||
modifier = GlanceModifier.background(Color.DarkGray).fillMaxSize().clickable(
|
||||
actionStartActivity<MainActivity>()
|
||||
)
|
||||
) {
|
||||
val bitmap = data?.bitmap
|
||||
if (bitmap != null) {
|
||||
Image(
|
||||
modifier = GlanceModifier.fillMaxSize(),
|
||||
provider = ImageProvider(bitmap),
|
||||
contentDescription = "ISS Location"
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
"Loading ISS Map...",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color.White),
|
||||
fontSize = TextUnit(20f, TextUnitType.Sp)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.surrus.peopleinspace.glance
|
||||
|
||||
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidgetReceiver
|
||||
|
||||
class ISSMapWidgetReceiver : BaseGlanceAppWidgetReceiver<ISSMapWidget>() {
|
||||
override fun createWidget(): ISSMapWidget = ISSMapWidget()
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package com.surrus.peopleinspace.glance
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.ExperimentalUnitApi
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.glance.GlanceModifier
|
||||
import androidx.glance.appwidget.lazy.LazyColumn
|
||||
import androidx.glance.background
|
||||
import androidx.glance.layout.Row
|
||||
import androidx.glance.layout.padding
|
||||
import androidx.glance.text.FontWeight
|
||||
import androidx.glance.text.Text
|
||||
import androidx.glance.text.TextStyle
|
||||
import androidx.glance.unit.ColorProvider
|
||||
import com.surrus.common.remote.Assignment
|
||||
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
|
||||
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidget
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class PeopleInSpaceWidget : BaseGlanceAppWidget<PeopleInSpaceWidget.Data>() {
|
||||
val repository: PeopleInSpaceRepositoryInterface by inject()
|
||||
|
||||
data class Data(val people: List<Assignment>)
|
||||
|
||||
override suspend fun loadData(): Data {
|
||||
return Data(repository.fetchPeopleAsFlow().first())
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnitApi::class)
|
||||
@Composable
|
||||
override fun Content(data: Data?) {
|
||||
LazyColumn(
|
||||
modifier = GlanceModifier.background(Color.DarkGray).padding(horizontal = 8.dp)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
modifier = GlanceModifier.padding(bottom = 8.dp),
|
||||
text = "People in Space",
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color.White),
|
||||
fontSize = TextUnit(12f, TextUnitType.Sp),
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
)
|
||||
}
|
||||
if (data != null) {
|
||||
items(data.people.size) {
|
||||
Row {
|
||||
Text(
|
||||
text = data.people[it].name,
|
||||
style = TextStyle(
|
||||
color = ColorProvider(Color.White),
|
||||
fontSize = TextUnit(10f, TextUnitType.Sp)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.surrus.peopleinspace.glance
|
||||
|
||||
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidgetReceiver
|
||||
|
||||
class PeopleInSpaceWidgetReceiver : BaseGlanceAppWidgetReceiver<PeopleInSpaceWidget>() {
|
||||
override fun createWidget(): PeopleInSpaceWidget = PeopleInSpaceWidget()
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package com.surrus.peopleinspace.glance.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.glance.GlanceId
|
||||
import androidx.glance.LocalGlanceId
|
||||
import androidx.glance.LocalSize
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
abstract class BaseGlanceAppWidget<T>(initialData: T? = null) : GlanceAppWidget(), KoinComponent {
|
||||
val context: Context by inject()
|
||||
|
||||
var glanceId by mutableStateOf<GlanceId?>(null)
|
||||
var size by mutableStateOf<DpSize?>(null)
|
||||
var data by mutableStateOf<T?>(initialData)
|
||||
|
||||
private val coroutineScope = MainScope()
|
||||
|
||||
abstract suspend fun loadData(): T
|
||||
|
||||
fun initiateLoad() {
|
||||
coroutineScope.launch {
|
||||
data = loadData()
|
||||
|
||||
val currentGlanceId = snapshotFlow { glanceId }.filterNotNull().firstOrNull()
|
||||
|
||||
if (currentGlanceId != null) {
|
||||
update(context, currentGlanceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
glanceId = LocalGlanceId.current
|
||||
size = LocalSize.current
|
||||
|
||||
Content(data)
|
||||
}
|
||||
|
||||
@Composable
|
||||
abstract fun Content(data: T?)
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package com.surrus.peopleinspace.glance.util
|
||||
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
||||
abstract class BaseGlanceAppWidgetReceiver<T : BaseGlanceAppWidget<*>> : GlanceAppWidgetReceiver(),
|
||||
KoinComponent {
|
||||
override val glanceAppWidget: GlanceAppWidget
|
||||
get() {
|
||||
return createWidget().apply {
|
||||
this.initiateLoad()
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun createWidget(): T
|
||||
}
|
|
@ -5,11 +5,7 @@ import androidx.activity.ComponentActivity
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.material.BottomNavigation
|
||||
import androidx.compose.material.BottomNavigationItem
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
|
@ -18,16 +14,22 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.surrus.common.remote.Assignment
|
||||
import com.surrus.peopleinspace.BuildConfig
|
||||
import org.osmdroid.config.Configuration
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// needed for osmandroid
|
||||
Configuration.getInstance().userAgentValue = BuildConfig.APPLICATION_ID
|
||||
|
||||
setContent {
|
||||
MainLayout()
|
||||
}
|
||||
|
@ -95,11 +97,11 @@ fun MainLayout() {
|
|||
AnimatedNavHost(navController, startDestination = Screen.PersonList.title) {
|
||||
composable(
|
||||
route = Screen.PersonList.title,
|
||||
exitTransition = {
|
||||
exitTransition = { _, _ ->
|
||||
slideOutHorizontally() +
|
||||
fadeOut(animationSpec = tween(1000))
|
||||
},
|
||||
popEnterTransition = {
|
||||
popEnterTransition = { _, _ ->
|
||||
slideInHorizontally()
|
||||
}
|
||||
) {
|
||||
|
@ -112,11 +114,11 @@ fun MainLayout() {
|
|||
}
|
||||
composable(
|
||||
route = Screen.PersonDetails.title + "/{person}",
|
||||
enterTransition = {
|
||||
enterTransition = { _, _ ->
|
||||
slideInHorizontally() +
|
||||
fadeIn(animationSpec = tween(1000))
|
||||
},
|
||||
popExitTransition = {
|
||||
popExitTransition = { _, _ ->
|
||||
slideOutHorizontally()
|
||||
}
|
||||
) { backStackEntry ->
|
||||
|
|
|
@ -2,7 +2,9 @@ package com.surrus.peopleinspace.ui
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.surrus.common.remote.Assignment
|
||||
import com.surrus.common.repository.PeopleInSpaceRepository
|
||||
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
|
|
@ -1,263 +0,0 @@
|
|||
<vector android:height="50.6dp" android:viewportHeight="847"
|
||||
android:viewportWidth="385" android:width="23dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#BF9586" android:pathData="M88.188,10.5c0,9.583 0,19.167 0,28.75c29.532,-2.404 59.044,-4.063 88.626,-5.874c21.633,-1.324 67.814,-12.872 82.985,9.115c10.227,14.821 9.438,27.326 10.052,45.106c0.222,6.446 1.379,10.841 4.587,16.528c3.953,7.008 3.604,14.493 3.875,22.438c0.521,15.255 1.72,30.486 2.303,45.75c0.208,5.47 -1.598,16.568 0.822,21.563c2.621,5.41 12.739,8.2 17.65,11.33c6.127,3.905 9.348,10.715 13.413,16.671c3.825,5.605 9.62,10.142 13.437,15.999c7.843,12.035 11.888,26.538 18.242,39.403c13.548,27.425 22.34,52.58 16.52,83.551c-3.112,16.566 -10.924,25.477 -25.137,34.796c-5.239,3.435 -11.405,5.852 -17,8.707c-11.009,5.618 -10.466,6.807 -13.011,19.07c-3.189,15.369 -3.989,29.261 -3.989,44.959c0,12.234 8.642,27.777 12.46,39.571c9.394,29.011 16.033,59.139 22.958,88.842c3.271,14.033 3.362,29.792 4.534,44.159c1.112,13.652 7.868,26.241 9.832,39.771c1.001,6.899 3.091,14.62 3.091,21.608c0,8.577 -4.215,14.454 -3.375,22.436c1.776,16.891 9.226,27.403 19.287,40.269c5.157,6.595 11.396,30.684 7.088,38.608c-4.814,8.856 -30.952,14.176 -39.981,14.711c-8.112,0.48 -33.541,5.416 -33.901,-9.797c-0.282,-11.894 1.398,-25.152 -3.806,-36.204c-3.239,-6.878 -7.119,-13.728 -9.812,-20.837c-0.796,-2.102 0.088,-5.656 -1.984,-6.881c-3.258,-1.925 -5.235,-3.384 -7.789,-6.138c-9.567,-10.316 -14.569,-22.602 -19.337,-35.637c-4.508,-12.326 -13.262,-20.086 -13.885,-33.96c-0.559,-12.43 -10.433,-27.2 -15.835,-38.135c-5.704,-11.543 -9.188,-23.077 -11.713,-35.598c-1.412,-7.006 -3.737,-36.459 -11.332,-37.025c-1.829,15.894 -9.9,28.164 -12.611,43.704c-2.472,14.173 -3.737,27.892 -8.165,41.726c-2.421,7.563 0.887,11.288 0.273,18.595c-0.583,6.955 -0.649,14.153 -1.935,21.021c-2.729,14.577 -2.847,34.049 -9.001,47.491c-2.575,5.626 -4.624,14.581 -9.435,18.583c-9.454,7.864 -10.506,7.491 -9.125,19.567c0.862,7.539 1.563,15.033 2.141,22.601c0.697,9.13 -3.316,12.024 -9.445,18.65c-4.756,5.142 -7.461,12.823 -10.742,18.965c-3.082,5.769 -9.683,8.14 -15.578,10.221c-15.008,5.297 -28.782,5.051 -43.969,1.501c-4.272,-0.998 -5.2,-13.176 -5.532,-16.374c-0.886,-8.555 3.324,-13.692 7.392,-20.966c6.636,-11.864 10.248,-26.148 10.784,-39.561c0.146,-3.629 3.242,-15.388 1.947,-17.978c-3.64,-7.282 -5.106,-14.659 -5.747,-22.873c-1.13,-14.474 3,-28.828 3,-43.437c0,-14.442 2.25,-28.59 2.25,-42.97c0,-7.57 1.08,-15.837 -0.792,-23.217c-1.78,-7.019 -3.084,-14.493 -3.084,-21.755c0,-7.729 -0.71,-15.801 0,-23.495c0.62,-6.719 3.626,-13.327 3.626,-20.093c0,-15.694 0.143,-31.326 0.795,-47.008c0.614,-14.763 -6.546,-27.976 -5.171,-43.399c-3.574,4.958 -11.04,11.074 -8.795,17.115c2.02,5.438 -0.339,14.252 -1.64,19.95c-1.521,6.66 -4.761,14.174 -3.457,20.984c1.647,8.603 3.966,17.055 3.767,25.823c-0.166,7.296 -7.125,12.766 -7.125,19.241c0,8.876 -1.774,15.393 -4.259,23.982c-3.871,13.38 -19.129,21.46 -31.678,13.653c-13.762,-8.562 -14.932,-27.48 -18.42,-41.704c-1.693,-6.903 -2.893,-12.319 -2.893,-19.389c0,-6.342 5.089,-13.196 1.981,-19.604c-6.162,-12.703 -4.996,-31.832 -4.763,-45.896c0.1,-6.003 3.176,-11.979 3.97,-18.035c0.935,-7.132 0.442,-14.378 1.375,-21.479c1.939,-14.755 5.937,-29.42 6.063,-44.394c0.128,-15.263 -2.336,-28.45 1.25,-43.688c3.438,-14.61 4.556,-29.692 7.874,-44.312c1.698,-7.481 4.188,-14.859 6.269,-22.245c1.666,-5.911 7.521,-12.521 7.144,-18.688c-1.003,-16.431 -1.937,-32.863 -2.843,-49.299c-0.348,-6.318 2.657,-12.763 4.863,-18.512c2.016,-5.254 7.648,-10.292 7.314,-15.942c-0.982,-16.627 -1.959,-33.251 -3.083,-49.869c-0.932,-13.789 -5.834,-29.325 -3.97,-42.996c1.909,-13.999 10.471,-30.212 22.963,-37.397c3.423,-1.969 5.491,-2.645 9.403,-3.317c4.609,-0.792 2.816,-4.521 2.816,-8.901c0,-6.844 1.874,-16.853 -3.626,-22.209l1.126,-4.75h5L88.188,10.5z"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M177.313,498.377c3.891,-8.237 18.606,-4.169 26.527,-5.556c10.382,-1.819 19.325,-4.771 27.657,-11.667c7.175,-5.938 -16.429,-47.24 -19.811,-54.777c6.543,3.951 9.538,8.634 12.523,15.771c2.493,5.961 5.974,20.196 12.352,22.729c0,-10.295 -4.481,-17.948 -8.625,-27.377c3.917,-0.467 7.863,3.155 11.246,2.227c5.679,-1.559 7.76,-3.639 12.191,-7.663c7.106,-6.454 13.804,-13.243 20.563,-20.063c0.441,1.995 1.883,3.798 2.5,5.75c3.133,-1.061 6.125,-3.39 6.125,-6.873c0,4.334 -1.616,9.537 2.75,11.873c1.851,-3.701 3.917,-7.244 5,-11.25c1.517,5.222 -4.055,10.545 0,14.377c5.224,-4.229 5.066,-8.646 6.125,-15c-0.317,3.897 -3.38,15.785 0.125,17.687c5.361,2.907 0.382,15.139 -0.615,19.367c-1.517,6.43 -0.135,14.62 -0.135,21.219c0,6.054 -7.09,6.888 -12.125,8.478c1.385,-0.958 6.356,-6.029 2.5,-7.25c-1.548,-0.49 -7.915,5.095 -9.759,6.148c-3.383,1.933 -14.245,13.37 -4.429,11.163c7.908,-1.778 15.495,-5.342 23.075,-8.179c3.038,-1.137 7.907,13.503 9.362,16.617c-3.059,-1.284 -6.43,-1.805 -9.375,0c4.008,3.644 12.242,7.717 14,12.5c-5.283,-3.123 -14.578,-8.825 -20.75,-8.377c4.056,6.576 10.599,11.533 16.625,16.627c-8.388,-4.934 -23.594,-19.377 -33.188,-19.377c-3.955,0 -2.864,5.656 0.299,6.483c3.513,0.918 5.207,1.913 8.206,3.951c6.023,4.095 12.039,8.201 18.081,12.27c6.495,4.373 11.003,8.115 16.267,13.912c3.728,4.106 4.69,13.689 6.354,19.058c4.104,13.237 6.519,27.179 9.203,40.81c2.797,14.204 3.536,28.693 4.874,43.103c1.258,13.558 5.737,26.809 8.273,40.227c1.354,7.158 -2.989,6.807 -9.259,10.271c-7.018,3.879 -13.942,8.076 -21.111,11.667c-5.182,2.596 -11.945,6.9 -17.648,7.836c-4.267,0.699 -8.534,1.399 -12.802,2.099c-3.108,0.51 -2.841,-2.927 -6.05,-4.185c5.146,-7.459 1.767,-10.418 -6.125,-8c4.462,-1.58 13.732,-2.528 14.5,-7.5c-4.609,0 -9.266,-0.434 -13.765,0.616c-6.292,1.468 -6.918,-1.119 -10.505,-6.446c-6.276,-9.323 -2.648,-20.737 -7.933,-30.986c-9.508,-18.438 -19.429,-37.207 -24.644,-57.264c-2.403,-9.243 -3.474,-18.876 -5.092,-28.293c-0.976,-5.677 -0.207,-10.939 -5.304,-13.852c-2.14,-1.223 -11.521,-1.083 -11.875,-1.685c-5.846,-9.91 -11.865,-19.611 -18.103,-29.28C180.422,514.295 173.869,508.319 177.313,498.377z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M107.813,452.377c0.662,7.818 7.573,-0.051 8.726,-3.574c0.799,-2.442 1.288,-4.989 2.836,-7.053c2.458,-3.276 4.482,3.172 7.312,2.627c7.851,-1.511 -3.45,-7.495 -3.354,-9.566c0.284,-6.145 0.569,-12.289 0.854,-18.434c2.78,6.672 2.48,12.478 10.5,8.623c-3.242,4.682 -1.119,10.25 0.87,14.983c3.115,7.413 2.651,11.308 11.006,11.644c1.936,0.078 5.023,0.665 6.589,-0.56c1.792,-1.4 4.864,-5.16 7.196,-4.544c4.365,1.153 11.735,7.631 15.839,3.354c2,-2.084 3.791,-14.872 4.785,-17.856c0.68,-2.038 -8.49,-9.621 -10.066,-12.06c-1.281,-1.982 -5.233,-6.089 -5.469,-8.461c-0.143,-1.433 2.902,-5.493 3.313,-7.5c0.985,-4.813 0.791,-11.56 -2.813,-15.25c5.115,-0.759 7.511,5.146 11.219,8.021c1.634,1.267 4.62,0.256 6.583,0.077c2.212,-0.202 4.081,3.232 5.452,4.75c6.738,7.461 10.967,14.387 15.487,23.228c4.245,8.302 29.737,51.044 17.677,55.779c-10.206,4.008 -20.369,6.146 -31.418,6.895c-4.441,0.301 -11.047,-1.76 -13.882,2.82c-2.647,4.276 -4.859,8.164 -5.242,13.243c-0.809,10.721 8.768,21.343 13.965,30.005c1.933,3.221 2.885,8.118 5.118,10.839c2.93,3.568 11.071,9.678 8.541,14.593c-4.434,-5.911 -8.677,-11.821 -12.783,-17.962c-2.179,-3.259 -14.341,-20.88 -10.114,-7.29c2.198,7.066 9.06,14.296 13.09,20.451c3.501,5.346 11.229,10.92 9.308,16.928c-6.502,-9.036 -11.682,-18.474 -21,-24.627c3.358,12.636 14.217,20.889 17.375,33.5c-3.501,-4.587 -6.176,-8.608 -10.499,-12.5c-1.248,6.69 3.76,14.123 7.499,19.377c-1.96,-3.109 -4.616,-5.467 -8.125,-3.25c5.959,13.073 4.768,27.96 0.872,42.041c-1.857,6.712 -5.293,13.104 -6.872,19.832c-1.673,7.131 1.418,13.344 1.155,20.528c-0.247,6.77 -0.717,12.667 -2.028,19.276c-1.235,6.218 -1.24,21.544 -5.251,26.445c-3.897,4.762 -14.587,3.338 -20.116,4.233c-7.723,1.251 -14.852,-0.356 -21.822,-0.356c-7.143,0 -14.781,-1.926 -21.355,-4.609c-8.343,-3.406 -10.384,-2.273 -8.957,-12.268c0.641,-4.491 1.877,-4.549 0.583,-10.379c-0.837,-3.766 -0.972,-5.803 -0.521,-9.621c0.898,-7.59 0.721,-15.146 1.673,-22.76c3.626,-28.988 -3.875,-56.686 -2.924,-85.688c0.253,-7.728 3.439,-15.249 3.439,-22.974c0,-12.982 0,-25.965 0,-38.947c0,-6.902 1.742,-16.386 -1.205,-22.505c-1.918,-3.984 -1.741,-9.742 -2.387,-14.091c-0.631,-4.241 3.594,-7.54 4.092,-11.908C93.697,461.048 103.875,453.231 107.813,452.377z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M132.813,333.126c-1.377,1.042 -3.415,1.537 -5,2.25c-0.477,-4.906 2.776,-7.961 3.874,-12.5c1.239,-5.12 0.422,-11.619 0.613,-16.866c0.174,-4.789 2.954,-26.351 -4.487,-26.26c0,6.742 -0.165,13.177 -0.876,19.876c-3.87,-14.32 -6.795,-35.584 -18,-46.25c-3.251,6.761 3.301,13.887 5.626,20c2.451,6.443 3.513,13.119 4.624,19.874c-1.381,-2.466 -3.023,-7.722 -5.25,-9.374c-3.273,-2.428 -5.79,-2.383 -3.766,2.44c3.456,8.235 7.302,16.302 9.642,24.934c-2.102,-2.208 -3.236,-1.818 -5.876,-2.25c-0.565,5.697 2.9,8.813 6.137,12.991c3.923,5.065 2.678,6.908 0.183,12.579c-4.49,10.2 -10.363,16.436 -12.793,27.579c-2.231,10.23 -7.96,19.427 -9.838,29.664c-2.069,11.28 -3.754,22.756 -6.446,33.963c-2.229,9.274 -6.165,18.1 -9.796,26.93c-2.084,5.068 -2.956,13.282 -8.696,13.544c-0.276,0.013 -12.788,-5.338 -13.25,-5.873c-5.348,-6.194 4.288,-25.591 -13.435,-18.857c-3.059,1.163 0.01,11.859 -2.505,12.31c-6.145,1.099 -12.29,2.198 -18.435,3.298c0,-5.784 0.638,-57.877 6.587,-57.877c9.175,0 18.76,0.362 27.495,3.414c4.817,1.684 9.912,4.216 14.918,5.213c3.249,0.647 8.238,-0.475 10.874,1.874c-3.423,-9.255 -21.051,-11.329 -29.124,-13.75c4.075,0 8.588,1.183 11.374,-2.5c-6.398,-3.075 -11.749,-2.5 -18.819,-2.5c-4.27,0 -14.445,2.682 -15.394,-0.554c-3.928,-13.395 -4.401,-27.219 -1.752,-40.956c5.139,-26.644 9.172,-52.179 17.139,-78.176c3.878,-12.655 14.55,-21.844 23.331,-31.039c4.184,-4.382 8.964,-7.804 13.773,-11.508c8.58,-6.609 9.221,-2.749 17.926,3.738c11.201,8.347 22.465,16.016 34.449,23.163c12.914,7.701 20.818,10.249 36.224,11.083c-12.943,2.275 -24.94,0.499 -33.912,11.121c-3.4,4.025 -1.588,11.855 -1.588,16.885c0,7.691 -0.261,15.433 0,23.12c0.208,6.112 0.973,12.208 1.254,18.319c0.435,9.464 3.937,12.673 9.786,20.248c1.064,1.378 3.858,0.235 4.433,1.983c1.005,3.058 1.729,6.416 3.207,9.282c3.239,6.282 4.753,12.585 7.571,19.041c-4.104,-2.364 -10.85,-11.873 -14.676,-11.873c-7.175,0 -12.04,4.398 -14.418,11.101c-1.937,5.458 -4.214,13.646 -2.613,19.308c1.266,4.478 10.759,14.278 5.457,16.092c-1.305,-4.268 -4.018,-7.016 -6.88,-10.279c-2.933,-3.344 0.063,-8.275 1.13,-12.348c-2.425,4.903 -4.489,10.206 -7.75,14.627c2.404,-14.487 4.463,-28.65 8.874,-42.627C135.533,365.693 148.464,330.283 132.813,333.126z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M353.313,317.876c1.832,14.859 3.971,28.936 -0.25,43.5c-0.702,-2.944 -4.053,-7.742 -6.875,-4.5c-1.129,1.297 1.151,10.045 1.375,12c-2.174,-0.616 -4.152,0.03 -6.375,-1.126c-2.104,4.608 2.525,7.184 0,12.126c-0.68,-2.45 -2.335,-3.571 -3.625,-5.5c-4.204,5.386 -11.125,11.691 -17.5,14.373c-2.054,0.864 -4.669,-0.852 -6.398,1.311c-2.552,3.192 -1.175,6.221 -2.727,9.816c-6.025,-17.198 -6.989,4.204 -10.5,2.123c-3.62,-2.146 -4.851,-22.551 -5.579,-27.03c-1.564,-9.627 -7.46,-17.153 -12.957,-24.864c-2.509,-3.52 -5.083,-4.657 -2.065,-8.729c4.117,-5.557 -0.709,-8.31 -4.773,-12.375c11.021,-8.733 21.879,-16.624 36.75,-16.624C324.855,312.376 340.307,315.567 353.313,317.876z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M191.688,268.626c0.824,-4.412 6.249,-3.299 9.875,-3.688c6.561,-0.703 8.555,-0.776 13.602,-4.946c6.745,-5.572 11.088,-13.126 15.472,-20.508c7.49,-12.61 23.45,-32.097 23.677,-46.984c11.12,4.083 21.685,7.713 32.022,13.537c9.611,5.414 13.216,11.935 20.7,19.775c7.4,7.753 13.906,16.94 18.408,26.654c2.926,6.315 -2.733,7.168 -8.166,10.033c-3.432,1.81 1.683,3.314 3.16,3.377c2.066,0.088 6.101,-3.146 8,-4.126c8.592,16.958 18.058,33.171 24.375,51.126c-9.041,-1.292 -18.043,-2.087 -27.125,-3c2.667,-0.542 5.333,-1.084 8,-1.626c-0.65,-5.397 -4.565,-3.559 -8.625,-3.124c3.603,-3.119 11.877,-5.827 8.125,-11.25c-11.156,5.758 -19.748,11.736 -32.002,13.98c-11.9,2.179 -20.854,9.082 -30.623,15.894c-1.335,-5.062 -4.372,-11.1 -1.899,-16.228c2.215,-4.592 9.656,-11.707 7.399,-16.146c-2.416,1.037 -4.528,2.822 -6,5c1.511,-6.764 11.443,-16.543 9.625,-22.5c-3.486,2.988 -6.497,5.697 -9.125,9.5c1.062,-6.274 5.02,-14.374 4.5,-20.5c-3.922,5.566 -10.846,13.542 -10.875,20.5c0,-5.667 0,-11.333 0,-17c-5.916,5.442 -4.375,10.362 -4.375,18.127c0,4.881 -5.006,9.649 -2.269,14.014c4.45,7.097 3.448,11.124 5.269,19.109c2.285,10.025 2.921,8.471 -4.625,16.624c0.096,-4.889 -0.421,-24.648 -3.931,-26.403c-6.937,-3.467 -6.933,-3.38 -7.93,-11.051c-0.732,-5.631 0.687,-9.145 -5.45,-9.651c-7.456,-0.615 -8.826,0.764 -11.097,-6.358c-1.392,-4.367 -2.84,-8.533 -6.843,-10.536C215.637,266.597 199.988,269.058 191.688,268.626z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M183.438,49.5c28.987,10.7 50.989,28.876 56.738,60.335c3.149,17.238 6.86,34.485 7.762,52.041c0.603,11.734 4.246,34.477 -4.75,44c-0.281,-1.839 -1.717,-2.813 -2.25,-4.25c-7.757,8.229 -40.307,33.065 -24.875,44.624c1.245,-10.528 2.029,-12.242 9.759,-19.536c5.454,-5.147 10.141,-11.35 15.116,-16.964c-6.548,11.136 -13.096,22.271 -19.645,33.407c-2.135,3.631 -4.359,6.318 -7.203,9.496c-4.473,4.999 -5.262,3.827 -11.902,3.847c19.361,-22.924 -16.904,-7.617 -23.75,-14.124c23.412,-7.863 39.81,-17.38 54.407,-37.95c1.781,-2.51 9.767,-9.809 10.231,-12.291c1.107,-5.904 0.747,-9.426 0.603,-15.089c-0.319,-12.538 0.029,-24.979 -2.308,-37.308c-2.008,-10.596 -3.277,-23.825 -11.621,-31.613c-7.361,-6.871 -19.631,-11.975 -29.118,-16.362c-2.632,-1.217 -9.318,-2.141 -12.405,-2.576c-7.768,-1.095 -15.536,-2.189 -23.303,-3.285c-12.811,-1.806 -20.455,0.426 -31.61,6.223C145.881,71.442 160.3,57.479 183.438,49.5z"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M263.688,100.626c10.691,9.33 8.421,31.461 9.375,44.374c1.189,16.093 2.32,32.37 1.375,48.626c-6.082,-2.493 -19.98,-4.598 -20.262,-11.707c-0.351,-8.869 -0.683,-17.675 -1.499,-26.509c-1.669,-18.071 -3.048,-38.176 -8.989,-55.284C247.552,99.189 262.908,95.396 263.688,100.626z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M258.188,57.5c6.447,5.976 4.33,20.602 4.649,28.304c0.484,11.693 -12.542,2.919 -19.649,8.822c-6.668,-16.568 -17.521,-28.595 -31.5,-39.876C227.354,53.681 243.08,52.465 258.188,57.5z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="2"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M257.563,52c-7.697,-4.972 -21.434,-5.167 -30.287,-4.662c-7.394,0.421 -14.788,0.843 -22.182,1.265c-1.857,0.105 -1.174,1.945 -3.306,1.204c-3.408,-1.186 -6.816,-2.371 -10.226,-3.557c-5.022,-1.747 -9.637,-4 -14.956,-4c-3.829,0 -16.521,-2.151 -19.231,0.563c-7.443,7.456 -24.992,2.195 -31.812,11.937c-3.743,-6.065 -34.692,1.03 -42.014,1.695c-5.863,0.533 -9.539,2.182 -14.353,5.444c-2.847,1.928 -8.524,8.728 -9.759,11.987c2.96,-2.054 4.959,-5.319 7.5,-2.5c13.106,-12.117 32.913,-9.998 50,-9.126C106.819,74.13 97.212,84.725 92.369,99.497c-4.368,13.321 -8.306,26.859 -8.306,40.865c0,9.299 0,18.597 0,27.896c0,2.938 -15.077,2.649 -18.346,2.501c-7.472,-0.339 -6.03,-33.322 -6.03,-40.07c0,-29.073 -13.36,-56.9 13.241,-78.785c7.922,-6.518 29.192,-7.702 39.31,-8C127.46,43.453 142.655,42 157.871,42c17.499,0 35.412,-1.459 52.82,-3.215c10.194,-1.028 19.386,-0.614 29.639,0.095C250.125,39.558 252.475,44.219 257.563,52z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#13132D" android:pathData="M181.469,199.969c-3,-14 -19,-21 -24,-33c-8,-20 8,-40 14,-59c1.493,-5.226 -0.358,-10.452 -2.641,-15.678c-27.291,-1.158 -54.426,11.526 -67.488,37.658c-5.551,11.104 -0.515,34.702 0.201,46.712c0.804,13.501 9.675,23.861 19.556,32.79c11.491,10.383 20.909,20.316 35.873,24.058c7.745,1.936 14.34,2.294 20.688,1.397C179.28,223.531 184.563,212.349 181.469,199.969z"/>
|
||||
<path android:fillColor="#363844" android:pathData="M236.417,154.423c-0.198,-12.696 0.148,-30.628 -10.512,-39.381c-12.791,-10.502 -24.972,-15.782 -40.842,-20.416c-5.345,-1.336 -10.793,-2.105 -16.236,-2.336c2.283,5.226 4.134,10.452 2.641,15.678c-6,19 -22,39 -14,59c5,12 21,19 24,33c3.095,12.38 -2.188,23.563 -3.813,34.937c7.68,-1.084 14.995,-4.011 23.531,-8.279c11.265,-5.632 23.966,-17.232 29.502,-28.776C237.611,183.419 236.664,170.254 236.417,154.423z"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M85.688,183.376c0.359,6.385 0.815,12.622 1.616,18.961c1.215,9.622 4.201,14.072 -3.203,20.291c-10.597,8.901 -19.846,19.709 -30.538,28.248c-1.719,-15.686 -5.664,-36.644 -0.699,-51.879c2.588,-7.943 5.519,-17.118 13.823,-20.871c2.048,-0.926 7.945,-1.376 10.375,-1.376C84.563,176.75 84.43,175.962 85.688,183.376z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M255.063,336.5c-12.07,7.293 -20.179,20.825 -33.509,26.438c-9.586,4.036 -17.755,24.561 -17.616,33.811c-7.695,-11.629 -3.978,-14.751 -0.375,-27.373c2.067,-7.245 0.225,-7.114 7.995,-8.032c7.362,-0.87 7.504,-1.348 12.692,-5.156c6.993,-5.132 7.232,-16.739 6.968,-24.278c-0.153,-4.39 -1.995,-12.023 -0.093,-16.097c1.298,-2.78 9.348,-4.055 12.063,-6.813C244.295,323.301 243.141,328.274 255.063,336.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M132.438,338.376c8.165,11.792 -3.332,31.704 -6.647,43.722c-4.535,16.438 -6.791,31.377 -8.853,48.401c0,-0.917 0,-1.833 0,-2.75c-5.878,2.924 -1.167,12.209 -5.435,15.963c-4.354,3.83 -10.554,3.804 -14.439,8.664c3.046,-3.877 12.355,-10.994 6.624,-15.5c-3.233,2.685 -6.736,5.103 -9.124,8.623c-0.542,-5.344 7.121,-12.93 10.048,-17.321c1.284,-1.926 4.568,-5.782 1.077,-7.052c-2.894,-1.052 -6.337,5.749 -8.125,8c-0.058,-4.182 12.552,-20.025 3.374,-17.127c2.589,-3.02 5.354,-5.36 8.063,-8.062c3.381,-3.373 -4.685,-3.078 -6.937,-0.562c1.932,-5.644 9.653,-8.011 11.624,-13.438c2.667,-7.343 -8.53,0.596 -10.5,2.438c2.005,-1.845 9.974,-6.61 10.75,-8.877c1.919,-5.603 -4.753,-4.012 -7.5,-1.123c-0.665,-6.499 8.108,-4.54 11.126,-9.127c5.165,-7.85 -3.121,-3.91 -6.876,-2.5c0.385,-3.74 19.824,-21.497 10.126,-20.499C123.098,347.333 130.067,336.511 132.438,338.376z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M101.813,196.75c10.273,12.271 19.525,22.736 31.922,32.786c9.674,7.842 30.274,15.593 42.702,14.214c-7.783,0.819 -15.567,1.639 -23.35,2.458c-1.069,0.112 -1.095,7.995 -1.209,9.301c-0.166,1.898 4.905,2.894 6.309,3.117c12.667,2.01 29.866,-1.376 42.875,-1.376c-11.194,1.023 -22.192,2.31 -33.427,2.034c-5.077,-0.125 -9.909,0.582 -14.424,-1.769c-2.445,-1.273 -15.292,-6.039 -15.433,-8.66c-0.318,-5.904 1.103,-9.957 -4.026,-13.054c-5.556,-3.354 -10.113,-6.211 -14.767,-10.669C109.949,216.475 101.813,209.615 101.813,196.75z"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M97.563,190.876c0,6.226 0,12.453 0,18.679c0,3.119 9.819,11.409 11.679,13.47c3.544,3.928 8.143,7.421 12.445,10.476c7.465,5.299 10.569,4.526 11.376,13.876c-10.746,-6.686 -19.382,-12.845 -28.601,-21.472c-3.767,-3.524 -6.993,-6.896 -10.255,-10.891c-2.863,-3.506 -2.014,-13.691 -2.523,-18.145c-2.441,-21.336 -0.747,-42.723 -0.747,-63.674c0,-23.747 10.692,-45.102 25.614,-62.82c15.217,-18.069 38.202,-21.749 60.512,-21.749c-14.345,4.632 -25.385,13.759 -35.847,24.221c-5.155,5.155 -8.588,14.156 -11.946,20.523c-2.347,4.448 -12.167,9.425 -16.225,13.381c-6.28,6.124 -10.519,11.005 -15.108,18.439c-4.029,6.525 -3.124,11.899 -3.124,19.621C94.813,160.476 95.864,175.327 97.563,190.876z"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M85.688,247.376c-2.899,3.708 -5.82,-0.186 -9.124,-2.25C79.075,240.626 83.718,244.005 85.688,247.376z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M302.938,246.5c2.284,4.423 4.454,8.922 6.949,13.231c0.517,0.893 9.24,14.781 4.176,13.645c-2.995,-0.672 -15.817,-25.346 -15.25,-29.876C300.852,243.487 301.84,244.675 302.938,246.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M78.813,252.376c1.833,2.292 3.667,4.583 5.5,6.874c-0.708,-0.667 -1.417,-1.333 -2.126,-2c-4.151,3.751 -9.45,-4.85 -13.874,-6.874C70.348,245.576 76.249,249.985 78.813,252.376z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M201.938,249.5c-2.667,0.292 -5.333,0.584 -8,0.876C196.566,249.066 199.438,248.129 201.938,249.5z"
|
||||
android:strokeColor="#FF9100" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M185.313,251.25c-2.837,-0.36 -5.645,-0.811 -8.5,-0.874c4.716,-0.166 9.405,-0.25 14.124,-0.25C189.066,250.5 187.196,250.945 185.313,251.25z"
|
||||
android:strokeColor="#FF0000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M74.938,258.626c4.221,3.685 9.273,6.971 11.626,12.25c-9.548,-1.025 -15.959,-13.899 -26.626,-15C62.749,249.834 71.068,256.304 74.938,258.626z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M294.063,283.626c-1.86,4.494 -5.976,3.954 -3.433,-0.832c3.575,-6.73 5.245,-7.908 12.058,-11.418C301.957,276.394 297.258,279.967 294.063,283.626z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M206.438,277.25c-6.504,5.153 1.116,12.832 6.875,13c11.788,0.344 5.397,-5.984 7.125,-13.874c7.675,9.903 3.375,36.119 3.375,48.148c0,11.538 -0.197,16.738 -5.379,26.899c-1.858,3.645 -7.438,1.953 -11.219,1.953c-3.793,0 -4.625,-3.119 -6.152,-6.376c3.936,-0.354 7.872,-0.708 11.808,-1.062c2.611,-0.234 3.362,-6.438 0.237,-6.438c-8.761,0 -17.635,-0.435 -26.383,0.018c-8.762,0.453 -17.528,0.85 -26.287,1.358c-2.444,0.142 -8.262,-0.793 -9.374,2.25c-1.53,4.187 3.642,5.674 6.374,4.374c-0.941,7.436 -6.627,2.822 -9.124,-1.624c-3.179,-5.662 -1.998,-14.208 -2.348,-20.46c-0.718,-12.808 -1.881,-25.226 -1.044,-38.017c0.295,-4.513 -0.415,-10.647 6.014,-11.055c5.992,-0.38 12.11,-1.197 18.114,-1.077c14.296,0.287 28.592,0.573 42.889,0.859C210.111,276.5 208.28,276.973 206.438,277.25z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M330.063,281.626c-7.801,2.065 -14.775,6.5 -22.125,8.624C306.098,282.555 328.329,270.151 330.063,281.626z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M187.063,287.75c0.456,9.423 1.028,18.781 1.826,28.181c0.325,3.843 -18.59,5.104 -20.452,3.319c-3.411,-3.269 -1.124,-23.851 -1.124,-29.52C167.313,283.207 182.992,285.344 187.063,287.75z"
|
||||
android:strokeColor="#A89187" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#00FFFF" android:pathData="M183.938,313.25c-3.667,0.25 -7.333,0.5 -11,0.75c-0.324,-7.449 -0.861,-14.917 -0.874,-22.374c5.086,0.236 10.624,-1.876 10.926,3.801C183.306,301.368 183.621,307.31 183.938,313.25z"/>
|
||||
<path android:fillColor="#13007C"
|
||||
android:pathData="M240.438,295.75c-4.033,1.78 -9.745,4.139 -8.875,9.376c-0.438,-1.813 -3.396,-8.88 -0.977,-9.669C233.794,294.412 239.348,291.82 240.438,295.75z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M160.438,305.126c2.341,5.195 1.194,12.482 1.362,18.151c0.188,6.352 4.15,14.599 -5.195,14.599c-6.529,0 -6.112,-30.577 -1.167,-35.25C156.868,304.685 158.271,304.126 160.438,305.126z"
|
||||
android:strokeColor="#A89187" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M100.938,314.626c4.542,4.25 9.084,8.5 13.626,12.75c-2.396,-2.428 -4.744,-0.617 -7.5,-2c-2.879,-1.445 -5.72,-5.646 -8.016,-7.911c-3.885,-3.834 -16.117,-7.539 -15.234,-13.339C90.469,304.522 95.922,310.814 100.938,314.626z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M240.438,308.75c-0.458,0.375 -0.917,0.75 -1.375,1.126c-0.842,-1.17 -1.16,-1.971 -1.125,-3.376c1.125,0 2.25,0 3.375,0C241.021,307.25 240.729,308 240.438,308.75z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#6CFF00" android:pathData="M157.438,334.25c-5.526,-4.073 -2.617,-19.78 -2.75,-26.25C159.959,307.818 161.703,330.906 157.438,334.25z"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M250.063,322.376c-1.535,-2.114 -1.201,-4.872 -0.5,-7.5C249.899,317.364 250.067,319.87 250.063,322.376z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M313.188,333.126c0.198,1.239 0,2.615 0,3.874c-2.983,-2.688 -17.137,-11.68 -18,-15.25C293.536,314.919 312.031,331.788 313.188,333.126z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M64.438,330.126c-2.18,9.125 -2.329,27.813 -7.75,34.874c-3.957,-3.67 1.789,-21.867 2.607,-27.301c0.565,-3.756 0.688,-11.658 2.893,-14.573C67.064,316.678 64.911,327.942 64.438,330.126z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M189.813,323.75c0.578,5.492 2.899,8.989 -3.057,9.369c-1.688,0.108 -16.451,1.859 -16.735,0.706c-0.962,-3.899 -4.211,-12.347 2.96,-11.983C178.031,322.098 185.606,320.915 189.813,323.75z"
|
||||
android:strokeColor="#A89187" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M173.438,328.75c3.06,-2.572 8.379,-1.214 12.5,-1.124C181.771,328.001 177.604,328.376 173.438,328.75z"
|
||||
android:strokeColor="#FF0000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M103.688,337.25c3.458,5.849 -5.57,2.727 -5.25,-1.624C100.19,335.818 102.189,336.276 103.688,337.25z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFF00" android:pathData="M259.563,348.376c7.59,5.087 16.046,14.193 19.531,22.644c1.269,3.077 9.177,34.404 0.094,29.356c-5.037,-2.8 0.027,-21.725 -4.438,-27.534c-7.332,-9.54 -14.344,-15.845 -23.813,-23.342C253.541,344.552 255.862,344.098 259.563,348.376z"/>
|
||||
<path android:fillColor="#13007C"
|
||||
android:pathData="M200.563,358.876c-2.454,8.548 -5.058,15.983 -8.875,24c-2.937,-9.811 -17.504,-10.767 -19.624,0c-4.822,-5.033 -5.566,-10.553 -8.565,-16.639c-5.107,-10.366 -2.742,-7.805 1.627,-17.424c1.242,-2.734 16.699,-1.563 20.495,-1.563C196.289,347.25 195.563,349.386 200.563,358.876z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M277.563,336.5c-5.898,6.193 -4.518,6.5 0,13.75c3.227,5.179 7.723,10.085 9.848,15.829c4.428,11.968 8.423,23.756 6.652,36.547c-7.801,1.036 -5.098,-9.271 -5.234,-14.393c-0.167,-6.297 -4.068,-13.798 -5.918,-19.82c-3.323,-10.815 -13.645,-20.853 -22.348,-27.537c3.386,-2.913 6.771,-5.825 10.158,-8.738C273.422,329.814 275.447,334.287 277.563,336.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M260.313,367.75c4.569,7.092 8.239,12.441 10.227,20.592c0.796,3.262 3.246,13.1 0.854,15.621c-10.066,10.612 -20.093,26.737 -35.456,26.787c-9.021,0.029 -11.832,-7.586 -17.508,-12.816c-2.075,-1.912 -5.421,0.273 -8.18,-1.934c-3.102,-2.48 -4.067,-4.908 -1.604,-8.466c3.421,-4.941 -2.849,-12.047 2.167,-18.784c1.154,-1.551 16.55,-11.327 5.75,-10.75c2.772,-7.785 13.679,-12.316 20,-16.873C247.326,353.368 250.879,359.859 260.313,367.75z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M187.813,386c-1.686,2.064 -13.877,7.631 -11.274,0.979C178.616,381.671 185.527,377.577 187.813,386z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M335.563,384.376c-0.708,0.333 -1.417,0.667 -2.125,1c0.04,-2.644 -0.199,-2.056 1.625,-3.877C335.396,382.434 335.3,383.484 335.563,384.376z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M153.563,386.75c7.177,4.215 10.385,10.581 8.25,18.627c-0.931,3.508 -4.848,4.997 -2.021,8.81c2.966,4 5.933,8 8.898,12c3.103,4.185 8.78,15.929 -0.504,16.813c-3.04,0.29 -6.964,-3.873 -10.74,-3.873c-3.669,0 -6.358,5.126 -9.76,5.25c-4.806,0.175 -11.27,-9.867 -7.175,-14.499c3.208,-3.629 14.637,-12.92 9.113,-18.439c-3.267,-3.264 -7.083,1.137 -9.688,-4.189c-1.47,-3.005 0.455,-10.817 0.599,-14.234C140.797,386.836 150.813,376.118 153.563,386.75z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M327.063,389.876c-1.093,-0.67 -3.414,0.649 -0.625,-2.25C326.646,388.376 326.854,389.126 327.063,389.876z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M198.938,398.75c1.292,2.375 2.583,4.751 3.875,7.127c-4.046,-4.716 -6.986,-9.549 -10,-14.877C194.854,393.583 196.896,396.166 198.938,398.75z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M235.938,410.877c0.77,1.706 4.544,7.634 3.375,9.75c-2.836,5.135 -6.79,-7.477 -7.135,-8.308c-1.887,-4.545 -8.157,-17.23 -2.49,-19.943C232.261,398.487 234.114,404.493 235.938,410.877z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M208.063,444.877c6.279,10.938 2.696,27.903 -12.523,20.301C186.373,460.599 196.059,434.559 208.063,444.877z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M205.813,452.127c-0.415,4.371 -5.919,13.81 -9.125,5.5C193.979,450.608 201.321,448.549 205.813,452.127z"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M145.438,466.5c5.49,1.89 2.685,-2.333 5.626,-4.123c3.564,-2.169 5.859,-2.323 10.859,-2.684c8.859,-0.639 7.837,3.99 6.265,12.307c2.626,-4.195 12.711,-11 16.876,-7.75c4.48,3.495 -8.841,6.465 -10.476,10.39c-3.299,7.92 2.161,15.263 -10.365,14.085c-7.714,-0.726 -8.892,-9.377 -2.409,-12.848c-1.833,-1.626 -3.667,-3.251 -5.5,-4.877c-1.651,5.008 -2.901,15.814 -10.713,11.757c-7.489,-3.891 -15.028,-7.236 -22.794,-10.547c-4.938,-2.104 0.573,-11.096 3.194,-13.021C129.924,456.306 141.905,464.779 145.438,466.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M66.688,479.5c5.025,3.038 7.616,4.401 5.464,10.858c-1.75,5.251 -2.041,10.639 -3.838,15.769c-7.489,-3.137 -13.58,-6.435 -21.598,-2.684c-7.499,3.507 -15.022,6.804 -22.652,9.307c-0.806,-5.367 -7.504,-38.609 -2.341,-39.877C37.626,468.968 51.769,474.47 66.688,479.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M225.813,493.127c7.112,5.575 -17.545,7.534 -19.528,7.798c-5.713,0.76 -22.345,4.553 -26.471,-1.111c-3.587,-4.925 17.508,-2.536 19.956,-2.817C206.316,496.244 220.97,491.704 225.813,493.127z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M196.438,506.377c-3.442,4.375 -12.568,5.338 -16.124,0.873C183.83,502.563 192.15,503.856 196.438,506.377z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M65.188,512.5c5.698,5.89 10.519,29.75 3.876,35.627c-0.344,-3.045 0.308,-12.207 -1.626,-14.377c-2.802,-3.143 -3.66,0.448 -3.451,2.975c0.83,10.039 0.852,18.942 0.201,28.902c-0.611,9.343 -1.701,19.31 -8.895,26.025c-8.796,8.211 -16.757,-1.5 -21.457,-8.601c-10.636,-16.074 -11.455,-37.119 -8.901,-55.342C26.875,513.867 53.676,504.028 65.188,512.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M190.563,516.627c-1.925,0.501 -11.222,-1.199 -6.874,-3.612C187.177,511.078 192.1,511.25 190.563,516.627z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M347.563,706.127c2.827,10.743 -24.355,21.701 -31.46,25.866c-5.481,3.215 -15.369,2.255 -21.613,2.919c-8.831,0.939 -10.492,-2.626 -16.427,-9.162c10.097,-7.609 24.412,-6.044 35.649,-11.23c7.77,-3.586 15.539,-7.172 23.309,-10.758C344.429,700.343 345.8,695.744 347.563,706.127z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M107.063,728c2.812,0.779 5.708,1.041 8.5,0.25c0.254,3.753 23.388,8.174 25.75,5c4.42,5.243 8.703,0.566 13.624,0c1.673,-0.192 2.848,1.589 4.7,1.084c2.86,-0.781 5.351,-1.591 8.3,-1.584c-2.406,1.721 -2.468,2.997 -2.13,5.622c0.274,2.121 -3.735,3.727 -5.37,4.878c1.19,0.577 0.997,0.959 2.5,1.377c-5.047,5.384 -8.459,10.25 -15.844,10.25c-7.569,0 -15.8,-0.318 -23.156,-2.25c-6.461,-1.696 -14.617,-6.329 -19.652,-10.525c-3.844,-3.203 -10.049,-14.414 -7.722,-19.102C100.25,724.44 103.964,725.494 107.063,728z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M344.813,729.127c1.183,14.487 6.975,23.606 16.148,34.457c7.267,8.595 9.12,21.742 10.137,31.998c1.016,10.245 -27.745,13.067 -34.418,14.313c-12.13,2.265 -26.692,8.847 -25.367,-10.768c2.989,5.224 6.408,-1.427 9.41,-4.219c3.716,-3.457 12.509,-4.174 17.283,-5.562c4.608,-1.34 11.135,-4.383 15.932,-4.597c3.506,-0.156 9.207,4.035 11.25,1.127c7.558,-10.758 -27.36,-3.568 -30.514,-2.739c-6.93,1.821 -20.75,6.063 -22.861,14.112c-3.152,-4.81 -1.522,-12.932 -1.599,-18.576c-0.074,-5.439 -4.552,-8.488 -0.026,-13.297c-2.284,-0.462 -3.976,-1.593 -6.375,-1.377c2.567,-1.798 5.897,-1.922 5.5,-6.123c-3.174,-0.105 -6.572,0.672 -8.875,-1.627c2.47,-2.102 8,-0.46 8,-4.562c0,-1.918 -8.538,-1.552 -10.5,-3.438c2.265,-0.387 18.64,-0.854 16.125,-6.123c-3.014,-6.312 -15.192,4.396 -18.875,-1.877c7.186,-0.666 18.631,0.42 25.187,-3.3C325.761,733.895 341.331,720.169 344.813,729.127z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M160.438,737.127c0.726,-0.258 1.461,-1.174 3,-0.75C162.438,736.627 161.438,736.877 160.438,737.127z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M123.313,761.25c7.675,0.866 15.349,1.731 23.024,2.598c3.334,0.376 4.321,14.507 5.1,18.402c3.142,15.709 -9.428,26.272 -16.621,39.235c-6.638,11.964 -21.567,15.892 -34.894,15.892c-7.624,0 -15.038,0.83 -18.359,-7.5c-2.55,-6.397 -0.152,-16.521 5,-21c-0.157,1.568 0.511,2.913 0.5,4.373c3.155,-5.297 7.952,-2.895 12.905,-3.705c7.833,-1.282 13.483,-3.006 20.438,1.344c2.335,1.46 2.588,-3.633 1.087,-4.952c-2.813,-2.473 -4.873,-3.85 -8.676,-3.751c-7.495,0.194 -14.904,0.518 -22.38,1.064c-0.392,-8.586 9.178,-18.335 9.626,-27.873c1.909,1.315 6.971,1.857 6.75,-2c-0.057,-0.988 -5.069,-4.182 -6.126,-5.25c0.747,-0.223 14.673,2.827 7.75,-3.25c4.56,1.721 10.91,4.372 14.126,-0.877c-5.984,-2.372 -21.022,-4.47 -19.626,-13.873C109.831,754.79 115.474,758.555 123.313,761.25z"
|
||||
android:strokeColor="#000000" android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M207.813,145.5c13.659,-2.167 10.148,-31.902 -0.939,-28.458C195.919,120.445 195.34,143.716 207.813,145.5z"/>
|
||||
<path android:fillColor="#FA002A"
|
||||
android:pathData="M153.813,404c4.07,-9.431 -1.446,-6.081 -4.75,-14.123c-3.146,3.039 -3.12,6.077 -3.406,10.125C145.302,405.048 149.279,403.527 153.813,404z"
|
||||
android:strokeColor="#FA002A" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FA7800" android:pathData="M153.563,423.877c6.251,0 18.406,4.306 15.124,13c-10.103,-4.718 -12.256,-5.389 -21.5,0.873C142.788,433.468 150.714,427.534 153.563,423.877z"/>
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M63.04,523.027c0,0 -0.218,-0.135 -0.626,-0.388c-0.396,-0.275 -1.006,-0.624 -1.75,-1.019c-0.752,-0.344 -1.784,-0.754 -2.639,-0.543c-0.399,0.146 -0.739,0.391 -1.023,0.866c-0.273,0.477 -0.493,1.068 -0.642,1.752c-0.314,1.352 -0.434,2.944 -0.481,4.633c-0.041,1.693 -0.014,3.501 0.044,5.378c0.08,1.876 0.176,3.823 0.321,5.804c0.172,1.987 0.347,4.007 0.522,6.025c0.087,1.074 0.061,2.041 0.097,3.063l0.032,1.509c-0.014,0.506 -0.028,1.01 -0.042,1.513c-0.039,1.004 -0.042,2.003 -0.11,2.982c-0.074,0.978 -0.146,1.943 -0.218,2.893c-0.176,1.896 -0.382,3.725 -0.617,5.45c-0.247,1.726 -0.473,3.356 -0.738,4.854c-0.484,3 -0.973,5.482 -1.297,7.224c-0.333,1.74 -0.523,2.733 -0.523,2.733c-0.004,0.02 -0.022,0.032 -0.042,0.028c-0.019,-0.003 -0.031,-0.021 -0.028,-0.04c0,0 0.158,-1 0.434,-2.749c0.266,-1.75 0.673,-4.247 1.058,-7.256c0.215,-1.501 0.387,-3.135 0.576,-4.86c0.178,-1.726 0.322,-3.551 0.435,-5.438c0.041,-0.943 0.081,-1.903 0.122,-2.875c0.035,-0.971 0.006,-1.949 0.012,-2.937c-0.002,-0.493 -0.004,-0.988 -0.007,-1.485l-0.081,-1.506c-0.067,-0.991 -0.081,-2.053 -0.192,-2.993c-0.528,-3.981 -1.245,-8.004 -1.585,-11.82c-0.182,-1.909 -0.274,-3.771 -0.216,-5.555c0.063,-1.784 0.246,-3.49 0.727,-5.071c0.215,-0.79 0.596,-1.547 1.082,-2.221c0.484,-0.689 1.327,-1.19 2.125,-1.292c0.403,-0.029 0.785,-0.035 1.111,0.069c0.167,0.039 0.334,0.077 0.486,0.128c0.141,0.066 0.279,0.132 0.415,0.196c0.559,0.23 0.947,0.575 1.334,0.838c0.715,0.61 1.203,1.115 1.501,1.489c0.308,0.369 0.473,0.565 0.473,0.565c0.013,0.016 0.011,0.038 -0.004,0.051C63.07,523.034 63.053,523.035 63.04,523.027z"/>
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M248.966,363.022c0,0 -0.143,0.494 -0.503,1.273c-0.359,0.782 -0.916,1.855 -1.723,3.055c-0.405,0.601 -0.889,1.218 -1.399,1.872c-0.509,0.653 -1.099,1.304 -1.738,1.949c-0.647,0.64 -1.287,1.32 -2.011,1.949c-0.716,0.635 -1.425,1.292 -2.122,1.958c-0.734,0.628 -1.469,1.256 -2.191,1.874c-0.702,0.64 -1.497,1.145 -2.209,1.697c-0.724,0.541 -1.42,1.064 -2.129,1.491c-0.695,0.445 -1.352,0.866 -1.958,1.255c-0.596,0.403 -1.192,0.692 -1.693,0.991c-0.508,0.288 -0.95,0.539 -1.314,0.745c-0.729,0.413 -1.145,0.648 -1.145,0.648c-0.017,0.01 -0.039,0.004 -0.049,-0.013c-0.009,-0.017 -0.002,-0.039 0.014,-0.048c0,0 0.4,-0.261 1.102,-0.718c0.351,-0.228 0.775,-0.504 1.264,-0.822c0.48,-0.328 1.055,-0.649 1.616,-1.096c0.572,-0.428 1.192,-0.892 1.848,-1.383c0.669,-0.472 1.319,-1.039 1.993,-1.623c0.662,-0.596 1.402,-1.144 2.047,-1.823c0.663,-0.655 1.337,-1.321 2.01,-1.987c0.629,-0.711 1.245,-1.432 1.835,-2.153c0.6,-0.711 1.109,-1.488 1.658,-2.183c1.099,-1.39 2.111,-2.705 3.1,-3.735c0.503,-0.507 0.945,-1.003 1.384,-1.398c0.422,-0.41 0.821,-0.743 1.152,-1.015c0.664,-0.544 1.106,-0.806 1.106,-0.806c0.019,-0.011 0.042,-0.005 0.053,0.014C248.968,363.001 248.969,363.013 248.966,363.022z"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M337.563,739.377c3.495,12.533 8.209,19.954 16.625,29.873"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M360.188,792.75c-5.732,-1.196 -9.619,0.341 -15.125,2"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M137.438,771.5c-0.549,11.256 -4.222,15.88 -10.5,25.377"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M113.688,816.877c-6.847,2.125 -12.803,2.576 -20,3"
|
||||
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M250.813,403.876c0,0 -0.246,-0.283 -0.62,-0.8c-0.377,-0.515 -0.88,-1.265 -1.434,-2.183c-1.104,-1.838 -2.333,-4.381 -3.33,-7.011c-0.861,-2.711 -1.486,-5.505 -1.76,-7.644c-0.137,-1.07 -0.205,-1.97 -0.226,-2.606c-0.025,-0.635 -0.006,-1.006 -0.006,-1.006s0.232,0.3 0.578,0.838c0.35,0.537 0.813,1.313 1.313,2.254c0.999,1.884 2.071,4.453 2.907,7.105c0.993,2.631 1.755,5.351 2.143,7.459c0.193,1.054 0.313,1.949 0.37,2.584C250.81,403.502 250.813,403.876 250.813,403.876z"/>
|
||||
<path android:fillColor="#FA002A" android:pathData="M275.813,259.75c2.225,-4.67 23.463,-28.569 21.125,-31C291.535,223.132 265.829,261.555 275.813,259.75z"/>
|
||||
<path android:fillColor="#FA002A" android:pathData="M279.188,221.626c1.113,-2.504 7.331,-6.185 6.625,-1.376c-0.468,3.186 -6.914,7.801 -8.875,10.5c-3.701,5.094 -8.024,19.159 -14.125,20.5C260.807,241.297 272.867,228.612 279.188,221.626z"/>
|
||||
<path android:fillColor="#FA002A" android:pathData="M271.688,218c5.358,-5.668 10.277,-1.495 3.375,3.626C270.036,225.356 266.084,221.832 271.688,218z"/>
|
||||
<path android:fillColor="#FA002A" android:pathData="M262.563,221c0.733,-2.185 11.817,-15.949 13.875,-10.25C277.19,212.836 264.699,221.63 262.563,221z"/>
|
||||
<path android:fillColor="#FA002A" android:pathData="M257.063,218.5c1.981,-3.302 7.674,-15.482 12.375,-10.75C265.533,210.375 260.92,220.092 257.063,218.5z"/>
|
||||
<path android:fillColor="#1300C6" android:pathData="M255.938,220.5c5.788,4.081 13.771,2.381 13.25,9.626c-0.026,0.37 -8.12,15.954 -8.625,16.374c-3.659,3.046 -14.066,-2.89 -13.62,-7.8C247.461,232.996 250.542,223.237 255.938,220.5z"/>
|
||||
<path android:fillColor="#FA002A" android:pathData="M289.938,224.626c0.333,5.91 -7.832,12.249 -11.391,16.565c-3.909,4.741 -5.481,12.765 -11.234,15.309C267.316,249.658 281.768,220.788 289.938,224.626z"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M103.438,326.75c3.49,2.737 7.772,4.977 8.25,9.75c-6.138,-1.255 -25.76,-11.779 -24.25,-18.374C93.332,318.604 98.615,323.664 103.438,326.75z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M56.688,385.126c-2.159,1.787 -18.553,5.835 -19.124,2.25C36.463,380.464 55.263,383.531 56.688,385.126z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A60053"
|
||||
android:pathData="M78.813,394.25c-2.641,0.338 1.361,-0.095 2.25,0C80.313,394.25 79.563,394.25 78.813,394.25z"
|
||||
android:strokeColor="#A60053" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FFFF00" android:pathData="M53.563,463.25c5.965,4.16 13.117,6.081 18.874,10.5c-5.903,1.747 -17.396,-4.874 -23.671,-6.508c-9.498,-2.473 -17.147,-1.08 -26.453,-0.742c1.038,-5.998 14.309,-4.932 19.44,-4.529c12.097,0.948 3.525,-14.243 11.06,-13.971C53.063,453.083 53.313,458.167 53.563,463.25z"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M232.938,503.877c-1.235,9.531 -4.108,19.102 -5.75,28.623c-0.8,4.638 -0.996,10.029 -2.5,14.5c-0.903,2.686 -2.414,7.929 -5.25,1.127c-2.6,-6.236 4.209,-23.522 5.433,-29.439C225.71,514.622 229.626,495.624 232.938,503.877z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M240.188,505.5c0.49,18.999 -8.947,34.421 -10.5,52c0,-9.837 1.354,-19.467 1.851,-29.383C231.729,524.29 234.769,501.56 240.188,505.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M133.938,509.127c5.188,1.984 12.112,3.215 16,7.25c-4.437,3.969 -12.418,-0.811 -17.147,-2.577c-6.249,-2.335 -13.143,-3.05 -19.729,-4.385c-6.679,-1.354 -8.324,-4.569 -0.247,-4.165C120.03,505.61 126.891,507.542 133.938,509.127z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M247.313,506.377c0.537,4.927 -3.634,26.399 -7.125,18.5C238.668,521.438 242.461,503.3 247.313,506.377z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M127.188,517.127c0.583,0.541 1.167,1.082 1.75,1.623c-5.068,1.583 -18.704,0.165 -21.624,-4.373C113.616,511.233 121.182,514.888 127.188,517.127z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M119.813,524.877c0.844,6.79 -9.675,3.093 -10,-2.25C113.197,523.043 117.344,522.659 119.813,524.877z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M271.688,572c0.773,8.227 1.547,16.453 2.32,24.68c0.567,6.04 1.542,1.898 3.805,5.447c1.54,2.416 5.701,3.513 2.75,6.873c-0.929,-1.015 -2.388,-1.538 -3.375,-2.5c-0.857,3.351 0.093,5.071 3.375,4.25c-0.667,0.459 -1.333,0.918 -2,1.377c2.004,2.838 7.75,5.175 7.75,8.437c0,2.019 -8.258,4.788 -0.991,7.484c6.181,2.293 2.54,8.87 -2.073,5.052c-3.792,-3.139 -8.391,-7.059 -9.295,-11.989c-1.661,-9.063 -12.095,-62.388 -5.016,-65.983C269.854,560.751 270.771,566.376 271.688,572z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M143.813,612.377c-4.079,5.317 -11.072,6.564 -15.924,10.877c-4.354,3.87 -10.099,12.56 -15.576,8.996c2.428,-6.34 8.99,-11.623 14.68,-15.012c5.541,-3.3 4.569,-11.064 9.32,-12.861c0.277,2.033 1.548,3.96 1.75,6c2.81,-2.87 2.101,-13.25 4.375,-13.25C145.29,597.127 143.813,609.424 143.813,612.377z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M172.313,655c-0.79,4.546 -32.291,7.431 -35.626,4.627c5.006,-5.458 5.655,-5.415 15.571,-6.095C156.253,653.259 170.864,649.868 172.313,655z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M321.563,661c-2.592,3.908 -5.184,7.815 -7.775,11.724c-1.862,2.807 3.057,4.037 2.15,6.026c-2.131,4.678 -18.698,5.782 -23.62,7.209c-4.669,1.353 -2.869,-4.493 -1.093,-6.432c2.537,-2.768 8.138,-2.279 11.449,-4.583c7.477,-5.201 12.119,-11.943 18.014,-18.817C319.493,658.676 321.263,658.806 321.563,661z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M155.438,665.25c-0.743,5.374 -13.75,7.711 -13.75,1.375c0,-6.806 12.909,-2.51 16.25,-1.375C157.104,665.25 156.271,665.25 155.438,665.25z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#A89187"
|
||||
android:pathData="M309.313,690.127c-1.826,1.37 -12.029,4.19 -10.5,-0.25C299.974,686.507 308.207,687.872 309.313,690.127z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M344.813,692c-11.668,5.957 -22.197,12.215 -34.367,17.402c-4.347,1.853 -34.706,13.177 -37.07,7.036c-1.405,-3.65 14.092,-3.136 16.281,-3.277c5.316,-0.343 14.506,-2.691 19.035,-5.502c11.026,-6.844 23.721,-13.549 35.496,-19.159C344.766,689.629 344.917,690.744 344.813,692z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000"
|
||||
android:pathData="M102.563,714.5c17.159,-2.251 32.889,15.131 49.874,4.127c-0.667,0.75 -1.333,1.5 -2,2.25c5.008,0.152 14.498,-0.661 19.376,-3.127c-1.728,2.915 0.97,9.614 -1.861,10.357c-4.976,1.305 -9.879,2.143 -15.027,2.143c-4.826,0 -9.651,0 -14.477,0c-4.904,0 -8.795,-2 -13.506,-2c-6.384,0 -15.344,-4.258 -21.505,-7.373c-4.171,-2.109 -9.427,-1.75 -8.754,-7.307C95.183,709.453 100.844,712.602 102.563,714.5z"
|
||||
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M88.849,22.891C88.601,17.992 89.917,6.615 83,5.75c-0.421,-0.053 -0.896,0.28 -1.087,0.633c-1.324,2.432 -0.769,4.769 -0.324,7.343c0.67,3.875 0.502,7.938 0.817,11.854c0.632,7.834 2.416,15.559 2.938,23.42c0.226,3.407 5.161,3.444 5.313,0C91.046,40.131 89.292,31.678 88.849,22.891z"/>
|
||||
<path android:fillColor="#CBFF00" android:pathData="M207.5,283a5.75,5.5 0,1 0,11.5 0a5.75,5.5 0,1 0,-11.5 0z"/>
|
||||
</vector>
|
|
@ -1,5 +0,0 @@
|
|||
<vector android:height="64dp" android:viewportHeight="401.294"
|
||||
android:viewportWidth="401.294" android:width="64dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M115.342,188.015c-7.025,0 -12.741,5.716 -12.741,12.741s5.716,12.741 12.741,12.741c7.026,0 12.742,-5.716 12.742,-12.741S122.368,188.015 115.342,188.015zM115.342,205.497c-2.614,0 -4.741,-2.127 -4.741,-4.741s2.127,-4.741 4.741,-4.741c2.615,0 4.742,2.127 4.742,4.741S117.957,205.497 115.342,205.497z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M397.294,164.141c2.209,0 4,-1.791 4,-4V89.215c0,-2.209 -1.791,-4 -4,-4h-33.219c-2.209,0 -4,1.791 -4,4v70.926c0,2.209 1.791,4 4,4h12.61v8.999h-36.205c-6.008,-4.585 -13.401,-7.094 -20.986,-7.094c-17.762,0 -32.434,13.455 -34.376,30.706h-3.421v-9.79c0,-2.209 -1.791,-4 -4,-4h-28.993l-6.173,-11.125c-0.705,-1.271 -2.044,-2.059 -3.498,-2.059h-39.513v-6.524h16.196c2.209,0 4,-1.791 4,-4V73.281c0,-2.209 -1.791,-4 -4,-4h-40.266c-2.209,0 -4,1.791 -4,4v85.972c0,2.209 1.791,4 4,4h16.069v6.524h-38.468c-2.209,0 -4,1.791 -4,4v22.974h-4.52c-1.97,-15.047 -14.865,-26.705 -30.44,-26.705c-10.313,0 -19.703,5.125 -25.326,13.331c-5.623,-8.206 -15.012,-13.331 -25.326,-13.331H44.653v-5.905h6.324c2.209,0 4,-1.791 4,-4V89.215c0,-2.209 -1.791,-4 -4,-4H17.758c-2.209,0 -4,1.791 -4,4v70.926c0,2.209 1.791,4 4,4h6.325v5.905H4c-2.209,0 -4,1.791 -4,4v53.419c0,2.209 1.791,4 4,4h20.083v5.905h-6.325c-2.209,0 -4,1.791 -4,4v70.927c0,2.209 1.791,4 4,4h33.219c2.209,0 4,-1.791 4,-4v-70.927c0,-2.209 -1.791,-4 -4,-4h-6.325v-5.905h18.789c10.313,0 19.703,-5.125 25.326,-13.331c5.624,8.205 15.013,13.331 25.326,13.331c15.578,0 28.476,-11.663 30.441,-26.714h4.518v22.765c0,2.209 1.791,4 4,4h38.468v6.524h-16.069c-2.209,0 -4,1.791 -4,4v85.972c0,2.209 1.791,4 4,4h40.266c2.209,0 4,-1.791 4,-4V242.04c0,-2.209 -1.791,-4 -4,-4h-16.196v-6.524h39.513c1.454,0 2.792,-0.789 3.498,-2.059l6.173,-11.125h28.993c2.209,0 4,-1.791 4,-4v-9.581h3.447c2.037,17.151 16.66,30.497 34.35,30.497c7.581,0 14.972,-2.507 20.978,-7.087h36.214v9.209h-12.61c-2.209,0 -4,1.791 -4,4v70.927c0,2.209 1.791,4 4,4h33.219c2.209,0 4,-1.791 4,-4v-70.927c0,-2.209 -1.791,-4 -4,-4h-12.609v-9.209h12.435c2.209,0 4,-1.791 4,-4V177.14c0,-2.209 -1.791,-4 -4,-4h-12.435v-8.999H397.294zM199.521,155.253v-13.221h12.196v13.221H199.521zM191.521,112.267h-12.069V98.502h12.069V112.267zM199.521,98.502h12.196v13.765h-12.196V98.502zM191.521,120.267v13.765h-12.069v-13.765H191.521zM199.521,120.267h12.196v13.765h-12.196V120.267zM211.717,90.502h-12.196V77.281h12.196V90.502zM191.521,77.281v13.221h-12.069V77.281H191.521zM179.451,155.253v-13.221h12.069v13.221H179.451zM24.689,223.466v-45.419h22.038v45.419H24.689zM54.727,178.046h6.641v45.419h-6.641V178.046zM46.977,128.678v9.956H21.758v-9.956H46.977zM21.758,120.678v-9.957h25.219v9.957H21.758zM46.977,93.215v9.507H21.758v-9.507H46.977zM21.758,146.634h25.219v9.507H21.758V146.634zM32.083,164.141h4.569v5.905h-4.569V164.141zM8,178.046h8.689v45.419H8V178.046zM21.758,272.834v-9.956h25.219v9.956H21.758zM46.977,280.834v9.956H21.758v-9.956H46.977zM21.758,308.298v-9.507h25.219v9.507H21.758zM46.977,254.878H21.758v-9.507h25.219V254.878zM36.652,237.371h-4.569v-5.905h4.569V237.371zM69.368,222.667v-43.822c6.952,1.876 12.703,6.983 15.31,13.918v15.987C82.071,215.684 76.32,220.791 69.368,222.667zM114.093,223.466c-9.664,0 -18.222,-6.091 -21.415,-15.184v-15.05c3.193,-9.094 11.751,-15.185 21.415,-15.185c12.522,0 22.709,10.188 22.709,22.71C136.802,213.278 126.615,223.466 114.093,223.466zM191.521,246.04v13.221h-12.069V246.04H191.521zM199.521,289.027h12.196v13.765h-12.196V289.027zM191.521,302.792h-12.069v-13.765h12.069V302.792zM199.521,281.027v-13.765h12.196v13.765H199.521zM191.521,281.027h-12.069v-13.765h12.069V281.027zM179.451,310.792h12.069v13.221h-12.069V310.792zM199.521,324.012v-13.221h12.196v13.221H199.521zM211.717,246.04v13.221h-12.196V246.04H211.717zM157.052,177.778h11.046v45.739h-11.046V177.778zM236.678,223.516h-60.58v-45.739h60.58l5.311,9.571v26.596L236.678,223.516zM273.696,210.332h-23.707v-19.371h23.707V210.332zM368.075,272.834v-9.956h25.219v9.956H368.075zM393.294,280.834v9.956h-25.219v-9.956H393.294zM368.075,308.298v-9.507h25.219v9.507H368.075zM393.294,254.878h-25.219v-9.507h25.219V254.878zM292.892,200.647c0,-8.817 4.315,-16.641 10.94,-21.484v42.968C297.207,217.288 292.892,209.463 292.892,200.647zM319.493,227.248c-2.663,0 -5.234,-0.398 -7.661,-1.129v-50.944c2.428,-0.731 4.998,-1.129 7.661,-1.129c6.217,0 12.267,2.193 17.036,6.175c0.248,0.208 0.521,0.368 0.803,0.506v39.849c-0.29,0.141 -0.57,0.304 -0.821,0.513C331.747,225.061 325.703,227.248 319.493,227.248zM393.119,220.161h-47.787V181.14h47.787V220.161zM393.294,128.678v9.956h-25.219v-9.956H393.294zM368.075,120.678v-9.957h25.219v9.957H368.075zM393.294,93.215v9.507h-25.219v-9.507H393.294zM368.075,146.634h25.219v9.507h-25.219V146.634z"/>
|
||||
</vector>
|
|
@ -1,6 +0,0 @@
|
|||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="80dp"
|
||||
android:minHeight="80dp"
|
||||
android:targetCellWidth="4"
|
||||
android:targetCellHeight="2"
|
||||
android:resizeMode="none" />
|
|
@ -1,10 +0,0 @@
|
|||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="80dp"
|
||||
android:minHeight="80dp"
|
||||
android:targetCellWidth="2"
|
||||
android:targetCellHeight="2"
|
||||
android:minResizeWidth="40dp"
|
||||
android:minResizeHeight="40dp"
|
||||
android:maxResizeWidth="120dp"
|
||||
android:maxResizeHeight="120dp"
|
||||
android:resizeMode="horizontal|vertical" />
|
|
@ -16,9 +16,8 @@ dependencies {
|
|||
with(Deps.Ktor) {
|
||||
implementation(serverCore)
|
||||
implementation(serverNetty)
|
||||
implementation(serialization)
|
||||
implementation(websockets)
|
||||
implementation(serverContentNegotiation)
|
||||
implementation(json)
|
||||
}
|
||||
|
||||
with(Deps.Log) {
|
||||
|
|
|
@ -26,16 +26,9 @@ val personImages = mapOf(
|
|||
"Anton Shkaplerov" to "https://alchetron.com/cdn/anton-shkaplerov-799e7545-54ae-4145-83d1-18f906d22b1-resize-750.jpeg",
|
||||
"Klim Shipenko" to "https://upload.wikimedia.org/wikipedia/pt/f/f9/Shipenko_klim.jpg",
|
||||
"Yulia Pereslid" to "https://metro.co.uk/wp-content/uploads/2021/10/PRI_203475964.jpg?quality=90&strip=all&zoom=1&resize=540%2C810",
|
||||
"Zhai Zhigang" to "https://dingyue.ws.126.net/2021/0701/aaa7e1e7j00qvkk13001hc000hs00lzg.jpg",
|
||||
"Zhai Zhigang" to "https://lh3.googleusercontent.com/proxy/lWh12ncF8OJvAf51jP49Slc3jbtKBNsexI19tHqXSkhv1pChXeuOF1xjGC3IcsoMlDh9ch_E_f7f7j1LdsFeJND5VRwmI2dOGDJ6invQEhqDTjJqfiSZ2SPmELoHyPYuNPOPM4bHUcFb44s",
|
||||
"Wang Yaping" to "https://alchetron.com/cdn/wang-yaping-2f869150-f66f-4884-b638-62ff678084a-resize-750.jpeg",
|
||||
"Ye Guangfu" to "https://upload.wikimedia.org/wikipedia/commons/a/a2/Ye_Guangfu_in_2021.jpg",
|
||||
"Raja Chari" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_RajaChari.jpg",
|
||||
"Tom Marshburn" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_TomMarshburn.jpg",
|
||||
"Kayla Barron" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_KaylaBarron.jpg",
|
||||
"Matthias Maurer" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_MathiasMaurer.jpg",
|
||||
"Alexander Misurkin" to "https://spaceflight101.com/iss/wp-content/uploads/sites/37/2017/08/36468940815_70ef48a8b6_o-768x1152.jpg",
|
||||
"Yusaku Maezawa" to "https://pbs.twimg.com/media/FD599ihaUAAyjC7?format=jpg",
|
||||
"Yozo Hirano" to "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS1lYvHXPvl-eG3yX5MWAtvTaxkoiISJ5KXrg&usqp=CAU",
|
||||
"Ye Guangfu" to "https://lh3.googleusercontent.com/proxy/gRPPtJvE3yJ3gKbFKcO2_AacsmfNXebiSlibtt7D7i-7S0cxTdc94i08JpLuVFxYdue4pnHGwh7mhpL1zwwA2UaawIUeQ1V0-PKKMhWHmp9c4zSeIfp9oh6ET31jWv6-QkFmYYT6ug",
|
||||
)
|
||||
|
||||
val personBios = mapOf(
|
||||
|
@ -69,12 +62,4 @@ val personBios = mapOf(
|
|||
"Zhai Zhigang" to "Zhai Zhigang (born October 10, 1966) is a major general of the People's Liberation Army Strategic Support Force (PLASSF) in active service as a People's Liberation Army Astronaut Corps (PLAAC) taikonaut. During the Shenzhou 7 mission in 2008, he became the first Chinese citizen to carry out a spacewalk. He was a People's Liberation Army Air Force (PLAAF) fighter pilot.",
|
||||
"Wang Yaping" to "Colonel Wang Yaping (born 27 January 1980) is a Chinese military pilot and astronaut. Wang was the second female astronaut selected to the People's Liberation Army Astronaut Corps, and the second Chinese woman in space.",
|
||||
"Ye Guangfu" to "Colonel Ye Guangfu (Chinese: 叶光富; born 1 September 1980[1]) is a Chinese People's Liberation Army Astronaut Corps (PLAAC) astronaut selected as part of the Shenzhou program.",
|
||||
"Raja Chari" to "Raja Jon Vurputoor \"Grinder\" Chari (born June 24, 1977; Colonel, United States Air Force) is an American test pilot and NASA astronaut. He is a graduate of the U.S. Air Force Academy, Massachusetts Institute of Technology, and U.S. Naval Test Pilot School, and has over 2,000 flying hours.",
|
||||
"Tom Marshburn" to "Thomas Henry \"Tom\" Marshburn (born August 29, 1960) is an American physician and a NASA astronaut. He is a veteran of two spaceflights to the International Space Station.",
|
||||
"Kayla Barron" to "Kayla Jane Barron (born September 19, 1987; LCDR, USN) is an American submarine warfare officer, engineer and NASA astronaut.",
|
||||
"Matthias Maurer" to "Matthias Josef Maurer (born 18 March 1970 in St. Wendel, Saarland) is a German European Space Agency astronaut and materials scientist, who was selected in 2015 to take part in space training.",
|
||||
"Alexander Misurkin" to "Alexander Alexanderovich Misurkin (Russian: Aлександр Aлександрович Мисуркин) (born September 23, 1977), a major in the Russian Air Force, is a Russian cosmonaut, selected in 2006. He flew aboard Soyuz TMA-08M on 28 March 2013 as his first space mission, and launched on Soyuz MS-06 as his second flight, in 2017. He was Commander of the International Space Station for Expedition 54.",
|
||||
"Yusaku Maezawa" to "Yusaku Maezawa (前澤 友作, Maezawa Yūsaku, born 22 November 1975) is a Japanese billionaire entrepreneur and art collector. He founded Start Today in 1998 and launched the online fashion retail website Zozotown in 2004, now Japan's largest. Most recently, Maezawa introduced a custom-fit apparel brand ZOZO and at-home measurement system, the ZOZOSUIT, in 2018. As of July 2021, he is estimated by Forbes to have a net worth of $1.9 billion.",
|
||||
"Yozo Hirano" to "Yozo Hirano (Japanese: 平野 陽三, 1985- ) born in Imabari, Ehime Prefecture, Japan, is a Japanese spaceflight participant. He is scheduled to fly on Soyuz MS-20.\n\n"
|
||||
+ "He is foreseen to fly with Yusaku Maezawa, who will pay for both seats; his function will be as production assistant of Maezawa and to document the flight."
|
||||
)
|
||||
|
|
|
@ -2,13 +2,14 @@ import com.surrus.common.di.initKoin
|
|||
import com.surrus.common.remote.Assignment
|
||||
import com.surrus.common.remote.AstroResult
|
||||
import com.surrus.common.remote.PeopleInSpaceApi
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.application.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.serialization.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.ktor.server.plugins.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun main() {
|
||||
val koin = initKoin(enableNetworkLogs = true).koin
|
||||
|
@ -21,6 +22,20 @@ fun main() {
|
|||
json()
|
||||
}
|
||||
|
||||
install(CORS) {
|
||||
method(HttpMethod.Options)
|
||||
method(HttpMethod.Put)
|
||||
method(HttpMethod.Delete)
|
||||
method(HttpMethod.Patch)
|
||||
header(HttpHeaders.Authorization)
|
||||
header(HttpHeaders.ContentType)
|
||||
header(HttpHeaders.AccessControlAllowOrigin)
|
||||
// header("any header") if you want to add any header
|
||||
allowCredentials = true
|
||||
allowNonSimpleContentTypes = true
|
||||
anyHost()
|
||||
}
|
||||
|
||||
routing {
|
||||
|
||||
get("/astros.json") {
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
buildscript {
|
||||
val kotlinVersion: String by project
|
||||
println(kotlinVersion)
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -11,16 +8,15 @@ buildscript {
|
|||
|
||||
dependencies {
|
||||
// keeping this here to allow AS to automatically update
|
||||
classpath("com.android.tools.build:gradle:7.1.0")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
classpath("org.jetbrains.kotlin:kotlin-serialization:${kotlinVersion}")
|
||||
classpath("com.android.tools.build:gradle:7.0.3")
|
||||
|
||||
with(Deps.Gradle) {
|
||||
classpath(kotlin)
|
||||
classpath(kotlinSerialization)
|
||||
classpath(sqlDelight)
|
||||
classpath(shadow)
|
||||
classpath(kotlinter)
|
||||
classpath(gradleVersionsPlugin)
|
||||
classpath("com.rickclephas.kmp:kmp-nativecoroutines-gradle-plugin:${Versions.kmpNativeCoroutinesVersion}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,13 +29,5 @@ allprojects {
|
|||
mavenCentral()
|
||||
maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers")
|
||||
maven(url = "https://jitpack.io")
|
||||
maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// On Apple Silicon we need Node.js 16.0.0
|
||||
// https://youtrack.jetbrains.com/issue/KT-49109
|
||||
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class) {
|
||||
rootProject.the(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension::class).nodeVersion = "16.0.0"
|
||||
}
|
|
@ -3,21 +3,18 @@ object Versions {
|
|||
const val androidCompileSdk = 31
|
||||
const val androidTargetSdk = androidCompileSdk
|
||||
|
||||
const val kotlinCoroutines = "1.6.0"
|
||||
const val koin = "3.1.4"
|
||||
const val ktor = "2.0.0-beta-1"
|
||||
const val kotlinxSerialization = "1.3.2"
|
||||
const val kotlin = "1.5.31"
|
||||
|
||||
const val kotlinCoroutines = "1.5.2-native-mt"
|
||||
const val koin = "3.1.2"
|
||||
const val ktor = "1.6.4"
|
||||
const val kotlinxSerialization = "1.2.2"
|
||||
const val kotlinxHtmlJs = "0.7.3"
|
||||
|
||||
const val kmpNativeCoroutinesVersion = "0.11.1-new-mm"
|
||||
|
||||
const val compose = "1.1.0-rc01"
|
||||
const val composeCompiler = "1.1.0-rc02"
|
||||
const val wearCompose = "1.0.0-alpha13"
|
||||
const val navCompose = "2.4.0-rc01"
|
||||
const val accompanist = "0.22.0-rc"
|
||||
|
||||
const val composeDesktopWeb = "1.0.1"
|
||||
const val compose = "1.0.4"
|
||||
const val wearCompose = "1.0.0-alpha08"
|
||||
const val navCompose = "2.4.0-alpha10"
|
||||
const val accompanist = "0.19.0"
|
||||
|
||||
const val junit = "4.12"
|
||||
const val androidXTestJUnit = "1.1.3"
|
||||
|
@ -25,7 +22,7 @@ object Versions {
|
|||
const val mockito = "3.11.2"
|
||||
const val robolectric = "4.6.1"
|
||||
|
||||
const val sqlDelight = "1.5.3"
|
||||
const val sqlDelight = "1.5.2"
|
||||
const val shadow = "7.0.0"
|
||||
const val kotlinterGradle = "3.4.5"
|
||||
|
||||
|
@ -43,13 +40,15 @@ object Versions {
|
|||
|
||||
const val slf4j = "1.7.30"
|
||||
const val logback = "1.2.3"
|
||||
const val kermit = "1.0.0"
|
||||
const val kermit = "0.1.9"
|
||||
|
||||
const val gradleVersionsPlugin = "0.39.0"
|
||||
}
|
||||
|
||||
object Deps {
|
||||
object Gradle {
|
||||
const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
|
||||
const val kotlinSerialization = "org.jetbrains.kotlin:kotlin-serialization:${Versions.kotlin}"
|
||||
const val kotlinter = "org.jmailen.gradle:kotlinter-gradle:${Versions.kotlinterGradle}"
|
||||
const val shadow = "gradle.plugin.com.github.jengelman.gradle.plugins:shadow:${Versions.shadow}"
|
||||
const val sqlDelight = "com.squareup.sqldelight:gradle-plugin:${Versions.sqlDelight}"
|
||||
|
@ -59,7 +58,6 @@ object Deps {
|
|||
object Kotlinx {
|
||||
const val serializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.kotlinxSerialization}"
|
||||
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}"
|
||||
}
|
||||
|
||||
|
@ -81,13 +79,15 @@ object Deps {
|
|||
const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}"
|
||||
const val testCore = "androidx.test:core:${Versions.testCore}"
|
||||
|
||||
const val kotlinTest = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}"
|
||||
const val kotlinTestJUnit = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlin}"
|
||||
|
||||
const val composeUiTest = "androidx.compose.ui:ui-test:${Versions.compose}"
|
||||
const val composeUiTestJUnit = "androidx.compose.ui:ui-test-junit4:${Versions.compose}"
|
||||
const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest:${Versions.compose}"
|
||||
}
|
||||
|
||||
object Compose {
|
||||
const val compiler = "androidx.compose.compiler:compiler:${Versions.composeCompiler}"
|
||||
const val ui = "androidx.compose.ui:ui:${Versions.compose}"
|
||||
const val uiGraphics = "androidx.compose.ui:ui-graphics:${Versions.compose}"
|
||||
const val uiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}"
|
||||
|
@ -114,19 +114,18 @@ object Deps {
|
|||
object Ktor {
|
||||
const val serverCore = "io.ktor:ktor-server-core:${Versions.ktor}"
|
||||
const val serverNetty = "io.ktor:ktor-server-netty:${Versions.ktor}"
|
||||
const val contentNegotiation = "io.ktor:ktor-client-content-negotiation:${Versions.ktor}"
|
||||
const val json = "io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}"
|
||||
|
||||
const val serverContentNegotiation = "io.ktor:ktor-server-content-negotiation:${Versions.ktor}"
|
||||
|
||||
const val serialization = "io.ktor:ktor-serialization:${Versions.ktor}"
|
||||
const val websockets = "io.ktor:ktor-websockets:${Versions.ktor}"
|
||||
const val clientCore = "io.ktor:ktor-client-core:${Versions.ktor}"
|
||||
const val clientJson = "io.ktor:ktor-client-json:${Versions.ktor}"
|
||||
const val clientLogging = "io.ktor:ktor-client-logging:${Versions.ktor}"
|
||||
const val clientSerialization = "io.ktor:ktor-client-serialization:${Versions.ktor}"
|
||||
const val clientAndroid = "io.ktor:ktor-client-android:${Versions.ktor}"
|
||||
const val clientApache = "io.ktor:ktor-client-apache:${Versions.ktor}"
|
||||
const val clientJava = "io.ktor:ktor-client-java:${Versions.ktor}"
|
||||
const val clientIos = "io.ktor:ktor-client-ios:${Versions.ktor}"
|
||||
const val clientCio = "io.ktor:ktor-client-cio:${Versions.ktor}"
|
||||
const val clientCurl = "io.ktor:ktor-client-curl:${Versions.ktor}"
|
||||
const val clientJs = "io.ktor:ktor-client-js:${Versions.ktor}"
|
||||
}
|
||||
|
||||
|
@ -156,9 +155,4 @@ object Deps {
|
|||
const val logback = "ch.qos.logback:logback-classic:${Versions.logback}"
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
|
@ -7,7 +6,6 @@ plugins {
|
|||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.native.cocoapods")
|
||||
id("com.squareup.sqldelight")
|
||||
id("com.rickclephas.kmp.nativecoroutines")
|
||||
id("com.chromaticnoise.multiplatform-swiftpackage") version "2.0.3"
|
||||
}
|
||||
|
||||
|
@ -28,15 +26,28 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
val iosTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget = when {
|
||||
System.getenv("SDK_NAME")?.startsWith("iphoneos") == true -> ::iosArm64
|
||||
System.getenv("NATIVE_ARCH")?.startsWith("arm") == true -> ::iosSimulatorArm64 // available to KT 1.5.30
|
||||
else -> ::iosX64
|
||||
// Workaround for https://youtrack.jetbrains.com/issue/KT-43944
|
||||
android {
|
||||
configurations {
|
||||
create("androidTestApi")
|
||||
create("androidTestDebugApi")
|
||||
create("androidTestReleaseApi")
|
||||
create("testApi")
|
||||
create("testDebugApi")
|
||||
create("testReleaseApi")
|
||||
}
|
||||
iosTarget("iOS") {}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
val sdkName: String? = System.getenv("SDK_NAME")
|
||||
|
||||
val isiOSDevice = sdkName.orEmpty().startsWith("iphoneos")
|
||||
if (isiOSDevice) {
|
||||
iosArm64("iOS")
|
||||
} else {
|
||||
iosX64("iOS")
|
||||
}
|
||||
|
||||
val isWatchOSDevice = sdkName.orEmpty().startsWith("watchos")
|
||||
if (isWatchOSDevice) {
|
||||
watchosArm64("watch")
|
||||
|
@ -61,17 +72,18 @@ kotlin {
|
|||
|
||||
sourceSets {
|
||||
sourceSets["commonMain"].dependencies {
|
||||
implementation(Deps.Kotlinx.coroutinesCore) {
|
||||
isForce = true
|
||||
}
|
||||
|
||||
with(Deps.Ktor) {
|
||||
implementation(clientCore)
|
||||
implementation(clientJson)
|
||||
implementation(clientLogging)
|
||||
implementation(contentNegotiation)
|
||||
implementation(json)
|
||||
implementation(clientSerialization)
|
||||
}
|
||||
|
||||
with(Deps.Kotlinx) {
|
||||
implementation(coroutinesCore)
|
||||
implementation(serializationCore)
|
||||
}
|
||||
|
||||
|
@ -90,10 +102,6 @@ kotlin {
|
|||
}
|
||||
}
|
||||
sourceSets["commonTest"].dependencies {
|
||||
implementation(Deps.Koin.test)
|
||||
implementation(Deps.Kotlinx.coroutinesTest)
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
|
||||
sourceSets["androidMain"].dependencies {
|
||||
|
@ -101,6 +109,10 @@ kotlin {
|
|||
implementation(Deps.SqlDelight.androidDriver)
|
||||
}
|
||||
sourceSets["androidTest"].dependencies {
|
||||
// having issue with following after update to Kotlin 1.5.21
|
||||
// need to investigate further
|
||||
//implementation(Deps.Test.kotlinTest)
|
||||
//implementation(Deps.Test.kotlinTestJUnit)
|
||||
implementation(Deps.Test.junit)
|
||||
}
|
||||
|
||||
|
@ -123,7 +135,7 @@ kotlin {
|
|||
}
|
||||
|
||||
sourceSets["macOSMain"].dependencies {
|
||||
implementation(Deps.Ktor.clientIos)
|
||||
implementation(Deps.Ktor.clientCurl)
|
||||
implementation(Deps.SqlDelight.nativeDriverMacos)
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,10 @@ Pod::Spec.new do |spec|
|
|||
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
|
||||
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
|
||||
-Pkotlin.native.cocoapods.archs="$ARCHS" \
|
||||
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION
|
||||
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION \
|
||||
-Pkotlin.native.cocoapods.cflags="$OTHER_CFLAGS" \
|
||||
-Pkotlin.native.cocoapods.paths.headers="$HEADER_SEARCH_PATHS" \
|
||||
-Pkotlin.native.cocoapods.paths.frameworks="$FRAMEWORK_SEARCH_PATHS"
|
||||
SCRIPT
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package com.surrus.common.repository
|
||||
|
||||
import co.touchlab.kermit.LogcatLogger
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
||||
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
|
||||
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
|
||||
import io.ktor.client.engine.android.*
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
||||
|
||||
actual fun platformModule() = module {
|
||||
single {
|
||||
val driver =
|
||||
|
@ -15,5 +14,5 @@ actual fun platformModule() = module {
|
|||
|
||||
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
|
||||
}
|
||||
single { Android.create() }
|
||||
single<Logger> { LogcatLogger() }
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package com.surrus.common.di
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.surrus.common.remote.PeopleInSpaceApi
|
||||
import com.surrus.common.repository.PeopleInSpaceRepository
|
||||
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
|
||||
import com.surrus.common.repository.platformModule
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.logging.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import io.ktor.client.features.logging.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
@ -28,21 +28,21 @@ fun initKoin() = initKoin(enableNetworkLogs = false) {}
|
|||
|
||||
fun commonModule(enableNetworkLogs: Boolean) = module {
|
||||
single { createJson() }
|
||||
single { createHttpClient(get(), get(), enableNetworkLogs = enableNetworkLogs) }
|
||||
single { createHttpClient(get(), enableNetworkLogs = enableNetworkLogs) }
|
||||
|
||||
single { CoroutineScope(Dispatchers.Default + SupervisorJob() ) }
|
||||
|
||||
single<PeopleInSpaceRepositoryInterface> { PeopleInSpaceRepository() }
|
||||
|
||||
single { PeopleInSpaceApi(get()) }
|
||||
single { Kermit(logger = get()) }
|
||||
}
|
||||
|
||||
fun createJson() = Json { isLenient = true; ignoreUnknownKeys = true }
|
||||
|
||||
|
||||
fun createHttpClient(httpClientEngine: HttpClientEngine, json: Json, enableNetworkLogs: Boolean) = HttpClient(httpClientEngine) {
|
||||
install(ContentNegotiation) {
|
||||
json(json)
|
||||
fun createHttpClient(json: Json, enableNetworkLogs: Boolean) = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(json)
|
||||
}
|
||||
if (enableNetworkLogs) {
|
||||
install(Logging) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.surrus.common.remote
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
@ -22,6 +21,6 @@ class PeopleInSpaceApi(
|
|||
private val client: HttpClient,
|
||||
var baseUrl: String = "https://people-in-space-proxy.ew.r.appspot.com",
|
||||
) : KoinComponent {
|
||||
suspend fun fetchPeople() = client.get("$baseUrl/astros.json").body<AstroResult>()
|
||||
suspend fun fetchISSPosition() = client.get("$baseUrl/iss-now.json").body<IssResponse>()
|
||||
suspend fun fetchPeople() = client.get<AstroResult>("$baseUrl/astros.json")
|
||||
suspend fun fetchISSPosition() = client.get<IssResponse>("$baseUrl/iss-now.json")
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.surrus.common.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
||||
import com.squareup.sqldelight.runtime.coroutines.mapToList
|
||||
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
|
||||
|
@ -12,6 +11,7 @@ import kotlinx.coroutines.*
|
|||
import kotlinx.coroutines.flow.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
interface PeopleInSpaceRepositoryInterface {
|
||||
fun fetchPeopleAsFlow(): Flow<List<Assignment>>
|
||||
|
@ -22,13 +22,13 @@ interface PeopleInSpaceRepositoryInterface {
|
|||
|
||||
class PeopleInSpaceRepository : KoinComponent, PeopleInSpaceRepositoryInterface {
|
||||
private val peopleInSpaceApi: PeopleInSpaceApi by inject()
|
||||
private val logger: Kermit by inject()
|
||||
|
||||
@NativeCoroutineScope
|
||||
private val coroutineScope: CoroutineScope = MainScope()
|
||||
private val peopleInSpaceDatabase: PeopleInSpaceDatabaseWrapper by inject()
|
||||
private val peopleInSpaceQueries = peopleInSpaceDatabase.instance?.peopleInSpaceQueries
|
||||
|
||||
val logger = Logger.withTag("PeopleInSpaceRepository")
|
||||
var peopleJob: Job? = null
|
||||
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
|
@ -71,21 +71,54 @@ class PeopleInSpaceRepository : KoinComponent, PeopleInSpaceRepositoryInterface
|
|||
}
|
||||
}
|
||||
|
||||
// Used by web and apple clients atm
|
||||
// Used by web client atm
|
||||
override suspend fun fetchPeople(): List<Assignment> = peopleInSpaceApi.fetchPeople().people
|
||||
|
||||
override fun pollISSPosition(): Flow<IssPosition> {
|
||||
return flow {
|
||||
while (true) {
|
||||
val position = peopleInSpaceApi.fetchISSPosition().iss_position
|
||||
emit(position)
|
||||
logger.d { position.toString() }
|
||||
delay(POLL_INTERVAL)
|
||||
// called from Kotlin/Native clients
|
||||
fun startObservingPeopleUpdates(success: (List<Assignment>) -> Unit) {
|
||||
logger.d { "startObservingPeopleUpdates" }
|
||||
peopleJob = coroutineScope.launch {
|
||||
fetchPeopleAsFlow().collect {
|
||||
success(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopObservingPeopleUpdates() {
|
||||
logger.d { "stopObservingPeopleUpdates, peopleJob = $peopleJob" }
|
||||
peopleJob?.cancel()
|
||||
}
|
||||
|
||||
override fun pollISSPosition(): Flow<IssPosition> = flow {
|
||||
while (true) {
|
||||
val position = peopleInSpaceApi.fetchISSPosition().iss_position
|
||||
emit(position)
|
||||
logger.d("PeopleInSpaceRepository") { position.toString() }
|
||||
delay(POLL_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
val iosScope: CoroutineScope = object : CoroutineScope {
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = SupervisorJob() + Dispatchers.Main
|
||||
}
|
||||
|
||||
fun iosPollISSPosition() = KotlinNativeFlowWrapper(pollISSPosition())
|
||||
|
||||
companion object {
|
||||
private const val POLL_INTERVAL = 10000L
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinNativeFlowWrapper<T>(private val flow: Flow<T>) {
|
||||
fun subscribe(
|
||||
scope: CoroutineScope,
|
||||
onEach: (item: T) -> Unit,
|
||||
onComplete: () -> Unit,
|
||||
onThrow: (error: Throwable) -> Unit
|
||||
) = flow
|
||||
.onEach { onEach(it) }
|
||||
.catch { onThrow(it) }
|
||||
.onCompletion { onComplete() }
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
package com.surrus.common.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import co.touchlab.kermit.NSLogLogger
|
||||
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
|
||||
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
|
||||
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
|
||||
import io.ktor.client.engine.ios.*
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
||||
actual fun platformModule() = module {
|
||||
single {
|
||||
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
|
||||
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
|
||||
}
|
||||
single { Ios.create() }
|
||||
single<Logger> { NSLogLogger() }
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
package com.surrus.common.repository
|
||||
|
||||
import co.touchlab.kermit.CommonLogger
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
|
||||
import io.ktor.client.engine.js.*
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual fun platformModule() = module {
|
||||
single {
|
||||
PeopleInSpaceDatabaseWrapper(null)
|
||||
}
|
||||
single { Js.create() }
|
||||
|
||||
single<Logger> { CommonLogger() }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package com.surrus.common.repository
|
||||
|
||||
import co.touchlab.kermit.CommonLogger
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
|
||||
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
|
||||
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
|
||||
import io.ktor.client.engine.java.*
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual fun platformModule() = module {
|
||||
|
@ -12,5 +13,6 @@ actual fun platformModule() = module {
|
|||
.also { PeopleInSpaceDatabase.Schema.create(it) }
|
||||
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
|
||||
}
|
||||
single { Java.create() }
|
||||
|
||||
single<Logger> { CommonLogger() }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package com.surrus.common.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import co.touchlab.kermit.NSLogLogger
|
||||
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
|
||||
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
|
||||
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
|
||||
import io.ktor.client.engine.ios.*
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual fun platformModule() = module {
|
||||
|
@ -11,5 +12,5 @@ actual fun platformModule() = module {
|
|||
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
|
||||
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
|
||||
}
|
||||
single { Ios.create() }
|
||||
single<Logger> { NSLogLogger() }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package com.surrus.common.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import co.touchlab.kermit.NSLogLogger
|
||||
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
|
||||
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
|
||||
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
|
||||
import io.ktor.client.engine.ios.*
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual fun platformModule() = module {
|
||||
|
@ -11,5 +12,5 @@ actual fun platformModule() = module {
|
|||
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
|
||||
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
|
||||
}
|
||||
single { Ios.create() }
|
||||
single<Logger> { NSLogLogger() }
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("org.jetbrains.compose") version Versions.composeDesktopWeb
|
||||
id("org.jetbrains.compose") version "1.0.0-beta1"
|
||||
application
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("org.jetbrains.compose") version Versions.composeDesktopWeb
|
||||
id("org.jetbrains.compose") version "1.0.0-beta1"
|
||||
}
|
||||
|
||||
version = "1.0"
|
||||
|
@ -33,7 +33,6 @@ kotlin {
|
|||
afterEvaluate {
|
||||
rootProject.extensions.configure<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
|
||||
versions.webpackDevServer.version = "4.0.0"
|
||||
versions.webpackCli.version = "4.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,4 @@ android.enableJetifier=false
|
|||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# XCode
|
||||
xcodeproj=./ios/PeopleInSpaceSwiftUI
|
||||
|
||||
# Kotlin/Native clients can override this through updating common.podspec
|
||||
kotlinVersion=1.6.10
|
||||
|
||||
kotlin.native.binary.memoryModel=experimental
|
||||
kotlin.native.binary.freezing=disabled
|
||||
xcodeproj=./ios/PeopleInSpaceSwiftUI
|
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
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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"
|
|
@ -7,13 +7,6 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1A2873952733F5C600D7C25E /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */; };
|
||||
1A287396273427B300D7C25E /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA82EEC25052E1E00193051 /* ImageView.swift */; };
|
||||
1A28791D2733F257008EC881 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A28791C2733F257008EC881 /* WidgetKit.framework */; };
|
||||
1A28791F2733F257008EC881 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A28791E2733F257008EC881 /* SwiftUI.framework */; };
|
||||
1A2879222733F257008EC881 /* PeopleInSpaceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2879212733F257008EC881 /* PeopleInSpaceWidget.swift */; };
|
||||
1A2879242733F259008EC881 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A2879232733F259008EC881 /* Assets.xcassets */; };
|
||||
1A2879282733F259008EC881 /* PeopleInSpaceWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1A28791B2733F257008EC881 /* PeopleInSpaceWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
1A40FE1923DB5C35008428A6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */; };
|
||||
1AA82EED25052E1E00193051 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA82EEC25052E1E00193051 /* ImageView.swift */; };
|
||||
1ABD44FA23B00008008387E3 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABD44F923B00008008387E3 /* ViewModel.swift */; };
|
||||
|
@ -23,44 +16,12 @@
|
|||
1ABFB8C423AFF5CE003D807E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ABFB8C323AFF5CE003D807E /* Assets.xcassets */; };
|
||||
1ABFB8C723AFF5CE003D807E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ABFB8C623AFF5CE003D807E /* Preview Assets.xcassets */; };
|
||||
1ABFB8CA23AFF5CE003D807E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1ABFB8C823AFF5CE003D807E /* LaunchScreen.storyboard */; };
|
||||
1AC2439025A1D57700F17D2F /* IssPositionPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC2438F25A1D57700F17D2F /* IssPositionPublisher.swift */; };
|
||||
1AD2EC6A25E9984900CCEE81 /* ISSMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD2EC6925E9984900CCEE81 /* ISSMapView.swift */; };
|
||||
1E1255057DE614781855FC02 /* libPods-PeopleInSpaceSwiftUI.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28681F6577A67864E2C2D9B4 /* libPods-PeopleInSpaceSwiftUI.a */; };
|
||||
EB26652EEED4D2D4BCFA681D /* libPods-PeopleInSpaceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0873B836B9801BC514F9A555 /* libPods-PeopleInSpaceWidgetExtension.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
1A2879262733F259008EC881 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 1ABFB8B223AFF5CC003D807E /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 1A28791A2733F257008EC881;
|
||||
remoteInfo = PeopleInSpaceWidgetExtension;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
1A28792C2733F259008EC881 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
1A2879282733F259008EC881 /* PeopleInSpaceWidgetExtension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0873B836B9801BC514F9A555 /* libPods-PeopleInSpaceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PeopleInSpaceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
18A371146E3D470DBF29C172 /* Pods-PeopleInSpaceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PeopleInSpaceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PeopleInSpaceWidgetExtension/Pods-PeopleInSpaceWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
1A28791B2733F257008EC881 /* PeopleInSpaceWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PeopleInSpaceWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1A28791C2733F257008EC881 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
1A28791E2733F257008EC881 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
1A2879212733F257008EC881 /* PeopleInSpaceWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleInSpaceWidget.swift; sourceTree = "<group>"; };
|
||||
1A2879232733F259008EC881 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
1A2879252733F259008EC881 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
|
||||
1AA82EEC25052E1E00193051 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||
1ABD44F923B00008008387E3 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -72,25 +33,14 @@
|
|||
1ABFB8C623AFF5CE003D807E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
1ABFB8C923AFF5CE003D807E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
1ABFB8CB23AFF5CE003D807E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
1AC2438F25A1D57700F17D2F /* IssPositionPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssPositionPublisher.swift; sourceTree = "<group>"; };
|
||||
1AD2EC6925E9984900CCEE81 /* ISSMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ISSMapView.swift; sourceTree = "<group>"; };
|
||||
28681F6577A67864E2C2D9B4 /* libPods-PeopleInSpaceSwiftUI.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PeopleInSpaceSwiftUI.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
56CD7101BBBC60F561BFB049 /* Pods-PeopleInSpaceSwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PeopleInSpaceSwiftUI.release.xcconfig"; path = "Target Support Files/Pods-PeopleInSpaceSwiftUI/Pods-PeopleInSpaceSwiftUI.release.xcconfig"; sourceTree = "<group>"; };
|
||||
BD4D317D12DFE46788F27B26 /* Pods-PeopleInSpaceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PeopleInSpaceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PeopleInSpaceWidgetExtension/Pods-PeopleInSpaceWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||
E9A29AFE5FFFB564C509840A /* Pods-PeopleInSpaceSwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PeopleInSpaceSwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-PeopleInSpaceSwiftUI/Pods-PeopleInSpaceSwiftUI.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
1A2879182733F257008EC881 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1A28791F2733F257008EC881 /* SwiftUI.framework in Frameworks */,
|
||||
1A28791D2733F257008EC881 /* WidgetKit.framework in Frameworks */,
|
||||
1A2873952733F5C600D7C25E /* libsqlite3.tbd in Frameworks */,
|
||||
EB26652EEED4D2D4BCFA681D /* libPods-PeopleInSpaceWidgetExtension.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1ABFB8B723AFF5CC003D807E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -108,27 +58,14 @@
|
|||
children = (
|
||||
E9A29AFE5FFFB564C509840A /* Pods-PeopleInSpaceSwiftUI.debug.xcconfig */,
|
||||
56CD7101BBBC60F561BFB049 /* Pods-PeopleInSpaceSwiftUI.release.xcconfig */,
|
||||
18A371146E3D470DBF29C172 /* Pods-PeopleInSpaceWidgetExtension.debug.xcconfig */,
|
||||
BD4D317D12DFE46788F27B26 /* Pods-PeopleInSpaceWidgetExtension.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1A2879202733F257008EC881 /* PeopleInSpaceWidget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1A2879212733F257008EC881 /* PeopleInSpaceWidget.swift */,
|
||||
1A2879232733F259008EC881 /* Assets.xcassets */,
|
||||
1A2879252733F259008EC881 /* Info.plist */,
|
||||
);
|
||||
path = PeopleInSpaceWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1ABFB8B123AFF5CC003D807E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1ABFB8BC23AFF5CC003D807E /* PeopleInSpaceSwiftUI */,
|
||||
1A2879202733F257008EC881 /* PeopleInSpaceWidget */,
|
||||
1ABFB8BB23AFF5CC003D807E /* Products */,
|
||||
0682AAA6A83414CA1CC104C4 /* Pods */,
|
||||
D0F1ED5C992B526EA33236F9 /* Frameworks */,
|
||||
|
@ -139,7 +76,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
1ABFB8BA23AFF5CC003D807E /* PeopleInSpaceSwiftUI.app */,
|
||||
1A28791B2733F257008EC881 /* PeopleInSpaceWidgetExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -156,6 +92,7 @@
|
|||
1ABFB8C523AFF5CE003D807E /* Preview Content */,
|
||||
1ABD44F923B00008008387E3 /* ViewModel.swift */,
|
||||
1AA82EEC25052E1E00193051 /* ImageView.swift */,
|
||||
1AC2438F25A1D57700F17D2F /* IssPositionPublisher.swift */,
|
||||
1AD2EC6925E9984900CCEE81 /* ISSMapView.swift */,
|
||||
);
|
||||
path = PeopleInSpaceSwiftUI;
|
||||
|
@ -174,9 +111,6 @@
|
|||
children = (
|
||||
1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */,
|
||||
28681F6577A67864E2C2D9B4 /* libPods-PeopleInSpaceSwiftUI.a */,
|
||||
1A28791C2733F257008EC881 /* WidgetKit.framework */,
|
||||
1A28791E2733F257008EC881 /* SwiftUI.framework */,
|
||||
0873B836B9801BC514F9A555 /* libPods-PeopleInSpaceWidgetExtension.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -184,24 +118,6 @@
|
|||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
1A28791A2733F257008EC881 /* PeopleInSpaceWidgetExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 1A2879292733F259008EC881 /* Build configuration list for PBXNativeTarget "PeopleInSpaceWidgetExtension" */;
|
||||
buildPhases = (
|
||||
CB56E28CF58AE58B52E5412F /* [CP] Check Pods Manifest.lock */,
|
||||
1A2879172733F257008EC881 /* Sources */,
|
||||
1A2879182733F257008EC881 /* Frameworks */,
|
||||
1A2879192733F257008EC881 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = PeopleInSpaceWidgetExtension;
|
||||
productName = PeopleInSpaceWidgetExtension;
|
||||
productReference = 1A28791B2733F257008EC881 /* PeopleInSpaceWidgetExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
1ABFB8B923AFF5CC003D807E /* PeopleInSpaceSwiftUI */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 1ABFB8CE23AFF5CE003D807E /* Build configuration list for PBXNativeTarget "PeopleInSpaceSwiftUI" */;
|
||||
|
@ -210,12 +126,10 @@
|
|||
1ABFB8B623AFF5CC003D807E /* Sources */,
|
||||
1ABFB8B723AFF5CC003D807E /* Frameworks */,
|
||||
1ABFB8B823AFF5CC003D807E /* Resources */,
|
||||
1A28792C2733F259008EC881 /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
1A2879272733F259008EC881 /* PBXTargetDependency */,
|
||||
);
|
||||
name = PeopleInSpaceSwiftUI;
|
||||
productName = PeopleInSpaceSwiftUI;
|
||||
|
@ -228,13 +142,10 @@
|
|||
1ABFB8B223AFF5CC003D807E /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1310;
|
||||
LastSwiftUpdateCheck = 1130;
|
||||
LastUpgradeCheck = 1130;
|
||||
ORGANIZATIONNAME = "John O'Reilly";
|
||||
TargetAttributes = {
|
||||
1A28791A2733F257008EC881 = {
|
||||
CreatedOnToolsVersion = 13.1;
|
||||
};
|
||||
1ABFB8B923AFF5CC003D807E = {
|
||||
CreatedOnToolsVersion = 11.3;
|
||||
};
|
||||
|
@ -254,20 +165,11 @@
|
|||
projectRoot = "";
|
||||
targets = (
|
||||
1ABFB8B923AFF5CC003D807E /* PeopleInSpaceSwiftUI */,
|
||||
1A28791A2733F257008EC881 /* PeopleInSpaceWidgetExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
1A2879192733F257008EC881 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1A2879242733F259008EC881 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1ABFB8B823AFF5CC003D807E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -303,45 +205,15 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
CB56E28CF58AE58B52E5412F /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-PeopleInSpaceWidgetExtension-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
1A2879172733F257008EC881 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1A287396273427B300D7C25E /* ImageView.swift in Sources */,
|
||||
1A2879222733F257008EC881 /* PeopleInSpaceWidget.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1ABFB8B623AFF5CC003D807E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1AA82EED25052E1E00193051 /* ImageView.swift in Sources */,
|
||||
1AC2439025A1D57700F17D2F /* IssPositionPublisher.swift in Sources */,
|
||||
1ABFB8BE23AFF5CC003D807E /* AppDelegate.swift in Sources */,
|
||||
1ABFB8C023AFF5CC003D807E /* SceneDelegate.swift in Sources */,
|
||||
1AD2EC6A25E9984900CCEE81 /* ISSMapView.swift in Sources */,
|
||||
|
@ -352,14 +224,6 @@
|
|||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
1A2879272733F259008EC881 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 1A28791A2733F257008EC881 /* PeopleInSpaceWidgetExtension */;
|
||||
targetProxy = 1A2879262733F259008EC881 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
1ABFB8C823AFF5CE003D807E /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
|
@ -372,66 +236,6 @@
|
|||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
1A28792A2733F259008EC881 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 18A371146E3D470DBF29C172 /* Pods-PeopleInSpaceWidgetExtension.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PeopleInSpaceWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PeopleInSpaceWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 John O'Reilly. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.surrus.PeopleInSpaceSwiftUI.PeopleInSpaceWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1A28792B2733F259008EC881 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = BD4D317D12DFE46788F27B26 /* Pods-PeopleInSpaceWidgetExtension.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PeopleInSpaceWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PeopleInSpaceWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 John O'Reilly. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.surrus.PeopleInSpaceSwiftUI.PeopleInSpaceWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
1ABFB8CC23AFF5CE003D807E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -482,7 +286,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
@ -536,7 +340,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -550,7 +354,6 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E9A29AFE5FFFB564C509840A /* Pods-PeopleInSpaceSwiftUI.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PeopleInSpaceSwiftUI/Preview Content\"";
|
||||
|
@ -572,7 +375,6 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 56CD7101BBBC60F561BFB049 /* Pods-PeopleInSpaceSwiftUI.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"PeopleInSpaceSwiftUI/Preview Content\"";
|
||||
|
@ -593,15 +395,6 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
1A2879292733F259008EC881 /* Build configuration list for PBXNativeTarget "PeopleInSpaceWidgetExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
1A28792A2733F259008EC881 /* Debug */,
|
||||
1A28792B2733F259008EC881 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
1ABFB8B523AFF5CC003D807E /* Build configuration list for PBXProject "PeopleInSpaceSwiftUI" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
<key>PeopleInSpaceSwiftUI.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
<key>PeopleInSpaceWidgetExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>6</integer>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import Combine
|
||||
import common
|
||||
|
||||
|
||||
public struct IssPositionPublisher: Publisher {
|
||||
public typealias Output = IssPosition
|
||||
public typealias Failure = Never
|
||||
|
||||
private let repository: PeopleInSpaceRepository
|
||||
public init(repository: PeopleInSpaceRepository) {
|
||||
self.repository = repository
|
||||
}
|
||||
|
||||
public func receive<S: Subscriber>(subscriber: S) where S.Input == IssPosition, S.Failure == Failure {
|
||||
let subscription = IssPositionSubscription(repository: repository, subscriber: subscriber)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
final class IssPositionSubscription<S: Subscriber>: Subscription where S.Input == IssPosition, S.Failure == Failure {
|
||||
private var subscriber: S?
|
||||
private var job: Kotlinx_coroutines_coreJob? = nil
|
||||
|
||||
private let repository: PeopleInSpaceRepository
|
||||
|
||||
init(repository: PeopleInSpaceRepository, subscriber: S) {
|
||||
self.repository = repository
|
||||
self.subscriber = subscriber
|
||||
|
||||
job = repository.iosPollISSPosition().subscribe(
|
||||
scope: repository.iosScope,
|
||||
onEach: { position in
|
||||
subscriber.receive(position!)
|
||||
},
|
||||
onComplete: { subscriber.receive(completion: .finished) },
|
||||
onThrow: { error in debugPrint(error) }
|
||||
)
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
subscriber = nil
|
||||
job?.cancel(cause: nil)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {}
|
||||
}
|
||||
}
|
|
@ -1,48 +1,31 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import common
|
||||
import KMPNativeCoroutinesAsync
|
||||
|
||||
|
||||
@MainActor
|
||||
class PeopleInSpaceViewModel: ObservableObject {
|
||||
@Published var people = [Assignment]()
|
||||
@Published var issPosition = IssPosition(latitude: 0, longitude: 0)
|
||||
|
||||
private var fetchPeopleTask: Task<(), Never>? = nil
|
||||
private var pollISSPositionTask: Task<(), Never>? = nil
|
||||
private var subscription: AnyCancellable?
|
||||
|
||||
private let repository: PeopleInSpaceRepository
|
||||
init(repository: PeopleInSpaceRepository) {
|
||||
self.repository = repository
|
||||
|
||||
pollISSPositionTask = Task {
|
||||
do {
|
||||
let stream = asyncStream(for: repository.pollISSPositionNative())
|
||||
for try await data in stream {
|
||||
self.issPosition = data
|
||||
}
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
subscription = IssPositionPublisher(repository: repository)
|
||||
//.map { position in String(format: "ISS Position = (%f, %f)", position.latitude, position.longitude ) }
|
||||
.assign(to: \.issPosition, on: self)
|
||||
}
|
||||
|
||||
func startObservingPeopleUpdates() {
|
||||
fetchPeopleTask = Task {
|
||||
do {
|
||||
let stream = asyncStream(for: repository.fetchPeopleAsFlowNative())
|
||||
for try await data in stream {
|
||||
self.people = data
|
||||
}
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
repository.startObservingPeopleUpdates(success: { data in
|
||||
self.people = data
|
||||
})
|
||||
}
|
||||
|
||||
func stopObservingPeopleUpdates() {
|
||||
fetchPeopleTask?.cancel()
|
||||
repository.stopObservingPeopleUpdates()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,73 +0,0 @@
|
|||
import WidgetKit
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import common
|
||||
import KMPNativeCoroutinesCombine
|
||||
|
||||
final class Provider: TimelineProvider {
|
||||
|
||||
private let repository: PeopleInSpaceRepository
|
||||
private var timelineCancellable: AnyCancellable?
|
||||
|
||||
init() {
|
||||
KoinKt.doInitKoin()
|
||||
repository = PeopleInSpaceRepository()
|
||||
}
|
||||
|
||||
func placeholder(in context: Context) -> PeopleInSpaceListEntry {
|
||||
PeopleInSpaceListEntry(date: Date(), peopleList: [])
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (PeopleInSpaceListEntry) -> ()) {
|
||||
let entry = PeopleInSpaceListEntry(date: Date(), peopleList: [])
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
timelineCancellable = createFuture(for: repository.fetchPeopleNative())
|
||||
.map { data in
|
||||
let entry = PeopleInSpaceListEntry(date: Date(), peopleList: data)
|
||||
return Timeline(entries: [entry], policy: .atEnd)
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveCompletion: { completion in
|
||||
debugPrint(completion)
|
||||
}, receiveValue: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PeopleInSpaceListEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let peopleList: [Assignment]?
|
||||
}
|
||||
|
||||
|
||||
struct PeopleInSpaceWidgetEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if let peopleList = entry.peopleList {
|
||||
ForEach(peopleList, id:\.name) { person in
|
||||
Text("\(person.name) (\(person.craft))").font(.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct PeopleInSpaceWidget: Widget {
|
||||
let kind: String = "PeopleInSpaceWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
PeopleInSpaceWidgetEntryView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("My Widget")
|
||||
.description("This is an example widget.")
|
||||
.supportedFamilies([.systemLarge])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,5 @@
|
|||
|
||||
target 'PeopleInSpaceSwiftUI' do
|
||||
pod 'common', :path => '../../common'
|
||||
pod 'KMPNativeCoroutinesAsync', '0.11.1'
|
||||
end
|
||||
|
||||
target 'PeopleInSpaceWidgetExtension' do
|
||||
pod 'common', :path => '../../common'
|
||||
pod 'KMPNativeCoroutinesCombine', '0.11.1'
|
||||
end
|
|
@ -1,32 +1,16 @@
|
|||
PODS:
|
||||
- common (1.0)
|
||||
- KMPNativeCoroutinesAsync (0.11.1):
|
||||
- KMPNativeCoroutinesCore (= 0.11.1)
|
||||
- KMPNativeCoroutinesCombine (0.11.1):
|
||||
- KMPNativeCoroutinesCore (= 0.11.1)
|
||||
- KMPNativeCoroutinesCore (0.11.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- common (from `../../common`)
|
||||
- KMPNativeCoroutinesAsync (= 0.11.1)
|
||||
- KMPNativeCoroutinesCombine (= 0.11.1)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- KMPNativeCoroutinesAsync
|
||||
- KMPNativeCoroutinesCombine
|
||||
- KMPNativeCoroutinesCore
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
common:
|
||||
:path: "../../common"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
common: 5def32d6e7131f79a49997cca9bbcc9cbd11e08a
|
||||
KMPNativeCoroutinesAsync: 1e6e09efe1fb04a9412483680829dbd35b55ef18
|
||||
KMPNativeCoroutinesCombine: 43a442a5e50ee8fcbb8633a361d12907fe933920
|
||||
KMPNativeCoroutinesCore: ed98a12d8337f861088f6b79636ffb29ff9bb2ad
|
||||
common: ed5a58383c5a02f46882243487c7aac341a3064f
|
||||
|
||||
PODFILE CHECKSUM: d51c9a2ba0fb9109f719acf8878fb54882154ace
|
||||
PODFILE CHECKSUM: f7c2bab6802efae18b223d30cb88e54a074a5f69
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.9.3
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../../../Target Support Files/KMPNativeCoroutinesAsync/KMPNativeCoroutinesAsync-umbrella.h
|
|
@ -1 +0,0 @@
|
|||
../../../Target Support Files/KMPNativeCoroutinesAsync/KMPNativeCoroutinesAsync.modulemap
|
|
@ -1 +0,0 @@
|
|||
../../../Target Support Files/KMPNativeCoroutinesCombine/KMPNativeCoroutinesCombine-umbrella.h
|
|
@ -1 +0,0 @@
|
|||
../../../Target Support Files/KMPNativeCoroutinesCombine/KMPNativeCoroutinesCombine.modulemap
|
|
@ -1 +0,0 @@
|
|||
../../../Target Support Files/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore-umbrella.h
|
|
@ -1 +0,0 @@
|
|||
../../../Target Support Files/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap
|
|
@ -1,69 +0,0 @@
|
|||
//
|
||||
// AsyncFunction.swift
|
||||
// KMPNativeCoroutinesAsync
|
||||
//
|
||||
// Created by Rick Clephas on 13/06/2021.
|
||||
//
|
||||
|
||||
import KMPNativeCoroutinesCore
|
||||
|
||||
/// Wraps the `NativeSuspend` in an async function.
|
||||
/// - Parameter nativeSuspend: The native suspend function to await.
|
||||
/// - Returns: The result from the `nativeSuspend`.
|
||||
/// - Throws: Errors thrown by the `nativeSuspend`.
|
||||
public func asyncFunction<Result, Failure: Error, Unit>(for nativeSuspend: @escaping NativeSuspend<Result, Failure, Unit>) async throws -> Result {
|
||||
let asyncFunctionActor = AsyncFunctionActor<Result, Unit>()
|
||||
return try await withTaskCancellationHandler {
|
||||
Task { await asyncFunctionActor.cancel() }
|
||||
} operation: {
|
||||
try await withUnsafeThrowingContinuation { continuation in
|
||||
Task {
|
||||
await asyncFunctionActor.setContinuation(continuation)
|
||||
let nativeCancellable = nativeSuspend({ output, unit in
|
||||
Task { await asyncFunctionActor.continueWith(result: output) }
|
||||
return unit
|
||||
}, { error, unit in
|
||||
Task { await asyncFunctionActor.continueWith(error: error) }
|
||||
return unit
|
||||
})
|
||||
await asyncFunctionActor.setNativeCancellable(nativeCancellable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal actor AsyncFunctionActor<Result, Unit> {
|
||||
|
||||
private var isCancelled = false
|
||||
private var nativeCancellable: NativeCancellable<Unit>? = nil
|
||||
|
||||
func setNativeCancellable(_ nativeCancellable: @escaping NativeCancellable<Unit>) {
|
||||
guard !isCancelled else {
|
||||
_ = nativeCancellable()
|
||||
return
|
||||
}
|
||||
self.nativeCancellable = nativeCancellable
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
isCancelled = true
|
||||
_ = nativeCancellable?()
|
||||
nativeCancellable = nil
|
||||
}
|
||||
|
||||
private var continuation: UnsafeContinuation<Result, Error>? = nil
|
||||
|
||||
func setContinuation(_ continuation: UnsafeContinuation<Result, Error>) {
|
||||
self.continuation = continuation
|
||||
}
|
||||
|
||||
func continueWith(result: Result) {
|
||||
continuation?.resume(returning: result)
|
||||
continuation = nil
|
||||
}
|
||||
|
||||
func continueWith(error: Error) {
|
||||
continuation?.resume(throwing: error)
|
||||
continuation = nil
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// AsyncResult.swift
|
||||
// KMPNativeCoroutinesAsync
|
||||
//
|
||||
// Created by Rick Clephas on 28/06/2021.
|
||||
//
|
||||
|
||||
import KMPNativeCoroutinesCore
|
||||
|
||||
/// Awaits the `NativeSuspend` and returns the result.
|
||||
/// - Parameter nativeSuspend: The native suspend function to await.
|
||||
/// - Returns: The `Result` from the `nativeSuspend`.
|
||||
public func asyncResult<Output, Failure: Error, Unit>(for nativeSuspend: @escaping NativeSuspend<Output, Failure, Unit>) async -> Result<Output, Error> {
|
||||
do {
|
||||
return .success(try await asyncFunction(for: nativeSuspend))
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// AsyncStream.swift
|
||||
// AsyncStream
|
||||
//
|
||||
// Created by Rick Clephas on 15/07/2021.
|
||||
//
|
||||
|
||||
import KMPNativeCoroutinesCore
|
||||
|
||||
/// Wraps the `NativeFlow` in an `AsyncThrowingStream`.
|
||||
/// - Parameter nativeFlow: The native flow to collect.
|
||||
/// - Returns: An stream that yields the collected values.
|
||||
public func asyncStream<Output, Failure: Error, Unit>(for nativeFlow: @escaping NativeFlow<Output, Failure, Unit>) -> AsyncThrowingStream<Output, Error> {
|
||||
let asyncStreamActor = AsyncStreamActor<Output, Unit>()
|
||||
return AsyncThrowingStream { continuation in
|
||||
continuation.onTermination = { @Sendable _ in
|
||||
Task { await asyncStreamActor.cancel() }
|
||||
}
|
||||
Task {
|
||||
let nativeCancellable = nativeFlow({ item, unit in
|
||||
continuation.yield(item)
|
||||
return unit
|
||||
}, { error, unit in
|
||||
if let error = error {
|
||||
continuation.finish(throwing: error)
|
||||
} else {
|
||||
continuation.finish(throwing: nil)
|
||||
}
|
||||
return unit
|
||||
})
|
||||
await asyncStreamActor.setNativeCancellable(nativeCancellable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal actor AsyncStreamActor<Output, Unit> {
|
||||
|
||||
private var isCancelled = false
|
||||
private var nativeCancellable: NativeCancellable<Unit>? = nil
|
||||
|
||||
func setNativeCancellable(_ nativeCancellable: @escaping NativeCancellable<Unit>) {
|
||||
guard !isCancelled else {
|
||||
_ = nativeCancellable()
|
||||
return
|
||||
}
|
||||
self.nativeCancellable = nativeCancellable
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
isCancelled = true
|
||||
_ = nativeCancellable?()
|
||||
nativeCancellable = nil
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Rick Clephas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,320 +0,0 @@
|
|||
# KMP-NativeCoroutines
|
||||
|
||||
A library to use Kotlin Coroutines from Swift code in KMP apps.
|
||||
|
||||
## Why this library?
|
||||
|
||||
Both KMP and Kotlin Coroutines are amazing but together they have some limitations.
|
||||
|
||||
The most important limitation is cancellation support.
|
||||
Kotlin suspend functions are exposed to Swift as functions with a completion handler.
|
||||
This allows you to easily use them from your Swift code, but it doesn't support cancellation.
|
||||
|
||||
> While Swift 5.5 brings async functions to Swift, it doesn't solve this issue.
|
||||
> For interoperability with ObjC all functions with a completion handler can be called like an async function.
|
||||
> This means starting with Swift 5.5 your Kotlin suspend functions will look like Swift async functions.
|
||||
> But that's just syntactic sugar, so there's still no cancellation support.
|
||||
|
||||
Besides cancellation support, ObjC doesn't support generics on protocols.
|
||||
So all the `Flow` interfaces lose their generic value type which make them hard to use.
|
||||
|
||||
This library solves both of these limitations 😄.
|
||||
|
||||
## 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`.
|
||||
Compatibility versions for older Kotlin versions are also available:
|
||||
|
||||
| Version | Version suffix | Kotlin | Coroutines |
|
||||
|--------------|-----------------|:----------:|:-------------------:|
|
||||
| _latest_ | -new-mm | 1.6.10 | 1.6.0 |
|
||||
| **_latest_** | **_no suffix_** | **1.6.10** | **1.6.0-native-mt** |
|
||||
| _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.6.0-native-mt |
|
||||
| 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 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt |
|
||||
|
||||
You can choose from a couple of Swift implementations.
|
||||
Depending on the implementation you can support as low as iOS 9, macOS 10.9, tvOS 9 and watchOS 3:
|
||||
|
||||
| Implementation | Swift | iOS | macOS | tvOS | watchOS |
|
||||
|----------------|:-----:|:----:|:-----:|:----:|:-------:|
|
||||
| Async | 5.5 | 13.0 | 10.15 | 13.0 | 6.0 |
|
||||
| Combine | 5.0 | 13.0 | 10.15 | 13.0 | 6.0 |
|
||||
| RxSwift | 5.0 | 9.0 | 10.9 | 9.0 | 3.0 |
|
||||
|
||||
## Installation
|
||||
|
||||
The library consists of a Kotlin and Swift part which you'll need to add to your project.
|
||||
The Kotlin part is available on Maven Central and the Swift part can be installed via CocoaPods
|
||||
or the Swift Package Manager.
|
||||
|
||||
Make sure to always use the same versions for all the libraries!
|
||||
|
||||
[![latest release](https://img.shields.io/github/v/release/rickclephas/KMP-NativeCoroutines?label=latest%20release&sort=semver)](https://github.com/rickclephas/KMP-NativeCoroutines/releases)
|
||||
|
||||
### Kotlin
|
||||
|
||||
For Kotlin just add the plugin to your `build.gradle.kts`:
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.rickclephas.kmp.nativecoroutines") version "<version>"
|
||||
}
|
||||
```
|
||||
|
||||
### Swift (Swift Package Manager)
|
||||
|
||||
All Swift implementations are also available via the Swift Package Manager.
|
||||
Just add it to your `Package.swift` file:
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/rickclephas/KMP-NativeCoroutines.git", from: "<version>")
|
||||
]
|
||||
```
|
||||
|
||||
Or add it in Xcode by going to `File` > `Add Packages...` and providing the URL:
|
||||
`https://github.com/rickclephas/KMP-NativeCoroutines.git`.
|
||||
|
||||
### Swift (CocoaPods)
|
||||
|
||||
Now for Swift you can choose from a couple of implementations.
|
||||
Add one or more of the following libraries to your `Podfile`:
|
||||
```ruby
|
||||
pod 'KMPNativeCoroutinesAsync' # Swift 5.5 Async/Await implementation
|
||||
pod 'KMPNativeCoroutinesCombine' # Combine implementation
|
||||
pod 'KMPNativeCoroutinesRxSwift' # RxSwift implementation
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Using your Kotlin Coroutines code from Swift is almost as easy as calling the Kotlin code.
|
||||
Just use the wrapper functions in Swift to get Observables, Publishers, AsyncStreams or async functions.
|
||||
|
||||
### Kotlin
|
||||
|
||||
The plugin will automagically generate the necessary code for you! 🔮
|
||||
|
||||
Your `Flow` properties/functions get a `Native` version:
|
||||
```kotlin
|
||||
class Clock {
|
||||
// Somewhere in your Kotlin code you define a Flow property
|
||||
val time: StateFlow<Long> // This can be any kind of Flow
|
||||
|
||||
// The plugin will generate this native property for you
|
||||
val timeNative
|
||||
get() = time.asNativeFlow()
|
||||
}
|
||||
```
|
||||
|
||||
In case of a `StateFlow` or `SharedFlow` property you also get a `NativeValue` or `NativeReplayCache` property:
|
||||
```kotlin
|
||||
// For the StateFlow defined above the plugin will generate this native value property
|
||||
val timeNativeValue
|
||||
get() = time.value
|
||||
|
||||
// In case of a SharedFlow the plugin would generate this native replay cache property
|
||||
val timeNativeReplayCache
|
||||
get() = time.replayCache
|
||||
```
|
||||
|
||||
The plugin also generates `Native` versions for all your suspend functions:
|
||||
```kotlin
|
||||
class RandomLettersGenerator {
|
||||
// Somewhere in your Kotlin code you define a suspend function
|
||||
suspend fun getRandomLetters(): String {
|
||||
// Code to generate some random letters
|
||||
}
|
||||
|
||||
// The plugin will generate this native function for you
|
||||
fun getRandomLettersNative() =
|
||||
nativeSuspend { getRandomLetters() }
|
||||
}
|
||||
```
|
||||
|
||||
#### Global properties and functions
|
||||
|
||||
The plugin is currently unable to generate native versions for global properties and functions.
|
||||
In such cases you have to manually create the native versions in your Kotlin native code.
|
||||
|
||||
#### Custom suffix
|
||||
|
||||
If you don't like the naming of these generated properties/functions, you can easily change the suffix.
|
||||
For example add the following to your `build.gradle.kts` to use the suffix `Apple`:
|
||||
```kotlin
|
||||
nativeCoroutines {
|
||||
suffix = "Apple"
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom CoroutineScope
|
||||
|
||||
For more control you can provide a custom `CoroutineScope` with the `NativeCoroutineScope` annotation:
|
||||
```kotlin
|
||||
class Clock {
|
||||
@NativeCoroutineScope
|
||||
internal val coroutineScope = CoroutineScope(job + Dispatchers.Default)
|
||||
}
|
||||
```
|
||||
|
||||
If you don't provide a `CoroutineScope` the default scope will be used which is defined as:
|
||||
```kotlin
|
||||
@SharedImmutable
|
||||
internal val defaultCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
```
|
||||
|
||||
#### Ignoring declarations
|
||||
|
||||
Use the `NativeCoroutinesIgnore` annotation to tell the plugin to ignore a property or function:
|
||||
```kotlin
|
||||
@NativeCoroutinesIgnore
|
||||
val ignoredFlowProperty: Flow<Int>
|
||||
|
||||
@NativeCoroutinesIgnore
|
||||
suspend fun ignoredSuspendFunction() { }
|
||||
```
|
||||
|
||||
### Swift 5.5 Async/Await
|
||||
|
||||
The Async implementation provides some functions to get async Swift functions and `AsyncStream`s.
|
||||
|
||||
Use the `asyncFunction(for:)` function to get an async function that can be awaited:
|
||||
```swift
|
||||
let handle = Task {
|
||||
do {
|
||||
let letters = try await asyncFunction(for: randomLettersGenerator.getRandomLettersNative())
|
||||
print("Got random letters: \(letters)")
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// To cancel the suspend function just cancel the async task
|
||||
handle.cancel()
|
||||
```
|
||||
|
||||
or if you don't like these do-catches you can use the `asyncResult(for:)` function:
|
||||
```swift
|
||||
let result = await asyncResult(for: randomLettersGenerator.getRandomLettersNative())
|
||||
if case let .success(letters) = result {
|
||||
print("Got random letters: \(letters)")
|
||||
}
|
||||
```
|
||||
|
||||
For `Flow`s there is the `asyncStream(for:)` function to get an `AsyncStream`:
|
||||
```swift
|
||||
let handle = Task {
|
||||
do {
|
||||
let stream = asyncStream(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
for try await letters in stream {
|
||||
print("Got random letters: \(letters)")
|
||||
}
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// To cancel the flow (collection) just cancel the async task
|
||||
handle.cancel()
|
||||
```
|
||||
|
||||
### Combine
|
||||
|
||||
The Combine implementation provides a couple functions to get an `AnyPublisher` for your Coroutines code.
|
||||
|
||||
For your `Flow`s use the `createPublisher(for:)` function:
|
||||
```swift
|
||||
// Create an AnyPublisher for your flow
|
||||
let publisher = createPublisher(for: clock.timeNative)
|
||||
|
||||
// Now use this publisher as you would any other
|
||||
let cancellable = publisher.sink { completion in
|
||||
print("Received completion: \(completion)")
|
||||
} receiveValue: { value in
|
||||
print("Received value: \(value)")
|
||||
}
|
||||
|
||||
// To cancel the flow (collection) just cancel the publisher
|
||||
cancellable.cancel()
|
||||
```
|
||||
|
||||
For the suspend functions you should use the `createFuture(for:)` function:
|
||||
```swift
|
||||
// Create a Future/AnyPublisher for the suspend function
|
||||
let future = createFuture(for: randomLettersGenerator.getRandomLettersNative())
|
||||
|
||||
// Now use this future as you would any other
|
||||
let cancellable = future.sink { completion in
|
||||
print("Received completion: \(completion)")
|
||||
} receiveValue: { value in
|
||||
print("Received value: \(value)")
|
||||
}
|
||||
|
||||
// To cancel the suspend function just cancel the future
|
||||
cancellable.cancel()
|
||||
```
|
||||
|
||||
You can also use the `createPublisher(for:)` function for suspend functions that return a `Flow`:
|
||||
```swift
|
||||
let publisher = createPublisher(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
```
|
||||
|
||||
**Note:** these functions create deferred `AnyPublisher`s.
|
||||
Meaning every subscription will trigger the collection of the `Flow` or execution of the suspend function.
|
||||
|
||||
### RxSwift
|
||||
|
||||
The RxSwift implementation provides a couple functions to get an `Observable` or `Single` for your Coroutines code.
|
||||
|
||||
For your `Flow`s use the `createObservable(for:)` function:
|
||||
```swift
|
||||
// Create an observable for your flow
|
||||
let observable = createObservable(for: clock.timeNative)
|
||||
|
||||
// Now use this observable as you would any other
|
||||
let disposable = observable.subscribe(onNext: { value in
|
||||
print("Received value: \(value)")
|
||||
}, onError: { error in
|
||||
print("Received error: \(error)")
|
||||
}, onCompleted: {
|
||||
print("Observable completed")
|
||||
}, onDisposed: {
|
||||
print("Observable disposed")
|
||||
})
|
||||
|
||||
// To cancel the flow (collection) just dispose the subscription
|
||||
disposable.dispose()
|
||||
```
|
||||
|
||||
For the suspend functions you should use the `createSingle(for:)` function:
|
||||
```swift
|
||||
// Create a single for the suspend function
|
||||
let single = createSingle(for: randomLettersGenerator.getRandomLettersNative())
|
||||
|
||||
// Now use this single as you would any other
|
||||
let disposable = single.subscribe(onSuccess: { value in
|
||||
print("Received value: \(value)")
|
||||
}, onFailure: { error in
|
||||
print("Received error: \(error)")
|
||||
}, onDisposed: {
|
||||
print("Single disposed")
|
||||
})
|
||||
|
||||
// To cancel the suspend function just dispose the subscription
|
||||
disposable.dispose()
|
||||
```
|
||||
|
||||
You can also use the `createObservable(for:)` function for suspend functions that return a `Flow`:
|
||||
```swift
|
||||
let observable = createObservable(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
```
|
||||
|
||||
**Note:** these functions create deferred `Observable`s and `Single`s.
|
||||
Meaning every subscription will trigger the collection of the `Flow` or execution of the suspend function.
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// Future.swift
|
||||
// KMPNativeCoroutinesCombine
|
||||
//
|
||||
// Created by Rick Clephas on 06/06/2021.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import KMPNativeCoroutinesCore
|
||||
|
||||
/// Creates an `AnyPublisher` for the provided `NativeSuspend`.
|
||||
/// - Parameter nativeSuspend: The native suspend function to await.
|
||||
/// - Returns: A publisher that either finishes with a single value or fails with an error.
|
||||
public func createFuture<Result, Failure: Error, Unit>(
|
||||
for nativeSuspend: @escaping NativeSuspend<Result, Failure, Unit>
|
||||
) -> AnyPublisher<Result, Failure> {
|
||||
return NativeSuspendFuture(nativeSuspend: nativeSuspend)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
internal struct NativeSuspendFuture<Result, Failure: Error, Unit>: Publisher {
|
||||
|
||||
typealias Output = Result
|
||||
typealias Failure = Failure
|
||||
|
||||
let nativeSuspend: NativeSuspend<Result, Failure, Unit>
|
||||
|
||||
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Result == S.Input {
|
||||
let subscription = NativeSuspendSubscription(nativeSuspend: nativeSuspend, subscriber: subscriber)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeSuspendSubscription<Result, Failure, Unit, S: Subscriber>: Subscription where S.Input == Result, S.Failure == Failure {
|
||||
|
||||
private var nativeCancellable: NativeCancellable<Unit>? = nil
|
||||
private var subscriber: S?
|
||||
|
||||
init(nativeSuspend: NativeSuspend<Result, Failure, Unit>, subscriber: S) {
|
||||
self.subscriber = subscriber
|
||||
nativeCancellable = nativeSuspend({ output, unit in
|
||||
if let subscriber = self.subscriber {
|
||||
_ = subscriber.receive(output)
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
return unit
|
||||
}, { error, unit in
|
||||
self.subscriber?.receive(completion: .failure(error))
|
||||
return unit
|
||||
})
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) { }
|
||||
|
||||
func cancel() {
|
||||
subscriber = nil
|
||||
_ = nativeCancellable?()
|
||||
nativeCancellable = nil
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// FuturePublisher.swift
|
||||
// KMPNativeCoroutinesCombine
|
||||
//
|
||||
// Created by Rick Clephas on 28/06/2021.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import KMPNativeCoroutinesCore
|
||||
|
||||
/// Creates an `AnyPublisher` for the provided `NativeSuspend` that returns a `NativeFlow`.
|
||||
/// - Parameter nativeSuspend: The native suspend function to await.
|
||||
/// - Returns: A publisher that publishes the collected values.
|
||||
public func createPublisher<Output, Failure: Error, Unit>(
|
||||
for nativeSuspend: @escaping NativeSuspend<NativeFlow<Output, Failure, Unit>, Failure, Unit>
|
||||
) -> AnyPublisher<Output, Failure> {
|
||||
return createFuture(for: nativeSuspend)
|
||||
.flatMap { createPublisher(for: $0) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
//
|
||||
// Publisher.swift
|
||||
// KMPNativeCoroutinesCombine
|
||||
//
|
||||
// Created by Rick Clephas on 06/06/2021.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import KMPNativeCoroutinesCore
|
||||
|
||||
/// Creates an `AnyPublisher` for the provided `NativeFlow`.
|
||||
/// - Parameter nativeFlow: The native flow to collect.
|
||||
/// - Returns: A publisher that publishes the collected values.
|
||||
public func createPublisher<Output, Failure: Error, Unit>(
|
||||
for nativeFlow: @escaping NativeFlow<Output, Failure, Unit>
|
||||
) -> AnyPublisher<Output, Failure> {
|
||||
return NativeFlowPublisher(nativeFlow: nativeFlow)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
internal struct NativeFlowPublisher<Output, Failure: Error, Unit>: Publisher {
|
||||
|
||||
typealias Output = Output
|
||||
typealias Failure = Failure
|
||||
|
||||
let nativeFlow: NativeFlow<Output, Failure, Unit>
|
||||
|
||||
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
|
||||
let subscription = NativeFlowSubscription(nativeFlow: nativeFlow, subscriber: subscriber)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeFlowSubscription<Output, Failure, Unit, S: Subscriber>: Subscription where S.Input == Output, S.Failure == Failure {
|
||||
|
||||
private var nativeCancellable: NativeCancellable<Unit>? = nil
|
||||
private var subscriber: S?
|
||||
|
||||
init(nativeFlow: NativeFlow<Output, Failure, Unit>, subscriber: S) {
|
||||
self.subscriber = subscriber
|
||||
nativeCancellable = nativeFlow({ item, unit in
|
||||
_ = self.subscriber?.receive(item)
|
||||
return unit
|
||||
}, { error, unit in
|
||||
if let error = error {
|
||||
self.subscriber?.receive(completion: .failure(error))
|
||||
} else {
|
||||
self.subscriber?.receive(completion: .finished)
|
||||
}
|
||||
return unit
|
||||
})
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) { }
|
||||
|
||||
func cancel() {
|
||||
subscriber = nil
|
||||
_ = nativeCancellable?()
|
||||
nativeCancellable = nil
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Rick Clephas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,320 +0,0 @@
|
|||
# KMP-NativeCoroutines
|
||||
|
||||
A library to use Kotlin Coroutines from Swift code in KMP apps.
|
||||
|
||||
## Why this library?
|
||||
|
||||
Both KMP and Kotlin Coroutines are amazing but together they have some limitations.
|
||||
|
||||
The most important limitation is cancellation support.
|
||||
Kotlin suspend functions are exposed to Swift as functions with a completion handler.
|
||||
This allows you to easily use them from your Swift code, but it doesn't support cancellation.
|
||||
|
||||
> While Swift 5.5 brings async functions to Swift, it doesn't solve this issue.
|
||||
> For interoperability with ObjC all functions with a completion handler can be called like an async function.
|
||||
> This means starting with Swift 5.5 your Kotlin suspend functions will look like Swift async functions.
|
||||
> But that's just syntactic sugar, so there's still no cancellation support.
|
||||
|
||||
Besides cancellation support, ObjC doesn't support generics on protocols.
|
||||
So all the `Flow` interfaces lose their generic value type which make them hard to use.
|
||||
|
||||
This library solves both of these limitations 😄.
|
||||
|
||||
## 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`.
|
||||
Compatibility versions for older Kotlin versions are also available:
|
||||
|
||||
| Version | Version suffix | Kotlin | Coroutines |
|
||||
|--------------|-----------------|:----------:|:-------------------:|
|
||||
| _latest_ | -new-mm | 1.6.10 | 1.6.0 |
|
||||
| **_latest_** | **_no suffix_** | **1.6.10** | **1.6.0-native-mt** |
|
||||
| _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.6.0-native-mt |
|
||||
| 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 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt |
|
||||
|
||||
You can choose from a couple of Swift implementations.
|
||||
Depending on the implementation you can support as low as iOS 9, macOS 10.9, tvOS 9 and watchOS 3:
|
||||
|
||||
| Implementation | Swift | iOS | macOS | tvOS | watchOS |
|
||||
|----------------|:-----:|:----:|:-----:|:----:|:-------:|
|
||||
| Async | 5.5 | 13.0 | 10.15 | 13.0 | 6.0 |
|
||||
| Combine | 5.0 | 13.0 | 10.15 | 13.0 | 6.0 |
|
||||
| RxSwift | 5.0 | 9.0 | 10.9 | 9.0 | 3.0 |
|
||||
|
||||
## Installation
|
||||
|
||||
The library consists of a Kotlin and Swift part which you'll need to add to your project.
|
||||
The Kotlin part is available on Maven Central and the Swift part can be installed via CocoaPods
|
||||
or the Swift Package Manager.
|
||||
|
||||
Make sure to always use the same versions for all the libraries!
|
||||
|
||||
[![latest release](https://img.shields.io/github/v/release/rickclephas/KMP-NativeCoroutines?label=latest%20release&sort=semver)](https://github.com/rickclephas/KMP-NativeCoroutines/releases)
|
||||
|
||||
### Kotlin
|
||||
|
||||
For Kotlin just add the plugin to your `build.gradle.kts`:
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.rickclephas.kmp.nativecoroutines") version "<version>"
|
||||
}
|
||||
```
|
||||
|
||||
### Swift (Swift Package Manager)
|
||||
|
||||
All Swift implementations are also available via the Swift Package Manager.
|
||||
Just add it to your `Package.swift` file:
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/rickclephas/KMP-NativeCoroutines.git", from: "<version>")
|
||||
]
|
||||
```
|
||||
|
||||
Or add it in Xcode by going to `File` > `Add Packages...` and providing the URL:
|
||||
`https://github.com/rickclephas/KMP-NativeCoroutines.git`.
|
||||
|
||||
### Swift (CocoaPods)
|
||||
|
||||
Now for Swift you can choose from a couple of implementations.
|
||||
Add one or more of the following libraries to your `Podfile`:
|
||||
```ruby
|
||||
pod 'KMPNativeCoroutinesAsync' # Swift 5.5 Async/Await implementation
|
||||
pod 'KMPNativeCoroutinesCombine' # Combine implementation
|
||||
pod 'KMPNativeCoroutinesRxSwift' # RxSwift implementation
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Using your Kotlin Coroutines code from Swift is almost as easy as calling the Kotlin code.
|
||||
Just use the wrapper functions in Swift to get Observables, Publishers, AsyncStreams or async functions.
|
||||
|
||||
### Kotlin
|
||||
|
||||
The plugin will automagically generate the necessary code for you! 🔮
|
||||
|
||||
Your `Flow` properties/functions get a `Native` version:
|
||||
```kotlin
|
||||
class Clock {
|
||||
// Somewhere in your Kotlin code you define a Flow property
|
||||
val time: StateFlow<Long> // This can be any kind of Flow
|
||||
|
||||
// The plugin will generate this native property for you
|
||||
val timeNative
|
||||
get() = time.asNativeFlow()
|
||||
}
|
||||
```
|
||||
|
||||
In case of a `StateFlow` or `SharedFlow` property you also get a `NativeValue` or `NativeReplayCache` property:
|
||||
```kotlin
|
||||
// For the StateFlow defined above the plugin will generate this native value property
|
||||
val timeNativeValue
|
||||
get() = time.value
|
||||
|
||||
// In case of a SharedFlow the plugin would generate this native replay cache property
|
||||
val timeNativeReplayCache
|
||||
get() = time.replayCache
|
||||
```
|
||||
|
||||
The plugin also generates `Native` versions for all your suspend functions:
|
||||
```kotlin
|
||||
class RandomLettersGenerator {
|
||||
// Somewhere in your Kotlin code you define a suspend function
|
||||
suspend fun getRandomLetters(): String {
|
||||
// Code to generate some random letters
|
||||
}
|
||||
|
||||
// The plugin will generate this native function for you
|
||||
fun getRandomLettersNative() =
|
||||
nativeSuspend { getRandomLetters() }
|
||||
}
|
||||
```
|
||||
|
||||
#### Global properties and functions
|
||||
|
||||
The plugin is currently unable to generate native versions for global properties and functions.
|
||||
In such cases you have to manually create the native versions in your Kotlin native code.
|
||||
|
||||
#### Custom suffix
|
||||
|
||||
If you don't like the naming of these generated properties/functions, you can easily change the suffix.
|
||||
For example add the following to your `build.gradle.kts` to use the suffix `Apple`:
|
||||
```kotlin
|
||||
nativeCoroutines {
|
||||
suffix = "Apple"
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom CoroutineScope
|
||||
|
||||
For more control you can provide a custom `CoroutineScope` with the `NativeCoroutineScope` annotation:
|
||||
```kotlin
|
||||
class Clock {
|
||||
@NativeCoroutineScope
|
||||
internal val coroutineScope = CoroutineScope(job + Dispatchers.Default)
|
||||
}
|
||||
```
|
||||
|
||||
If you don't provide a `CoroutineScope` the default scope will be used which is defined as:
|
||||
```kotlin
|
||||
@SharedImmutable
|
||||
internal val defaultCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
```
|
||||
|
||||
#### Ignoring declarations
|
||||
|
||||
Use the `NativeCoroutinesIgnore` annotation to tell the plugin to ignore a property or function:
|
||||
```kotlin
|
||||
@NativeCoroutinesIgnore
|
||||
val ignoredFlowProperty: Flow<Int>
|
||||
|
||||
@NativeCoroutinesIgnore
|
||||
suspend fun ignoredSuspendFunction() { }
|
||||
```
|
||||
|
||||
### Swift 5.5 Async/Await
|
||||
|
||||
The Async implementation provides some functions to get async Swift functions and `AsyncStream`s.
|
||||
|
||||
Use the `asyncFunction(for:)` function to get an async function that can be awaited:
|
||||
```swift
|
||||
let handle = Task {
|
||||
do {
|
||||
let letters = try await asyncFunction(for: randomLettersGenerator.getRandomLettersNative())
|
||||
print("Got random letters: \(letters)")
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// To cancel the suspend function just cancel the async task
|
||||
handle.cancel()
|
||||
```
|
||||
|
||||
or if you don't like these do-catches you can use the `asyncResult(for:)` function:
|
||||
```swift
|
||||
let result = await asyncResult(for: randomLettersGenerator.getRandomLettersNative())
|
||||
if case let .success(letters) = result {
|
||||
print("Got random letters: \(letters)")
|
||||
}
|
||||
```
|
||||
|
||||
For `Flow`s there is the `asyncStream(for:)` function to get an `AsyncStream`:
|
||||
```swift
|
||||
let handle = Task {
|
||||
do {
|
||||
let stream = asyncStream(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
for try await letters in stream {
|
||||
print("Got random letters: \(letters)")
|
||||
}
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// To cancel the flow (collection) just cancel the async task
|
||||
handle.cancel()
|
||||
```
|
||||
|
||||
### Combine
|
||||
|
||||
The Combine implementation provides a couple functions to get an `AnyPublisher` for your Coroutines code.
|
||||
|
||||
For your `Flow`s use the `createPublisher(for:)` function:
|
||||
```swift
|
||||
// Create an AnyPublisher for your flow
|
||||
let publisher = createPublisher(for: clock.timeNative)
|
||||
|
||||
// Now use this publisher as you would any other
|
||||
let cancellable = publisher.sink { completion in
|
||||
print("Received completion: \(completion)")
|
||||
} receiveValue: { value in
|
||||
print("Received value: \(value)")
|
||||
}
|
||||
|
||||
// To cancel the flow (collection) just cancel the publisher
|
||||
cancellable.cancel()
|
||||
```
|
||||
|
||||
For the suspend functions you should use the `createFuture(for:)` function:
|
||||
```swift
|
||||
// Create a Future/AnyPublisher for the suspend function
|
||||
let future = createFuture(for: randomLettersGenerator.getRandomLettersNative())
|
||||
|
||||
// Now use this future as you would any other
|
||||
let cancellable = future.sink { completion in
|
||||
print("Received completion: \(completion)")
|
||||
} receiveValue: { value in
|
||||
print("Received value: \(value)")
|
||||
}
|
||||
|
||||
// To cancel the suspend function just cancel the future
|
||||
cancellable.cancel()
|
||||
```
|
||||
|
||||
You can also use the `createPublisher(for:)` function for suspend functions that return a `Flow`:
|
||||
```swift
|
||||
let publisher = createPublisher(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
```
|
||||
|
||||
**Note:** these functions create deferred `AnyPublisher`s.
|
||||
Meaning every subscription will trigger the collection of the `Flow` or execution of the suspend function.
|
||||
|
||||
### RxSwift
|
||||
|
||||
The RxSwift implementation provides a couple functions to get an `Observable` or `Single` for your Coroutines code.
|
||||
|
||||
For your `Flow`s use the `createObservable(for:)` function:
|
||||
```swift
|
||||
// Create an observable for your flow
|
||||
let observable = createObservable(for: clock.timeNative)
|
||||
|
||||
// Now use this observable as you would any other
|
||||
let disposable = observable.subscribe(onNext: { value in
|
||||
print("Received value: \(value)")
|
||||
}, onError: { error in
|
||||
print("Received error: \(error)")
|
||||
}, onCompleted: {
|
||||
print("Observable completed")
|
||||
}, onDisposed: {
|
||||
print("Observable disposed")
|
||||
})
|
||||
|
||||
// To cancel the flow (collection) just dispose the subscription
|
||||
disposable.dispose()
|
||||
```
|
||||
|
||||
For the suspend functions you should use the `createSingle(for:)` function:
|
||||
```swift
|
||||
// Create a single for the suspend function
|
||||
let single = createSingle(for: randomLettersGenerator.getRandomLettersNative())
|
||||
|
||||
// Now use this single as you would any other
|
||||
let disposable = single.subscribe(onSuccess: { value in
|
||||
print("Received value: \(value)")
|
||||
}, onFailure: { error in
|
||||
print("Received error: \(error)")
|
||||
}, onDisposed: {
|
||||
print("Single disposed")
|
||||
})
|
||||
|
||||
// To cancel the suspend function just dispose the subscription
|
||||
disposable.dispose()
|
||||
```
|
||||
|
||||
You can also use the `createObservable(for:)` function for suspend functions that return a `Flow`:
|
||||
```swift
|
||||
let observable = createObservable(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
```
|
||||
|
||||
**Note:** these functions create deferred `Observable`s and `Single`s.
|
||||
Meaning every subscription will trigger the collection of the `Flow` or execution of the suspend function.
|
|
@ -1,12 +0,0 @@
|
|||
//
|
||||
// NativeCallback.swift
|
||||
// KMPNativeCoroutinesCore
|
||||
//
|
||||
// Created by Rick Clephas on 06/06/2021.
|
||||
//
|
||||
|
||||
/// A callback with a single argument.
|
||||
///
|
||||
/// The return value is provided as the second argument.
|
||||
/// This way Swift doesn't known what it is/how to get it.
|
||||
public typealias NativeCallback<T, Unit> = (T, Unit) -> Unit
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// NativeCancellable.swift
|
||||
// KMPNativeCoroutinesCore
|
||||
//
|
||||
// Created by Rick Clephas on 06/06/2021.
|
||||
//
|
||||
|
||||
/// A function that cancels the coroutines job.
|
||||
public typealias NativeCancellable<Unit> = () -> Unit
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// NativeFlow.swift
|
||||
// KMPNativeCoroutinesCore
|
||||
//
|
||||
// Created by Rick Clephas on 06/06/2021.
|
||||
//
|
||||
|
||||
/// A function that collects a Kotlin coroutines Flow via callbacks.
|
||||
///
|
||||
/// The function takes an `onItem` and `onComplete` callback
|
||||
/// and returns a cancellable that can be used to cancel the collection.
|
||||
public typealias NativeFlow<Output, Failure: Error, Unit> = (
|
||||
_ onItem: @escaping NativeCallback<Output, Unit>,
|
||||
_ onComplete: @escaping NativeCallback<Failure?, Unit>
|
||||
) -> NativeCancellable<Unit>
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// NativeSuspend.swift
|
||||
// KMPNativeCoroutinesCore
|
||||
//
|
||||
// Created by Rick Clephas on 06/06/2021.
|
||||
//
|
||||
|
||||
/// A function that awaits a suspend function via callbacks.
|
||||
///
|
||||
/// The function takes an `onResult` and `onError` callback
|
||||
/// and returns a cancellable that can be used to cancel the suspend function.
|
||||
public typealias NativeSuspend<Result, Failure: Error, Unit> = (
|
||||
_ onResult: @escaping NativeCallback<Result, Unit>,
|
||||
_ onError: @escaping NativeCallback<Failure, Unit>
|
||||
) -> NativeCancellable<Unit>
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Rick Clephas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,320 +0,0 @@
|
|||
# KMP-NativeCoroutines
|
||||
|
||||
A library to use Kotlin Coroutines from Swift code in KMP apps.
|
||||
|
||||
## Why this library?
|
||||
|
||||
Both KMP and Kotlin Coroutines are amazing but together they have some limitations.
|
||||
|
||||
The most important limitation is cancellation support.
|
||||
Kotlin suspend functions are exposed to Swift as functions with a completion handler.
|
||||
This allows you to easily use them from your Swift code, but it doesn't support cancellation.
|
||||
|
||||
> While Swift 5.5 brings async functions to Swift, it doesn't solve this issue.
|
||||
> For interoperability with ObjC all functions with a completion handler can be called like an async function.
|
||||
> This means starting with Swift 5.5 your Kotlin suspend functions will look like Swift async functions.
|
||||
> But that's just syntactic sugar, so there's still no cancellation support.
|
||||
|
||||
Besides cancellation support, ObjC doesn't support generics on protocols.
|
||||
So all the `Flow` interfaces lose their generic value type which make them hard to use.
|
||||
|
||||
This library solves both of these limitations 😄.
|
||||
|
||||
## 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`.
|
||||
Compatibility versions for older Kotlin versions are also available:
|
||||
|
||||
| Version | Version suffix | Kotlin | Coroutines |
|
||||
|--------------|-----------------|:----------:|:-------------------:|
|
||||
| _latest_ | -new-mm | 1.6.10 | 1.6.0 |
|
||||
| **_latest_** | **_no suffix_** | **1.6.10** | **1.6.0-native-mt** |
|
||||
| _latest_ | -kotlin-1.6.0 | 1.6.0 | 1.6.0-native-mt |
|
||||
| 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 | -kotlin-1.5.20 | 1.5.20 | 1.5.0-native-mt |
|
||||
|
||||
You can choose from a couple of Swift implementations.
|
||||
Depending on the implementation you can support as low as iOS 9, macOS 10.9, tvOS 9 and watchOS 3:
|
||||
|
||||
| Implementation | Swift | iOS | macOS | tvOS | watchOS |
|
||||
|----------------|:-----:|:----:|:-----:|:----:|:-------:|
|
||||
| Async | 5.5 | 13.0 | 10.15 | 13.0 | 6.0 |
|
||||
| Combine | 5.0 | 13.0 | 10.15 | 13.0 | 6.0 |
|
||||
| RxSwift | 5.0 | 9.0 | 10.9 | 9.0 | 3.0 |
|
||||
|
||||
## Installation
|
||||
|
||||
The library consists of a Kotlin and Swift part which you'll need to add to your project.
|
||||
The Kotlin part is available on Maven Central and the Swift part can be installed via CocoaPods
|
||||
or the Swift Package Manager.
|
||||
|
||||
Make sure to always use the same versions for all the libraries!
|
||||
|
||||
[![latest release](https://img.shields.io/github/v/release/rickclephas/KMP-NativeCoroutines?label=latest%20release&sort=semver)](https://github.com/rickclephas/KMP-NativeCoroutines/releases)
|
||||
|
||||
### Kotlin
|
||||
|
||||
For Kotlin just add the plugin to your `build.gradle.kts`:
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.rickclephas.kmp.nativecoroutines") version "<version>"
|
||||
}
|
||||
```
|
||||
|
||||
### Swift (Swift Package Manager)
|
||||
|
||||
All Swift implementations are also available via the Swift Package Manager.
|
||||
Just add it to your `Package.swift` file:
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/rickclephas/KMP-NativeCoroutines.git", from: "<version>")
|
||||
]
|
||||
```
|
||||
|
||||
Or add it in Xcode by going to `File` > `Add Packages...` and providing the URL:
|
||||
`https://github.com/rickclephas/KMP-NativeCoroutines.git`.
|
||||
|
||||
### Swift (CocoaPods)
|
||||
|
||||
Now for Swift you can choose from a couple of implementations.
|
||||
Add one or more of the following libraries to your `Podfile`:
|
||||
```ruby
|
||||
pod 'KMPNativeCoroutinesAsync' # Swift 5.5 Async/Await implementation
|
||||
pod 'KMPNativeCoroutinesCombine' # Combine implementation
|
||||
pod 'KMPNativeCoroutinesRxSwift' # RxSwift implementation
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Using your Kotlin Coroutines code from Swift is almost as easy as calling the Kotlin code.
|
||||
Just use the wrapper functions in Swift to get Observables, Publishers, AsyncStreams or async functions.
|
||||
|
||||
### Kotlin
|
||||
|
||||
The plugin will automagically generate the necessary code for you! 🔮
|
||||
|
||||
Your `Flow` properties/functions get a `Native` version:
|
||||
```kotlin
|
||||
class Clock {
|
||||
// Somewhere in your Kotlin code you define a Flow property
|
||||
val time: StateFlow<Long> // This can be any kind of Flow
|
||||
|
||||
// The plugin will generate this native property for you
|
||||
val timeNative
|
||||
get() = time.asNativeFlow()
|
||||
}
|
||||
```
|
||||
|
||||
In case of a `StateFlow` or `SharedFlow` property you also get a `NativeValue` or `NativeReplayCache` property:
|
||||
```kotlin
|
||||
// For the StateFlow defined above the plugin will generate this native value property
|
||||
val timeNativeValue
|
||||
get() = time.value
|
||||
|
||||
// In case of a SharedFlow the plugin would generate this native replay cache property
|
||||
val timeNativeReplayCache
|
||||
get() = time.replayCache
|
||||
```
|
||||
|
||||
The plugin also generates `Native` versions for all your suspend functions:
|
||||
```kotlin
|
||||
class RandomLettersGenerator {
|
||||
// Somewhere in your Kotlin code you define a suspend function
|
||||
suspend fun getRandomLetters(): String {
|
||||
// Code to generate some random letters
|
||||
}
|
||||
|
||||
// The plugin will generate this native function for you
|
||||
fun getRandomLettersNative() =
|
||||
nativeSuspend { getRandomLetters() }
|
||||
}
|
||||
```
|
||||
|
||||
#### Global properties and functions
|
||||
|
||||
The plugin is currently unable to generate native versions for global properties and functions.
|
||||
In such cases you have to manually create the native versions in your Kotlin native code.
|
||||
|
||||
#### Custom suffix
|
||||
|
||||
If you don't like the naming of these generated properties/functions, you can easily change the suffix.
|
||||
For example add the following to your `build.gradle.kts` to use the suffix `Apple`:
|
||||
```kotlin
|
||||
nativeCoroutines {
|
||||
suffix = "Apple"
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom CoroutineScope
|
||||
|
||||
For more control you can provide a custom `CoroutineScope` with the `NativeCoroutineScope` annotation:
|
||||
```kotlin
|
||||
class Clock {
|
||||
@NativeCoroutineScope
|
||||
internal val coroutineScope = CoroutineScope(job + Dispatchers.Default)
|
||||
}
|
||||
```
|
||||
|
||||
If you don't provide a `CoroutineScope` the default scope will be used which is defined as:
|
||||
```kotlin
|
||||
@SharedImmutable
|
||||
internal val defaultCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
```
|
||||
|
||||
#### Ignoring declarations
|
||||
|
||||
Use the `NativeCoroutinesIgnore` annotation to tell the plugin to ignore a property or function:
|
||||
```kotlin
|
||||
@NativeCoroutinesIgnore
|
||||
val ignoredFlowProperty: Flow<Int>
|
||||
|
||||
@NativeCoroutinesIgnore
|
||||
suspend fun ignoredSuspendFunction() { }
|
||||
```
|
||||
|
||||
### Swift 5.5 Async/Await
|
||||
|
||||
The Async implementation provides some functions to get async Swift functions and `AsyncStream`s.
|
||||
|
||||
Use the `asyncFunction(for:)` function to get an async function that can be awaited:
|
||||
```swift
|
||||
let handle = Task {
|
||||
do {
|
||||
let letters = try await asyncFunction(for: randomLettersGenerator.getRandomLettersNative())
|
||||
print("Got random letters: \(letters)")
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// To cancel the suspend function just cancel the async task
|
||||
handle.cancel()
|
||||
```
|
||||
|
||||
or if you don't like these do-catches you can use the `asyncResult(for:)` function:
|
||||
```swift
|
||||
let result = await asyncResult(for: randomLettersGenerator.getRandomLettersNative())
|
||||
if case let .success(letters) = result {
|
||||
print("Got random letters: \(letters)")
|
||||
}
|
||||
```
|
||||
|
||||
For `Flow`s there is the `asyncStream(for:)` function to get an `AsyncStream`:
|
||||
```swift
|
||||
let handle = Task {
|
||||
do {
|
||||
let stream = asyncStream(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
for try await letters in stream {
|
||||
print("Got random letters: \(letters)")
|
||||
}
|
||||
} catch {
|
||||
print("Failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// To cancel the flow (collection) just cancel the async task
|
||||
handle.cancel()
|
||||
```
|
||||
|
||||
### Combine
|
||||
|
||||
The Combine implementation provides a couple functions to get an `AnyPublisher` for your Coroutines code.
|
||||
|
||||
For your `Flow`s use the `createPublisher(for:)` function:
|
||||
```swift
|
||||
// Create an AnyPublisher for your flow
|
||||
let publisher = createPublisher(for: clock.timeNative)
|
||||
|
||||
// Now use this publisher as you would any other
|
||||
let cancellable = publisher.sink { completion in
|
||||
print("Received completion: \(completion)")
|
||||
} receiveValue: { value in
|
||||
print("Received value: \(value)")
|
||||
}
|
||||
|
||||
// To cancel the flow (collection) just cancel the publisher
|
||||
cancellable.cancel()
|
||||
```
|
||||
|
||||
For the suspend functions you should use the `createFuture(for:)` function:
|
||||
```swift
|
||||
// Create a Future/AnyPublisher for the suspend function
|
||||
let future = createFuture(for: randomLettersGenerator.getRandomLettersNative())
|
||||
|
||||
// Now use this future as you would any other
|
||||
let cancellable = future.sink { completion in
|
||||
print("Received completion: \(completion)")
|
||||
} receiveValue: { value in
|
||||
print("Received value: \(value)")
|
||||
}
|
||||
|
||||
// To cancel the suspend function just cancel the future
|
||||
cancellable.cancel()
|
||||
```
|
||||
|
||||
You can also use the `createPublisher(for:)` function for suspend functions that return a `Flow`:
|
||||
```swift
|
||||
let publisher = createPublisher(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
```
|
||||
|
||||
**Note:** these functions create deferred `AnyPublisher`s.
|
||||
Meaning every subscription will trigger the collection of the `Flow` or execution of the suspend function.
|
||||
|
||||
### RxSwift
|
||||
|
||||
The RxSwift implementation provides a couple functions to get an `Observable` or `Single` for your Coroutines code.
|
||||
|
||||
For your `Flow`s use the `createObservable(for:)` function:
|
||||
```swift
|
||||
// Create an observable for your flow
|
||||
let observable = createObservable(for: clock.timeNative)
|
||||
|
||||
// Now use this observable as you would any other
|
||||
let disposable = observable.subscribe(onNext: { value in
|
||||
print("Received value: \(value)")
|
||||
}, onError: { error in
|
||||
print("Received error: \(error)")
|
||||
}, onCompleted: {
|
||||
print("Observable completed")
|
||||
}, onDisposed: {
|
||||
print("Observable disposed")
|
||||
})
|
||||
|
||||
// To cancel the flow (collection) just dispose the subscription
|
||||
disposable.dispose()
|
||||
```
|
||||
|
||||
For the suspend functions you should use the `createSingle(for:)` function:
|
||||
```swift
|
||||
// Create a single for the suspend function
|
||||
let single = createSingle(for: randomLettersGenerator.getRandomLettersNative())
|
||||
|
||||
// Now use this single as you would any other
|
||||
let disposable = single.subscribe(onSuccess: { value in
|
||||
print("Received value: \(value)")
|
||||
}, onFailure: { error in
|
||||
print("Received error: \(error)")
|
||||
}, onDisposed: {
|
||||
print("Single disposed")
|
||||
})
|
||||
|
||||
// To cancel the suspend function just dispose the subscription
|
||||
disposable.dispose()
|
||||
```
|
||||
|
||||
You can also use the `createObservable(for:)` function for suspend functions that return a `Flow`:
|
||||
```swift
|
||||
let observable = createObservable(for: randomLettersGenerator.getRandomLettersFlowNative())
|
||||
```
|
||||
|
||||
**Note:** these functions create deferred `Observable`s and `Single`s.
|
||||
Meaning every subscription will trigger the collection of the `Flow` or execution of the suspend function.
|
|
@ -21,7 +21,7 @@
|
|||
"name": "Build common",
|
||||
"execution_position": "before_compile",
|
||||
"shell_path": "/bin/sh",
|
||||
"script": " if [ \"YES\" = \"$COCOAPODS_SKIP_KOTLIN_BUILD\" ]; then\n echo \"Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\"\"\n exit 0\n fi\n set -ev\n REPO_ROOT=\"$PODS_TARGET_SRCROOT\"\n \"$REPO_ROOT/../gradlew\" -p \"$REPO_ROOT\" $KOTLIN_PROJECT_PATH:syncFramework -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME -Pkotlin.native.cocoapods.archs=\"$ARCHS\" -Pkotlin.native.cocoapods.configuration=$CONFIGURATION\n"
|
||||
"script": " if [ \"YES\" = \"$COCOAPODS_SKIP_KOTLIN_BUILD\" ]; then\n echo \"Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\"\"\n exit 0\n fi\n set -ev\n REPO_ROOT=\"$PODS_TARGET_SRCROOT\"\n \"$REPO_ROOT/../gradlew\" -p \"$REPO_ROOT\" $KOTLIN_PROJECT_PATH:syncFramework -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME -Pkotlin.native.cocoapods.archs=\"$ARCHS\" -Pkotlin.native.cocoapods.configuration=$CONFIGURATION -Pkotlin.native.cocoapods.cflags=\"$OTHER_CFLAGS\" -Pkotlin.native.cocoapods.paths.headers=\"$HEADER_SEARCH_PATHS\" -Pkotlin.native.cocoapods.paths.frameworks=\"$FRAMEWORK_SEARCH_PATHS\"\n"
|
||||
}
|
||||
],
|
||||
"platforms": {
|
||||
|
|
22
ios/PeopleInSpaceSwiftUI/Pods/Manifest.lock
generated
22
ios/PeopleInSpaceSwiftUI/Pods/Manifest.lock
generated
|
@ -1,32 +1,16 @@
|
|||
PODS:
|
||||
- common (1.0)
|
||||
- KMPNativeCoroutinesAsync (0.11.1):
|
||||
- KMPNativeCoroutinesCore (= 0.11.1)
|
||||
- KMPNativeCoroutinesCombine (0.11.1):
|
||||
- KMPNativeCoroutinesCore (= 0.11.1)
|
||||
- KMPNativeCoroutinesCore (0.11.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- common (from `../../common`)
|
||||
- KMPNativeCoroutinesAsync (= 0.11.1)
|
||||
- KMPNativeCoroutinesCombine (= 0.11.1)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- KMPNativeCoroutinesAsync
|
||||
- KMPNativeCoroutinesCombine
|
||||
- KMPNativeCoroutinesCore
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
common:
|
||||
:path: "../../common"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
common: 5def32d6e7131f79a49997cca9bbcc9cbd11e08a
|
||||
KMPNativeCoroutinesAsync: 1e6e09efe1fb04a9412483680829dbd35b55ef18
|
||||
KMPNativeCoroutinesCombine: 43a442a5e50ee8fcbb8633a361d12907fe933920
|
||||
KMPNativeCoroutinesCore: ed98a12d8337f861088f6b79636ffb29ff9bb2ad
|
||||
common: ed5a58383c5a02f46882243487c7aac341a3064f
|
||||
|
||||
PODFILE CHECKSUM: d51c9a2ba0fb9109f719acf8878fb54882154ace
|
||||
PODFILE CHECKSUM: f7c2bab6802efae18b223d30cb88e54a074a5f69
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.9.3
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1240"
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForAnalyzing = "YES"
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
buildForArchiving = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6694FA49269F5170E337048A97626853"
|
||||
|
@ -23,15 +23,14 @@
|
|||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
@ -39,14 +38,17 @@
|
|||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
buildConfiguration = "Debug"
|
||||
allowLocationSimulation = "YES">
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
debugDocumentVersioning = "YES"
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1240"
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForAnalyzing = "YES"
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
buildForArchiving = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8217FBB9D1218C346C0781D0B8F2BBE8"
|
||||
|
@ -23,15 +23,14 @@
|
|||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
@ -39,14 +38,17 @@
|
|||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
buildConfiguration = "Debug"
|
||||
allowLocationSimulation = "YES">
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
debugDocumentVersioning = "YES"
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
|
|
|
@ -4,40 +4,12 @@
|
|||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>KMPNativeCoroutinesAsync.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>KMPNativeCoroutinesCombine.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>KMPNativeCoroutinesCore.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>Pods-PeopleInSpaceSwiftUI.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>Pods-PeopleInSpaceWidgetExtension.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>5</integer>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>common.xcscheme</key>
|
||||
<dict>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_KMPNativeCoroutinesAsync : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_KMPNativeCoroutinesAsync
|
||||
@end
|
|
@ -1,12 +0,0 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double KMPNativeCoroutinesAsyncVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char KMPNativeCoroutinesAsyncVersionString[];
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesAsync
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap" -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/KMPNativeCoroutinesAsync
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
|
@ -1,6 +0,0 @@
|
|||
module KMPNativeCoroutinesAsync {
|
||||
umbrella header "KMPNativeCoroutinesAsync-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesAsync
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap" -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/KMPNativeCoroutinesAsync
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
|
@ -1,5 +0,0 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_KMPNativeCoroutinesCombine : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_KMPNativeCoroutinesCombine
|
||||
@end
|
|
@ -1,12 +0,0 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double KMPNativeCoroutinesCombineVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char KMPNativeCoroutinesCombineVersionString[];
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
APPLICATION_EXTENSION_API_ONLY = YES
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCombine
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap" -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/KMPNativeCoroutinesCombine
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
|
@ -1,6 +0,0 @@
|
|||
module KMPNativeCoroutinesCombine {
|
||||
umbrella header "KMPNativeCoroutinesCombine-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
APPLICATION_EXTENSION_API_ONLY = YES
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCombine
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore/KMPNativeCoroutinesCore.modulemap" -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/KMPNativeCoroutinesCombine
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/KMPNativeCoroutinesCore"
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue