diff --git a/MultiplatformDemoWithSync/.gitignore b/MultiplatformDemoWithSync/.gitignore
new file mode 100644
index 0000000..8b09989
--- /dev/null
+++ b/MultiplatformDemoWithSync/.gitignore
@@ -0,0 +1,54 @@
+*.iml
+.gradle
+/local.properties
+.idea
+.DS_Store
+/build
+*/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+### CocoaPods ###
+## CocoaPods GitIgnore Template
+
+# CocoaPods - Only use to conserve bandwidth / Save time on Pushing
+# - Also handy if you have a large number of dependant pods
+# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE
+Pods/
+
+### Xcode ###
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
+build/
+DerivedData/
+*.moved-aside
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+
+## Gcc Patch
+/*.gcno
+
+### Xcode Patch ###
+*.xcodeproj/*
+!*.xcodeproj/project.pbxproj
+!*.xcodeproj/xcshareddata/
+!*.xcworkspace/contents.xcworkspacedata
+**/xcshareddata/WorkspaceSettings.xcsettings
+
diff --git a/MultiplatformDemoWithSync/README.md b/MultiplatformDemoWithSync/README.md
new file mode 100644
index 0000000..f880203
--- /dev/null
+++ b/MultiplatformDemoWithSync/README.md
@@ -0,0 +1,44 @@
+## Kotlin Multiplatform Sync demo App using a shared business logic:
+
+The demo demonstrates [Sync](https://www.mongodb.com/realm/mobile/sync) capability between an Android, iOS, macOS and JVM (currently Mac & Linux).
+
+
+
+# Steps to build:
+
+## 1 - Create a Realm Sync App on MongoDB Atlas
+
+- Follow the tutorial at https://docs.mongodb.com/realm/tutorial/realm-app/#a.-create-an-atlas-account or watch the screencast https://www.youtube.com/watch?v=lqo0Yf7lnyg
+
+- Replace the App identifier and the created user/password in [shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Constants.kt](./shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Constants.kt)
+
+## 2 - Build and run for Android
+
+```
+ ./gradlew :androidApp:installDebug
+```
+
+## 3 - Build and run for iOS
+
+```
+./gradlew shared:podInstall
+cd iosApp
+pod install
+open iosApp.xcworkspace
+```
+
+## 4 - Build and run for macOS
+
+```
+./gradlew shared:podInstall
+cd macosApp
+pod install
+open macosApp.xcworkspace
+```
+
+## 5 - Build and run for JVM
+
+```
+./gradlew :jvmApp:run
+```
+
diff --git a/MultiplatformDemoWithSync/Screenshots/kotlin-sync-demo.gif b/MultiplatformDemoWithSync/Screenshots/kotlin-sync-demo.gif
new file mode 100644
index 0000000..d16d5a5
Binary files /dev/null and b/MultiplatformDemoWithSync/Screenshots/kotlin-sync-demo.gif differ
diff --git a/MultiplatformDemoWithSync/androidApp/build.gradle.kts b/MultiplatformDemoWithSync/androidApp/build.gradle.kts
new file mode 100644
index 0000000..da91865
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/build.gradle.kts
@@ -0,0 +1,50 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+}
+
+val compose_version = "1.2.0-alpha01"
+
+dependencies {
+ implementation(project(":shared"))
+
+ implementation("androidx.compose.compiler:compiler:$compose_version")
+ implementation("androidx.compose.material:material:$compose_version")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt") {
+ version {
+ strictly("1.5.2-native-mt")
+ }
+ }
+ implementation("androidx.compose.ui:ui:$compose_version")
+ implementation("androidx.compose.ui:ui-tooling:$compose_version")
+ implementation("androidx.activity:activity-compose:1.4.0-beta01")
+}
+
+android {
+ compileSdk = 31
+ defaultConfig {
+ applicationId = "io.realm.kotlin.demo"
+ minSdk = 21
+ targetSdk = 31
+ versionCode = 1
+ versionName = "1.0"
+ }
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ }
+ }
+
+ // Required by Compose
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+ buildFeatures {
+ compose = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = compose_version
+ }
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/AndroidManifest.xml b/MultiplatformDemoWithSync/androidApp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7cb5c35
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Color.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Color.kt
new file mode 100644
index 0000000..0444fa0
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Color.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.realm.kotlin.demo.theme
+
+import androidx.compose.material.darkColors
+import androidx.compose.ui.graphics.Color
+
+val Purple200 = Color(0xFFBB86FC)
+val Purple500 = Color(0xFF6200EE)
+val Purple700 = Color(0xFF3700B3)
+val Teal200 = Color(0xFF03DAC5)
+
+val Green500 = Color(0xFF1EB980)
+val DarkBlue900 = Color(0xFF26282F)
+
+// Rally is always dark themed.
+val ColorPalette = darkColors(
+ primary = Green500,
+ surface = DarkBlue900,
+ onSurface = Color.White,
+ background = DarkBlue900,
+ onBackground = Color.White
+)
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/RealmColor.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/RealmColor.kt
new file mode 100644
index 0000000..8701f77
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/RealmColor.kt
@@ -0,0 +1,21 @@
+package io.realm.kotlin.demo.theme
+
+import androidx.compose.ui.graphics.Color
+
+object RealmColor {
+ // Grays
+ val Charcoal = Color(0xFF1C233F)
+ val Elephant = Color(0xFF9A9BA5)
+ val ElephantHalf = Color(0xFFb1b3bf)
+ val Dov = Color(0xFFEBEBF2)
+
+ // Orb colors
+ val Ultramarine = Color(0xFF39477F)
+ val Indigo = Color(0xFF59569E)
+ val GrapeJelly = Color(0xFF9A59A5)
+ val Mulberry = Color(0xFFD34CA3)
+ val Flamingo = Color(0xFFF25192)
+ val SexySalmon = Color(0xFFF77C88)
+ val Peach = Color(0xFFFC9F95)
+ val Melon = Color(0xFFFCC397)
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Shape.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Shape.kt
new file mode 100644
index 0000000..c8e7be4
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Shape.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.realm.kotlin.demo.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(4.dp),
+ medium = RoundedCornerShape(4.dp),
+ large = RoundedCornerShape(0.dp)
+)
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Size.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Size.kt
new file mode 100644
index 0000000..e8cb855
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Size.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.realm.kotlin.demo.theme
+
+import androidx.compose.ui.unit.dp
+
+val rowSize = 60.dp
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Theme.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Theme.kt
new file mode 100644
index 0000000..c9628e7
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Theme.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.realm.kotlin.demo.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+ primary = Purple200,
+ primaryVariant = Purple700,
+ secondary = Teal200
+)
+
+private val LightColorPalette = lightColors(
+ primary = Purple500,
+ primaryVariant = Purple700,
+ secondary = Teal200
+)
+
+@Composable
+fun MyApplicationTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable() () -> Unit
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colors = colors,
+ typography = Typography,
+ shapes = Shapes,
+ content = content
+ )
+}
+
+@Composable
+fun RallyTheme(content: @Composable () -> Unit) {
+ MaterialTheme(colors = ColorPalette, typography = Typography, content = content)
+}
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Type.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Type.kt
new file mode 100644
index 0000000..424ad31
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/theme/Type.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.realm.kotlin.demo.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+
+val Typography = Typography(
+ h1 = TextStyle(
+ fontWeight = FontWeight.W100,
+ fontSize = 96.sp,
+ ),
+ h2 = TextStyle(
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 44.sp,
+ letterSpacing = 1.5.sp
+ ),
+ h3 = TextStyle(
+ fontWeight = FontWeight.W400,
+ fontSize = 14.sp
+ ),
+ h4 = TextStyle(
+ fontWeight = FontWeight.W700,
+ fontSize = 34.sp
+ ),
+ h5 = TextStyle(
+ fontWeight = FontWeight.W700,
+ fontSize = 24.sp
+ ),
+ h6 = TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 18.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 3.sp
+ ),
+ subtitle1 = TextStyle(
+ fontWeight = FontWeight.Light,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 3.sp
+ ),
+ subtitle2 = TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ letterSpacing = 0.1.em
+ ),
+ body1 = TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ letterSpacing = 0.1.em
+ ),
+ body2 = TextStyle(
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.em
+ ),
+ button = TextStyle(
+ fontWeight = FontWeight.Bold,
+ fontSize = 14.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.2.em
+ ),
+ caption = TextStyle(
+ fontWeight = FontWeight.W500,
+ fontSize = 12.sp
+ ),
+ overline = TextStyle(
+ fontWeight = FontWeight.W500,
+ fontSize = 10.sp
+ )
+)
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/ui/counter/AndroidCounterViewModel.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/ui/counter/AndroidCounterViewModel.kt
new file mode 100644
index 0000000..2a148b2
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/ui/counter/AndroidCounterViewModel.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+import androidx.lifecycle.ViewModel
+import io.realm.kotlin.demo.util.CommonFlow
+
+class AndroidCounterViewModel: CounterViewModel, ViewModel() {
+ private val vm = SharedCounterViewModel()
+ override fun observeCounter(): CommonFlow = vm.observeCounter()
+ override fun increment() = vm.increment()
+ override fun decrement() = vm.decrement()
+
+ /**
+ * Implementation note: We could avoid the need for doing this
+ * by making it possible to inject the `viewModelScope` into the
+ * [SharedCounterViewModel], but doing it manually means that the
+ * pattern is the same between Android and iOS which lessens the
+ * cognitive load when switching between implementations.
+ */
+ override fun onCleared() {
+ vm.close()
+ }
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/ui/counter/CounterActivity.kt b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/ui/counter/CounterActivity.kt
new file mode 100644
index 0000000..1845156
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/kotlin/io/realm/kotlin/demo/ui/counter/CounterActivity.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import io.realm.kotlin.demo.theme.RealmColor
+import io.realm.kotlin.demo.theme.MyApplicationTheme
+
+class CounterActivity : ComponentActivity() {
+
+ private val viewModel = AndroidCounterViewModel()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ title = "Realm Kotlin Demo - ${viewModel.platform}"
+ setContent {
+ CounterApp(viewModel)
+ }
+ }
+
+ @Composable
+ fun CounterApp(vm: AndroidCounterViewModel) {
+ MyApplicationTheme {
+ Surface(color = RealmColor.SexySalmon) {
+ Column {
+ CounterButton(modifier = Modifier.weight(1F)) {
+ vm.increment()
+ }
+ CounterButton(modifier = Modifier.weight(1F)) {
+ vm.decrement()
+ }
+ }
+ Box(Modifier.fillMaxSize()) {
+ val value: String by vm.observeCounter().collectAsState(initial = "-")
+ Text(
+ text = value,
+ modifier = Modifier.align(Alignment.Center),
+ style = MaterialTheme.typography.h1,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun CounterButton(modifier: Modifier = Modifier, action: () -> Unit) {
+ Box(modifier = modifier
+ .fillMaxWidth()
+ .clickable {
+ action()
+ }
+ )
+ }
+
+ @Preview(showBackground = true)
+ @Composable
+ fun DefaultPreview() {
+ CounterApp(viewModel)
+ }
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/res/values/colors.xml b/MultiplatformDemoWithSync/androidApp/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4faecfa
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #6200EE
+ #3700B3
+ #03DAC5
+
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/androidApp/src/main/res/values/styles.xml b/MultiplatformDemoWithSync/androidApp/src/main/res/values/styles.xml
new file mode 100644
index 0000000..b750c93
--- /dev/null
+++ b/MultiplatformDemoWithSync/androidApp/src/main/res/values/styles.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/build.gradle.kts b/MultiplatformDemoWithSync/build.gradle.kts
new file mode 100644
index 0000000..bfae601
--- /dev/null
+++ b/MultiplatformDemoWithSync/build.gradle.kts
@@ -0,0 +1,25 @@
+buildscript {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
+ classpath("com.android.tools.build:gradle:7.1.0-rc01")
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ group = "io.realm.sample"
+ version = "0.8.2"
+}
+
+tasks.register("clean", Delete::class) {
+ delete(rootProject.buildDir)
+}
diff --git a/MultiplatformDemoWithSync/gradle.properties b/MultiplatformDemoWithSync/gradle.properties
new file mode 100644
index 0000000..d4667dd
--- /dev/null
+++ b/MultiplatformDemoWithSync/gradle.properties
@@ -0,0 +1,9 @@
+#Gradle
+org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
+
+#Kotlin
+kotlin.code.style=official
+kotlin.mpp.stability.nowarn=true
+
+#Android
+android.useAndroidX=true
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/gradle/wrapper/gradle-wrapper.jar b/MultiplatformDemoWithSync/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/MultiplatformDemoWithSync/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/MultiplatformDemoWithSync/gradle/wrapper/gradle-wrapper.properties b/MultiplatformDemoWithSync/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..351a27d
--- /dev/null
+++ b/MultiplatformDemoWithSync/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Sep 23 13:29:58 CEST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/MultiplatformDemoWithSync/gradlew b/MultiplatformDemoWithSync/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/MultiplatformDemoWithSync/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/MultiplatformDemoWithSync/gradlew.bat b/MultiplatformDemoWithSync/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/MultiplatformDemoWithSync/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/MultiplatformDemoWithSync/iosApp/Podfile b/MultiplatformDemoWithSync/iosApp/Podfile
new file mode 100644
index 0000000..aff9c51
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/Podfile
@@ -0,0 +1,5 @@
+target 'iosApp' do
+ use_frameworks!
+ platform :ios, '14.1'
+ pod 'shared', :path => '../shared'
+end
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/iosApp/Podfile.lock b/MultiplatformDemoWithSync/iosApp/Podfile.lock
new file mode 100644
index 0000000..86ea159
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/Podfile.lock
@@ -0,0 +1,16 @@
+PODS:
+ - shared (1.0)
+
+DEPENDENCIES:
+ - shared (from `../shared`)
+
+EXTERNAL SOURCES:
+ shared:
+ :path: "../shared"
+
+SPEC CHECKSUMS:
+ shared: 11463184695b113572ac0077e60cee9c9791894e
+
+PODFILE CHECKSUM: f282da88f39e69507b0a255187c8a6b644477756
+
+COCOAPODS: 1.11.2
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.pbxproj b/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..5feae2d
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -0,0 +1,639 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 2C71E4730783507AB1EF50B8 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ABB211E833F656B59EFD9D3 /* Pods_iosApp.framework */; };
+ 832C51A12707A71D0086207A /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C51A02707A71D0086207A /* iOSApp.swift */; };
+ 832C51A32707A71D0086207A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C51A22707A71D0086207A /* ContentView.swift */; };
+ 832C51A52707A7250086207A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 832C51A42707A7250086207A /* Assets.xcassets */; };
+ 832C51A82707A7250086207A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 832C51A72707A7250086207A /* Preview Assets.xcassets */; };
+ 832C51B32707A7250086207A /* iosAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C51B22707A7250086207A /* iosAppTests.swift */; };
+ 832C51BE2707A7250086207A /* iosAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C51BD2707A7250086207A /* iosAppUITests.swift */; };
+ 832C51DA2707ABF30086207A /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C51D92707ABF30086207A /* Theme.swift */; };
+ 832C51DF2707ABFD0086207A /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C51DE2707ABFD0086207A /* ViewModel.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 832C51AF2707A7250086207A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 832C51952707A71D0086207A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 832C519C2707A71D0086207A;
+ remoteInfo = iosApp;
+ };
+ 832C51BA2707A7250086207A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 832C51952707A71D0086207A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 832C519C2707A71D0086207A;
+ remoteInfo = iosApp;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 7ABB211E833F656B59EFD9D3 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 832C519D2707A71D0086207A /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 832C51A02707A71D0086207A /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; };
+ 832C51A22707A71D0086207A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ 832C51A42707A7250086207A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 832C51A72707A7250086207A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ 832C51A92707A7250086207A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 832C51AE2707A7250086207A /* iosAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 832C51B22707A7250086207A /* iosAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosAppTests.swift; sourceTree = ""; };
+ 832C51B42707A7250086207A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 832C51B92707A7250086207A /* iosAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 832C51BD2707A7250086207A /* iosAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosAppUITests.swift; sourceTree = ""; };
+ 832C51BF2707A7250086207A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 832C51D92707ABF30086207A /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; };
+ 832C51DE2707ABFD0086207A /* ViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; };
+ B7D5DF6EF20D815FEEF6F755 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
+ DF87338F42078C249671741B /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 832C519A2707A71D0086207A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2C71E4730783507AB1EF50B8 /* Pods_iosApp.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C51AB2707A7250086207A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C51B62707A7250086207A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 5D117C28EA2D79CD30408F16 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ B7D5DF6EF20D815FEEF6F755 /* Pods-iosApp.debug.xcconfig */,
+ DF87338F42078C249671741B /* Pods-iosApp.release.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
+ 832C51942707A71D0086207A = {
+ isa = PBXGroup;
+ children = (
+ 832C519F2707A71D0086207A /* iosApp */,
+ 832C51B12707A7250086207A /* iosAppTests */,
+ 832C51BC2707A7250086207A /* iosAppUITests */,
+ 832C519E2707A71D0086207A /* Products */,
+ 5D117C28EA2D79CD30408F16 /* Pods */,
+ D08047C6DCE244CCDAAEAAD7 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 832C519E2707A71D0086207A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 832C519D2707A71D0086207A /* iosApp.app */,
+ 832C51AE2707A7250086207A /* iosAppTests.xctest */,
+ 832C51B92707A7250086207A /* iosAppUITests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 832C519F2707A71D0086207A /* iosApp */ = {
+ isa = PBXGroup;
+ children = (
+ 832C51D92707ABF30086207A /* Theme.swift */,
+ 832C51A02707A71D0086207A /* iOSApp.swift */,
+ 832C51A22707A71D0086207A /* ContentView.swift */,
+ 832C51DE2707ABFD0086207A /* ViewModel.swift */,
+ 832C51A42707A7250086207A /* Assets.xcassets */,
+ 832C51A92707A7250086207A /* Info.plist */,
+ 832C51A62707A7250086207A /* Preview Content */,
+ );
+ path = iosApp;
+ sourceTree = "";
+ };
+ 832C51A62707A7250086207A /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ 832C51A72707A7250086207A /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+ 832C51B12707A7250086207A /* iosAppTests */ = {
+ isa = PBXGroup;
+ children = (
+ 832C51B22707A7250086207A /* iosAppTests.swift */,
+ 832C51B42707A7250086207A /* Info.plist */,
+ );
+ path = iosAppTests;
+ sourceTree = "";
+ };
+ 832C51BC2707A7250086207A /* iosAppUITests */ = {
+ isa = PBXGroup;
+ children = (
+ 832C51BD2707A7250086207A /* iosAppUITests.swift */,
+ 832C51BF2707A7250086207A /* Info.plist */,
+ );
+ path = iosAppUITests;
+ sourceTree = "";
+ };
+ D08047C6DCE244CCDAAEAAD7 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 7ABB211E833F656B59EFD9D3 /* Pods_iosApp.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 832C519C2707A71D0086207A /* iosApp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 832C51C22707A7250086207A /* Build configuration list for PBXNativeTarget "iosApp" */;
+ buildPhases = (
+ 8B59D48849FBD6E2D969612A /* [CP] Check Pods Manifest.lock */,
+ 832C51992707A71D0086207A /* Sources */,
+ 832C519A2707A71D0086207A /* Frameworks */,
+ 832C519B2707A71D0086207A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = iosApp;
+ productName = iosApp;
+ productReference = 832C519D2707A71D0086207A /* iosApp.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 832C51AD2707A7250086207A /* iosAppTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 832C51C52707A7250086207A /* Build configuration list for PBXNativeTarget "iosAppTests" */;
+ buildPhases = (
+ 832C51AA2707A7250086207A /* Sources */,
+ 832C51AB2707A7250086207A /* Frameworks */,
+ 832C51AC2707A7250086207A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 832C51B02707A7250086207A /* PBXTargetDependency */,
+ );
+ name = iosAppTests;
+ productName = iosAppTests;
+ productReference = 832C51AE2707A7250086207A /* iosAppTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 832C51B82707A7250086207A /* iosAppUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 832C51C82707A7250086207A /* Build configuration list for PBXNativeTarget "iosAppUITests" */;
+ buildPhases = (
+ 832C51B52707A7250086207A /* Sources */,
+ 832C51B62707A7250086207A /* Frameworks */,
+ 832C51B72707A7250086207A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 832C51BB2707A7250086207A /* PBXTargetDependency */,
+ );
+ name = iosAppUITests;
+ productName = iosAppUITests;
+ productReference = 832C51B92707A7250086207A /* iosAppUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 832C51952707A71D0086207A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1240;
+ LastUpgradeCheck = 1240;
+ TargetAttributes = {
+ 832C519C2707A71D0086207A = {
+ CreatedOnToolsVersion = 12.4;
+ };
+ 832C51AD2707A7250086207A = {
+ CreatedOnToolsVersion = 12.4;
+ TestTargetID = 832C519C2707A71D0086207A;
+ };
+ 832C51B82707A7250086207A = {
+ CreatedOnToolsVersion = 12.4;
+ TestTargetID = 832C519C2707A71D0086207A;
+ };
+ };
+ };
+ buildConfigurationList = 832C51982707A71D0086207A /* Build configuration list for PBXProject "iosApp" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 832C51942707A71D0086207A;
+ productRefGroup = 832C519E2707A71D0086207A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 832C519C2707A71D0086207A /* iosApp */,
+ 832C51AD2707A7250086207A /* iosAppTests */,
+ 832C51B82707A7250086207A /* iosAppUITests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 832C519B2707A71D0086207A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C51A82707A7250086207A /* Preview Assets.xcassets in Resources */,
+ 832C51A52707A7250086207A /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C51AC2707A7250086207A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C51B72707A7250086207A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 8B59D48849FBD6E2D969612A /* [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-iosApp-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 */
+ 832C51992707A71D0086207A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C51DA2707ABF30086207A /* Theme.swift in Sources */,
+ 832C51A32707A71D0086207A /* ContentView.swift in Sources */,
+ 832C51DF2707ABFD0086207A /* ViewModel.swift in Sources */,
+ 832C51A12707A71D0086207A /* iOSApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C51AA2707A7250086207A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C51B32707A7250086207A /* iosAppTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C51B52707A7250086207A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C51BE2707A7250086207A /* iosAppUITests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 832C51B02707A7250086207A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 832C519C2707A71D0086207A /* iosApp */;
+ targetProxy = 832C51AF2707A7250086207A /* PBXContainerItemProxy */;
+ };
+ 832C51BB2707A7250086207A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 832C519C2707A71D0086207A /* iosApp */;
+ targetProxy = 832C51BA2707A7250086207A /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 832C51C02707A7250086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.4;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 832C51C12707A7250086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.4;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 832C51C32707A7250086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = B7D5DF6EF20D815FEEF6F755 /* Pods-iosApp.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.iosApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 832C51C42707A7250086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = DF87338F42078C249671741B /* Pods-iosApp.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.iosApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 832C51C62707A7250086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ INFOPLIST_FILE = iosAppTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.iosAppTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iosApp.app/iosApp";
+ };
+ name = Debug;
+ };
+ 832C51C72707A7250086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ INFOPLIST_FILE = iosAppTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.iosAppTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iosApp.app/iosApp";
+ };
+ name = Release;
+ };
+ 832C51C92707A7250086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ INFOPLIST_FILE = iosAppUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.iosAppUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = iosApp;
+ };
+ name = Debug;
+ };
+ 832C51CA2707A7250086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ INFOPLIST_FILE = iosAppUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.iosAppUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = iosApp;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 832C51982707A71D0086207A /* Build configuration list for PBXProject "iosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C51C02707A7250086207A /* Debug */,
+ 832C51C12707A7250086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 832C51C22707A7250086207A /* Build configuration list for PBXNativeTarget "iosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C51C32707A7250086207A /* Debug */,
+ 832C51C42707A7250086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 832C51C52707A7250086207A /* Build configuration list for PBXNativeTarget "iosAppTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C51C62707A7250086207A /* Debug */,
+ 832C51C72707A7250086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 832C51C82707A7250086207A /* Build configuration list for PBXNativeTarget "iosAppUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C51C92707A7250086207A /* Debug */,
+ 832C51CA2707A7250086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 832C51952707A71D0086207A /* Project object */;
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/MultiplatformDemoWithSync/iosApp/iosApp.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..c009e7d
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MultiplatformDemoWithSync/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "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
+ }
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/Contents.json b/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/ContentView.swift b/MultiplatformDemoWithSync/iosApp/iosApp/ContentView.swift
new file mode 100644
index 0000000..5288f87
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/ContentView.swift
@@ -0,0 +1,76 @@
+//
+// ContentView.swift
+// iosApp
+//
+// Created by Christian Melchior on 24/09/2021.
+//
+
+import SwiftUI
+import shared
+
+struct ContentView: View {
+ @ObservedObject var vm = IOSCounterViewModel()
+ let screen = UIScreen.main.bounds
+ var body: some View {
+ ZStack {
+ Color.white
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height
+ )
+
+ VStack(spacing: 0) {
+ CounterButton(screen: screen, action: {
+ vm.increment()
+ })
+ CounterButton(screen: screen, action: {
+ vm.decrement()
+ })
+ }
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height
+ )
+
+ Text(vm.counter)
+ .fontWeight(.bold)
+ .font(.system(size: 150))
+
+ }
+ .onAppear {
+ vm.start()
+ }
+ .onDisappear {
+ vm.stop()
+ }
+ }}
+
+struct CounterButton: View {
+ var screen: CGRect
+ var action: () -> Void
+ var body: some View {
+ Button {
+ action()
+ } label: {
+ RealmColor.indigo
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height
+ )
+ }
+ .buttonStyle(PlainButtonStyle())
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height,
+ alignment: .center
+ )
+ }
+}
+
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView()
+ .previewDevice(PreviewDevice(rawValue: "Mac"))
+ }
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/Info.plist b/MultiplatformDemoWithSync/iosApp/iosApp/Info.plist
new file mode 100644
index 0000000..efc211a
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/Info.plist
@@ -0,0 +1,50 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchScreen
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/MultiplatformDemoWithSync/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/Theme.swift b/MultiplatformDemoWithSync/iosApp/iosApp/Theme.swift
new file mode 100644
index 0000000..1ef0581
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/Theme.swift
@@ -0,0 +1,38 @@
+//
+// Theme.swift
+// iosApp
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import Foundation
+import SwiftUI
+
+// Credit: https://stackoverflow.com/a/56874327/1389357
+extension Color {
+ init(hex: UInt, alpha: Double = 1) {
+ self.init(
+ .sRGB,
+ red: Double((hex >> 16) & 0xff) / 255,
+ green: Double((hex >> 08) & 0xff) / 255,
+ blue: Double((hex >> 00) & 0xff) / 255,
+ opacity: alpha
+ )
+ }
+}
+
+struct RealmColor {
+ // Greys
+ static let charcoal = Color.init(hex: 0x1C233F)
+ static let elephant = Color.init(hex: 0x9A9BA5)
+ static let dov = Color.init(hex: 0xEBEBF2)
+
+ // Orb colors
+ static let ultramarine = Color.init(hex: 0x39477F)
+ static let indigo = Color.init(hex: 0x59569E)
+ static let grapeJelly = Color.init(hex: 0x9A59A5)
+ static let mulberry = Color.init(hex: 0xD34CA3)
+ static let flamingo = Color.init(hex: 0xF25192)
+ static let sexySalmon = Color.init(hex: 0xFC9F95)
+ static let melon = Color.init(hex: 0xFCC397)
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/ViewModel.swift b/MultiplatformDemoWithSync/iosApp/iosApp/ViewModel.swift
new file mode 100644
index 0000000..4cdc73d
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/ViewModel.swift
@@ -0,0 +1,48 @@
+//
+// ViewModel.swift
+// iosApp
+//
+// Created by Christian Melchior on 24/09/2021.
+//
+import Foundation
+import Combine
+import shared
+
+// Generic Observable View Model, making it easier to control the lifecycle
+// of multiple Flows.
+class ObservableViewModel {
+ private var jobs = Array() // List of Kotlin Coroutine Jobs
+
+ func addObserver(observer: Closeable) {
+ jobs.append(observer)
+ }
+
+ func stop() {
+ jobs.forEach { job in job.close() }
+ }
+}
+
+class IOSCounterViewModel: ObservableViewModel, ObservableObject {
+ @Published var counter: String = "-"
+
+ private let vm: SharedCounterViewModel = SharedCounterViewModel()
+
+ func increment() {
+ vm.increment()
+ }
+
+ func decrement() {
+ vm.decrement()
+ }
+
+ func start() {
+ addObserver(observer: vm.observeCounter().watch { counterValue in
+ self.counter = counterValue! as String
+ })
+ }
+
+ override func stop() {
+ super.stop()
+ vm.close()
+ }
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosApp/iOSApp.swift b/MultiplatformDemoWithSync/iosApp/iosApp/iOSApp.swift
new file mode 100644
index 0000000..ede562f
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosApp/iOSApp.swift
@@ -0,0 +1,17 @@
+//
+// iOSApp.swift
+// iosApp
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import SwiftUI
+
+@main
+struct iOSApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosAppTests/Info.plist b/MultiplatformDemoWithSync/iosApp/iosAppTests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosAppTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/MultiplatformDemoWithSync/iosApp/iosAppTests/iosAppTests.swift b/MultiplatformDemoWithSync/iosApp/iosAppTests/iosAppTests.swift
new file mode 100644
index 0000000..3fb97fd
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosAppTests/iosAppTests.swift
@@ -0,0 +1,33 @@
+//
+// iosAppTests.swift
+// iosAppTests
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import XCTest
+@testable import iosApp
+
+class iosAppTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() throws {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ }
+
+ func testPerformanceExample() throws {
+ // This is an example of a performance test case.
+ self.measure {
+ // Put the code you want to measure the time of here.
+ }
+ }
+
+}
diff --git a/MultiplatformDemoWithSync/iosApp/iosAppUITests/Info.plist b/MultiplatformDemoWithSync/iosApp/iosAppUITests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosAppUITests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/MultiplatformDemoWithSync/iosApp/iosAppUITests/iosAppUITests.swift b/MultiplatformDemoWithSync/iosApp/iosAppUITests/iosAppUITests.swift
new file mode 100644
index 0000000..c7cb95a
--- /dev/null
+++ b/MultiplatformDemoWithSync/iosApp/iosAppUITests/iosAppUITests.swift
@@ -0,0 +1,42 @@
+//
+// iosAppUITests.swift
+// iosAppUITests
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import XCTest
+
+class iosAppUITests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ continueAfterFailure = false
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() throws {
+ // UI tests must launch the application that they test.
+ let app = XCUIApplication()
+ app.launch()
+
+ // Use recording to get started writing UI tests.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ }
+
+ func testLaunchPerformance() throws {
+ if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
+ // This measures how long it takes to launch your application.
+ measure(metrics: [XCTApplicationLaunchMetric()]) {
+ XCUIApplication().launch()
+ }
+ }
+ }
+}
diff --git a/MultiplatformDemoWithSync/jvmApp/build.gradle.kts b/MultiplatformDemoWithSync/jvmApp/build.gradle.kts
new file mode 100644
index 0000000..6b738bb
--- /dev/null
+++ b/MultiplatformDemoWithSync/jvmApp/build.gradle.kts
@@ -0,0 +1,30 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.compose") version "1.0.1-rc2"
+ application
+}
+
+repositories {
+ mavenCentral()
+ maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
+}
+
+dependencies {
+ implementation(compose.desktop.currentOs)
+ implementation(project(":shared"))
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt") {
+ version {
+ strictly("1.5.2-native-mt")
+ }
+ }
+}
+
+tasks.withType {
+ kotlinOptions.jvmTarget = "11"
+}
+
+application {
+ mainClass.set("io.realm.kotlin.demo.MainKt")
+}
diff --git a/MultiplatformDemoWithSync/jvmApp/src/main/kotlin/io/realm/kotlin/demo/Main.kt b/MultiplatformDemoWithSync/jvmApp/src/main/kotlin/io/realm/kotlin/demo/Main.kt
new file mode 100644
index 0000000..3495c34
--- /dev/null
+++ b/MultiplatformDemoWithSync/jvmApp/src/main/kotlin/io/realm/kotlin/demo/Main.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+import androidx.compose.ui.window.rememberWindowState
+import io.realm.kotlin.demo.theme.RealmColor
+import io.realm.kotlin.demo.ui.counter.SharedCounterViewModel
+
+fun main() {
+ val vm = SharedCounterViewModel()
+ application {
+ Window(
+ onCloseRequest = ::exitApplication,
+ title = "Realm Kotlin Demo - ${vm.platform}",
+ state = rememberWindowState(width = 320.dp, height = 500.dp)
+ ) {
+ MaterialTheme {
+ Surface(color = RealmColor.GrapeJelly) {
+ Column {
+ CounterButton(Modifier.weight(1F)) {
+ vm.increment()
+ }
+ CounterButton(Modifier.weight(1F)) {
+ vm.decrement()
+ }
+ }
+ Box(Modifier.fillMaxSize()) {
+ val state: String by vm.observeCounter()
+ .collectAsState(initial = "-")
+ Text(
+ text = state,
+ modifier = Modifier.align(Alignment.Center),
+ style = MaterialTheme.typography.h1,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun CounterButton(modifier: Modifier = Modifier, action: () -> Unit) {
+ Box(modifier = modifier
+ .fillMaxWidth()
+ .clickable {
+ action()
+ }
+ )
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/jvmApp/src/main/kotlin/io/realm/kotlin/demo/theme/RealmColor.kt b/MultiplatformDemoWithSync/jvmApp/src/main/kotlin/io/realm/kotlin/demo/theme/RealmColor.kt
new file mode 100644
index 0000000..0815366
--- /dev/null
+++ b/MultiplatformDemoWithSync/jvmApp/src/main/kotlin/io/realm/kotlin/demo/theme/RealmColor.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.theme
+
+import androidx.compose.ui.graphics.Color
+
+object RealmColor {
+ // Grays
+ val Charcoal = Color(0xFF1C233F)
+ val Elephant = Color(0xFF9A9BA5)
+ val Dov = Color(0xFFEBEBF2)
+
+ // Orb colors
+ val Ultramarine = Color(0xFF39477F)
+ val Indigo = Color(0xFF59569E)
+ val GrapeJelly = Color(0xFF9A59A5)
+ val Mulberry = Color(0xFFD34CA3)
+ val Flamingo = Color(0xFFF25192)
+ val SexySalmon = Color(0xFFF77C88)
+ val Peach = Color(0xFFFC9F95)
+ val Melon = Color(0xFFFCC397)
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/macosApp/Podfile b/MultiplatformDemoWithSync/macosApp/Podfile
new file mode 100644
index 0000000..84b1a28
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/Podfile
@@ -0,0 +1,5 @@
+target 'macosApp' do
+ use_frameworks!
+ platform :osx, '11.1'
+ pod 'shared', :path => '../shared'
+end
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/macosApp/Podfile.lock b/MultiplatformDemoWithSync/macosApp/Podfile.lock
new file mode 100644
index 0000000..3c15679
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/Podfile.lock
@@ -0,0 +1,16 @@
+PODS:
+ - shared (1.0)
+
+DEPENDENCIES:
+ - shared (from `../shared`)
+
+EXTERNAL SOURCES:
+ shared:
+ :path: "../shared"
+
+SPEC CHECKSUMS:
+ shared: 11463184695b113572ac0077e60cee9c9791894e
+
+PODFILE CHECKSUM: f3c0715e2cb2ef94e629cad90792d18c14dead0f
+
+COCOAPODS: 1.11.2
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.pbxproj b/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..415179e
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.pbxproj
@@ -0,0 +1,662 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 80A0D7C00AB8D4D744362775 /* Pods_macosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 335F78687BA807CBE8BD1E83 /* Pods_macosApp.framework */; };
+ 832C50A227067DDB0086207A /* MacOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C50A127067DDB0086207A /* MacOSApp.swift */; };
+ 832C50A427067DDB0086207A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C50A327067DDB0086207A /* ContentView.swift */; };
+ 832C50A627067DE20086207A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 832C50A527067DE20086207A /* Assets.xcassets */; };
+ 832C50A927067DE20086207A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 832C50A827067DE20086207A /* Preview Assets.xcassets */; };
+ 832C50B527067DE20086207A /* macosAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C50B427067DE20086207A /* macosAppTests.swift */; };
+ 832C50C027067DE20086207A /* macosAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C50BF27067DE20086207A /* macosAppUITests.swift */; };
+ 832C50EF27072F450086207A /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C50EE27072F450086207A /* ViewModel.swift */; };
+ 832C50FD270774FC0086207A /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C50FC270774FC0086207A /* Theme.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 832C50B127067DE20086207A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 832C509627067DDA0086207A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 832C509D27067DDA0086207A;
+ remoteInfo = macosApp;
+ };
+ 832C50BC27067DE20086207A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 832C509627067DDA0086207A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 832C509D27067DDA0086207A;
+ remoteInfo = macosApp;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 335F78687BA807CBE8BD1E83 /* Pods_macosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_macosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3EF55CD32EE8037A5A9B3E5A /* Pods-macosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macosApp.debug.xcconfig"; path = "Target Support Files/Pods-macosApp/Pods-macosApp.debug.xcconfig"; sourceTree = ""; };
+ 832C509E27067DDA0086207A /* macosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = macosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 832C50A127067DDB0086207A /* MacOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacOSApp.swift; sourceTree = ""; };
+ 832C50A327067DDB0086207A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ 832C50A527067DE20086207A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 832C50A827067DE20086207A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ 832C50AA27067DE20086207A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 832C50AB27067DE20086207A /* macosApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macosApp.entitlements; sourceTree = ""; };
+ 832C50B027067DE20086207A /* macosAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macosAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 832C50B427067DE20086207A /* macosAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = macosAppTests.swift; sourceTree = ""; };
+ 832C50B627067DE20086207A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 832C50BB27067DE20086207A /* macosAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macosAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 832C50BF27067DE20086207A /* macosAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = macosAppUITests.swift; sourceTree = ""; };
+ 832C50C127067DE20086207A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 832C50EE27072F450086207A /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; };
+ 832C50FC270774FC0086207A /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; };
+ AD037C024F0CB43D8054D8E6 /* Pods-macosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-macosApp.release.xcconfig"; path = "Target Support Files/Pods-macosApp/Pods-macosApp.release.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 832C509B27067DDA0086207A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 80A0D7C00AB8D4D744362775 /* Pods_macosApp.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C50AD27067DE20086207A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C50B827067DE20086207A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 349979C59F122842CF372E51 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 335F78687BA807CBE8BD1E83 /* Pods_macosApp.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 832C509527067DDA0086207A = {
+ isa = PBXGroup;
+ children = (
+ 832C50A027067DDA0086207A /* macosApp */,
+ 832C50B327067DE20086207A /* macosAppTests */,
+ 832C50BE27067DE20086207A /* macosAppUITests */,
+ 832C509F27067DDA0086207A /* Products */,
+ C59E600E717D53B4EE2A251F /* Pods */,
+ 349979C59F122842CF372E51 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 832C509F27067DDA0086207A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 832C509E27067DDA0086207A /* macosApp.app */,
+ 832C50B027067DE20086207A /* macosAppTests.xctest */,
+ 832C50BB27067DE20086207A /* macosAppUITests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 832C50A027067DDA0086207A /* macosApp */ = {
+ isa = PBXGroup;
+ children = (
+ 832C50EE27072F450086207A /* ViewModel.swift */,
+ 832C50A127067DDB0086207A /* MacOSApp.swift */,
+ 832C50A327067DDB0086207A /* ContentView.swift */,
+ 832C50FC270774FC0086207A /* Theme.swift */,
+ 832C50A527067DE20086207A /* Assets.xcassets */,
+ 832C50AA27067DE20086207A /* Info.plist */,
+ 832C50AB27067DE20086207A /* macosApp.entitlements */,
+ 832C50A727067DE20086207A /* Preview Content */,
+ );
+ path = macosApp;
+ sourceTree = "";
+ };
+ 832C50A727067DE20086207A /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ 832C50A827067DE20086207A /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+ 832C50B327067DE20086207A /* macosAppTests */ = {
+ isa = PBXGroup;
+ children = (
+ 832C50B427067DE20086207A /* macosAppTests.swift */,
+ 832C50B627067DE20086207A /* Info.plist */,
+ );
+ path = macosAppTests;
+ sourceTree = "";
+ };
+ 832C50BE27067DE20086207A /* macosAppUITests */ = {
+ isa = PBXGroup;
+ children = (
+ 832C50BF27067DE20086207A /* macosAppUITests.swift */,
+ 832C50C127067DE20086207A /* Info.plist */,
+ );
+ path = macosAppUITests;
+ sourceTree = "";
+ };
+ C59E600E717D53B4EE2A251F /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 3EF55CD32EE8037A5A9B3E5A /* Pods-macosApp.debug.xcconfig */,
+ AD037C024F0CB43D8054D8E6 /* Pods-macosApp.release.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 832C509D27067DDA0086207A /* macosApp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 832C50C427067DE20086207A /* Build configuration list for PBXNativeTarget "macosApp" */;
+ buildPhases = (
+ 45A259FAD301C419E0C3E7AD /* [CP] Check Pods Manifest.lock */,
+ 832C509A27067DDA0086207A /* Sources */,
+ 832C509B27067DDA0086207A /* Frameworks */,
+ 832C509C27067DDA0086207A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = macosApp;
+ productName = macosApp;
+ productReference = 832C509E27067DDA0086207A /* macosApp.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 832C50AF27067DE20086207A /* macosAppTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 832C50C727067DE20086207A /* Build configuration list for PBXNativeTarget "macosAppTests" */;
+ buildPhases = (
+ 832C50AC27067DE20086207A /* Sources */,
+ 832C50AD27067DE20086207A /* Frameworks */,
+ 832C50AE27067DE20086207A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 832C50B227067DE20086207A /* PBXTargetDependency */,
+ );
+ name = macosAppTests;
+ productName = macosAppTests;
+ productReference = 832C50B027067DE20086207A /* macosAppTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 832C50BA27067DE20086207A /* macosAppUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 832C50CA27067DE20086207A /* Build configuration list for PBXNativeTarget "macosAppUITests" */;
+ buildPhases = (
+ 832C50B727067DE20086207A /* Sources */,
+ 832C50B827067DE20086207A /* Frameworks */,
+ 832C50B927067DE20086207A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 832C50BD27067DE20086207A /* PBXTargetDependency */,
+ );
+ name = macosAppUITests;
+ productName = macosAppUITests;
+ productReference = 832C50BB27067DE20086207A /* macosAppUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 832C509627067DDA0086207A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1240;
+ LastUpgradeCheck = 1240;
+ TargetAttributes = {
+ 832C509D27067DDA0086207A = {
+ CreatedOnToolsVersion = 12.4;
+ };
+ 832C50AF27067DE20086207A = {
+ CreatedOnToolsVersion = 12.4;
+ TestTargetID = 832C509D27067DDA0086207A;
+ };
+ 832C50BA27067DE20086207A = {
+ CreatedOnToolsVersion = 12.4;
+ TestTargetID = 832C509D27067DDA0086207A;
+ };
+ };
+ };
+ buildConfigurationList = 832C509927067DDA0086207A /* Build configuration list for PBXProject "macosApp" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 832C509527067DDA0086207A;
+ productRefGroup = 832C509F27067DDA0086207A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 832C509D27067DDA0086207A /* macosApp */,
+ 832C50AF27067DE20086207A /* macosAppTests */,
+ 832C50BA27067DE20086207A /* macosAppUITests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 832C509C27067DDA0086207A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C50A927067DE20086207A /* Preview Assets.xcassets in Resources */,
+ 832C50A627067DE20086207A /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C50AE27067DE20086207A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C50B927067DE20086207A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 45A259FAD301C419E0C3E7AD /* [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-macosApp-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 */
+ 832C509A27067DDA0086207A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C50A427067DDB0086207A /* ContentView.swift in Sources */,
+ 832C50FD270774FC0086207A /* Theme.swift in Sources */,
+ 832C50EF27072F450086207A /* ViewModel.swift in Sources */,
+ 832C50A227067DDB0086207A /* MacOSApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C50AC27067DE20086207A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C50B527067DE20086207A /* macosAppTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 832C50B727067DE20086207A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 832C50C027067DE20086207A /* macosAppUITests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 832C50B227067DE20086207A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 832C509D27067DDA0086207A /* macosApp */;
+ targetProxy = 832C50B127067DE20086207A /* PBXContainerItemProxy */;
+ };
+ 832C50BD27067DE20086207A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 832C509D27067DDA0086207A /* macosApp */;
+ targetProxy = 832C50BC27067DE20086207A /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 832C50C227067DE20086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 11.1;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 832C50C327067DE20086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 11.1;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Release;
+ };
+ 832C50C527067DE20086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 3EF55CD32EE8037A5A9B3E5A /* Pods-macosApp.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = macosApp/macosApp.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_ASSET_PATHS = "\"macosApp/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = macosApp/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.1;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-l\"c++\"",
+ "-framework",
+ "\"shared\"",
+ "-undefined",
+ dynamic_lookup,
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.macosApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 832C50C627067DE20086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = AD037C024F0CB43D8054D8E6 /* Pods-macosApp.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = macosApp/macosApp.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_ASSET_PATHS = "\"macosApp/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = macosApp/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.1;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-l\"c++\"",
+ "-framework",
+ "\"shared\"",
+ "-undefined",
+ dynamic_lookup,
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.macosApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 832C50C827067DE20086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = macosAppTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.macosAppTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/macosApp.app/Contents/MacOS/macosApp";
+ };
+ name = Debug;
+ };
+ 832C50C927067DE20086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = macosAppTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 11.0;
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.macosAppTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/macosApp.app/Contents/MacOS/macosApp";
+ };
+ name = Release;
+ };
+ 832C50CB27067DE20086207A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = macosAppUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.macosAppUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = macosApp;
+ };
+ name = Debug;
+ };
+ 832C50CC27067DE20086207A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = macosAppUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = io.realm.kotlin.demo.macosAppUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = macosApp;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 832C509927067DDA0086207A /* Build configuration list for PBXProject "macosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C50C227067DE20086207A /* Debug */,
+ 832C50C327067DE20086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 832C50C427067DE20086207A /* Build configuration list for PBXNativeTarget "macosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C50C527067DE20086207A /* Debug */,
+ 832C50C627067DE20086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 832C50C727067DE20086207A /* Build configuration list for PBXNativeTarget "macosAppTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C50C827067DE20086207A /* Debug */,
+ 832C50C927067DE20086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 832C50CA27067DE20086207A /* Build configuration list for PBXNativeTarget "macosAppUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 832C50CB27067DE20086207A /* Debug */,
+ 832C50CC27067DE20086207A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 832C509627067DDA0086207A /* Project object */;
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp.xcworkspace/contents.xcworkspacedata b/MultiplatformDemoWithSync/macosApp/macosApp.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d889d1
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MultiplatformDemoWithSync/macosApp/macosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..3f00db4
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,58 @@
+{
+ "images" : [
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/Contents.json b/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/ContentView.swift b/MultiplatformDemoWithSync/macosApp/macosApp/ContentView.swift
new file mode 100644
index 0000000..a6f92a5
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/ContentView.swift
@@ -0,0 +1,80 @@
+//
+// ContentView.swift
+// macosApp
+//
+// Created by Christian Melchior on 24/09/2021.
+//
+import SwiftUI
+import shared
+
+struct Screen {
+ var width: CGFloat;
+ var height: CGFloat;
+}
+
+struct ContentView: View {
+ @ObservedObject var viewModel: MacOSCounterViewModel
+ var screen = Screen(width: 320, height: 500)
+ var body: some View {
+ ZStack {
+ Color.white
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height
+ )
+
+ VStack(spacing: 0) {
+ CounterButton(screen: screen, action:{
+ viewModel.increment()
+ })
+ CounterButton(screen: screen, action: {
+ viewModel.decrement()
+ })
+ }
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height
+ )
+
+ Text(viewModel.counter)
+ .fontWeight(.bold)
+ .font(.system(size: 150))
+
+ }
+ .onAppear {
+ viewModel.start()
+ }
+ .onDisappear {
+ viewModel.stop()
+ }
+ }
+}
+
+struct CounterButton: View {
+ var screen: Screen
+ var action: () -> Void
+ var body: some View {
+ Button {
+ action()
+ } label: {
+ RealmColor.mulberry
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height
+ )
+ }
+ .buttonStyle(PlainButtonStyle())
+ .frame(
+ minWidth: screen.width,
+ minHeight: screen.height,
+ alignment: .center
+ )
+ }
+}
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView(viewModel: MacOSCounterViewModel())
+ .previewDevice(PreviewDevice(rawValue: "Mac"))
+ }
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/Info.plist b/MultiplatformDemoWithSync/macosApp/macosApp/Info.plist
new file mode 100644
index 0000000..69c84ae
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/MacOSApp.swift b/MultiplatformDemoWithSync/macosApp/macosApp/MacOSApp.swift
new file mode 100644
index 0000000..b9bf727
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/MacOSApp.swift
@@ -0,0 +1,18 @@
+//
+// macosAppApp.swift
+// macosApp
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import SwiftUI
+
+@main
+struct MacOSApp: App {
+ let vm = MacOSCounterViewModel()
+ var body: some Scene {
+ WindowGroup(vm.platform(), id: "MainScreen") {
+ ContentView(viewModel: vm)
+ }
+ }
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/Preview Content/Preview Assets.xcassets/Contents.json b/MultiplatformDemoWithSync/macosApp/macosApp/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/Theme.swift b/MultiplatformDemoWithSync/macosApp/macosApp/Theme.swift
new file mode 100644
index 0000000..cf701d8
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/Theme.swift
@@ -0,0 +1,38 @@
+//
+// Theme.swift
+// macosApp
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import Foundation
+import SwiftUI
+
+// Credit: https://stackoverflow.com/a/56874327/1389357
+extension Color {
+ init(hex: UInt, alpha: Double = 1) {
+ self.init(
+ .sRGB,
+ red: Double((hex >> 16) & 0xff) / 255,
+ green: Double((hex >> 08) & 0xff) / 255,
+ blue: Double((hex >> 00) & 0xff) / 255,
+ opacity: alpha
+ )
+ }
+}
+
+struct RealmColor {
+ // Greys
+ static let charcoal = Color.init(hex: 0x1C233F)
+ static let elephant = Color.init(hex: 0x9A9BA5)
+ static let dov = Color.init(hex: 0xEBEBF2)
+
+ // Orb colors
+ static let ultramarine = Color.init(hex: 0x39477F)
+ static let indigo = Color.init(hex: 0x59569E)
+ static let grapeJelly = Color.init(hex: 0x9A59A5)
+ static let mulberry = Color.init(hex: 0xD34CA3)
+ static let flamingo = Color.init(hex: 0xF25192)
+ static let sexySalmon = Color.init(hex: 0xFC9F95)
+ static let melon = Color.init(hex: 0xFCC397)
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/ViewModel.swift b/MultiplatformDemoWithSync/macosApp/macosApp/ViewModel.swift
new file mode 100644
index 0000000..4144a10
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/ViewModel.swift
@@ -0,0 +1,51 @@
+//
+// ViewModel.swift
+// macosApp
+//
+// Created by Christian Melchior on 24/09/2021.
+//
+import Foundation
+import Combine
+import shared
+
+// Generic Observable View Model, making it easier to control the lifecycle
+// of multiple Flows.
+class ObservableViewModel {
+ private var jobs = Array() // List of Kotlin Coroutine Jobs
+
+ func addObserver(observer: Closeable) {
+ jobs.append(observer)
+ }
+
+ func stop() {
+ jobs.forEach { job in job.close() }
+ }
+}
+
+class MacOSCounterViewModel: ObservableViewModel, ObservableObject {
+ @Published var counter: String = "-"
+ private let vm: SharedCounterViewModel = SharedCounterViewModel()
+
+ func platform() -> String {
+ return vm.platform
+ }
+
+ func increment() {
+ vm.increment()
+ }
+
+ func decrement() {
+ vm.decrement()
+ }
+
+ func start() {
+ addObserver(observer: vm.observeCounter().watch { counterValue in
+ self.counter = counterValue! as String
+ })
+ }
+
+ override func stop() {
+ super.stop()
+ vm.close()
+ }
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosApp/macosApp.entitlements b/MultiplatformDemoWithSync/macosApp/macosApp/macosApp.entitlements
new file mode 100644
index 0000000..40b639e
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosApp/macosApp.entitlements
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.files.user-selected.read-only
+
+ com.apple.security.network.client
+
+ com.apple.security.network.server
+
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosAppTests/Info.plist b/MultiplatformDemoWithSync/macosApp/macosAppTests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosAppTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosAppTests/macosAppTests.swift b/MultiplatformDemoWithSync/macosApp/macosAppTests/macosAppTests.swift
new file mode 100644
index 0000000..62c0eaf
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosAppTests/macosAppTests.swift
@@ -0,0 +1,33 @@
+//
+// macosAppTests.swift
+// macosAppTests
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import XCTest
+@testable import macosApp
+
+class macosAppTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() throws {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ }
+
+ func testPerformanceExample() throws {
+ // This is an example of a performance test case.
+ self.measure {
+ // Put the code you want to measure the time of here.
+ }
+ }
+
+}
diff --git a/MultiplatformDemoWithSync/macosApp/macosAppUITests/Info.plist b/MultiplatformDemoWithSync/macosApp/macosAppUITests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosAppUITests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/MultiplatformDemoWithSync/macosApp/macosAppUITests/macosAppUITests.swift b/MultiplatformDemoWithSync/macosApp/macosAppUITests/macosAppUITests.swift
new file mode 100644
index 0000000..36f592b
--- /dev/null
+++ b/MultiplatformDemoWithSync/macosApp/macosAppUITests/macosAppUITests.swift
@@ -0,0 +1,42 @@
+//
+// macosAppUITests.swift
+// macosAppUITests
+//
+// Created by Christian Melchior on 01/10/2021.
+//
+
+import XCTest
+
+class macosAppUITests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ continueAfterFailure = false
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() throws {
+ // UI tests must launch the application that they test.
+ let app = XCUIApplication()
+ app.launch()
+
+ // Use recording to get started writing UI tests.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ }
+
+ func testLaunchPerformance() throws {
+ if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
+ // This measures how long it takes to launch your application.
+ measure(metrics: [XCTApplicationLaunchMetric()]) {
+ XCUIApplication().launch()
+ }
+ }
+ }
+}
diff --git a/MultiplatformDemoWithSync/settings.gradle.kts b/MultiplatformDemoWithSync/settings.gradle.kts
new file mode 100644
index 0000000..3760fba
--- /dev/null
+++ b/MultiplatformDemoWithSync/settings.gradle.kts
@@ -0,0 +1,13 @@
+pluginManagement {
+ repositories {
+ google()
+ gradlePluginPortal()
+ mavenCentral()
+ maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ }
+}
+
+rootProject.name = "Realm Kotlin Multiplatform Demo"
+include(":androidApp")
+include(":jvmApp")
+include(":shared")
diff --git a/MultiplatformDemoWithSync/shared/build.gradle.kts b/MultiplatformDemoWithSync/shared/build.gradle.kts
new file mode 100644
index 0000000..9991ea7
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/build.gradle.kts
@@ -0,0 +1,70 @@
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
+
+plugins {
+ kotlin("multiplatform")
+ kotlin("native.cocoapods")
+ id("com.android.library")
+ id("io.realm.kotlin") version "0.8.2"
+}
+
+version = "1.0"
+
+kotlin {
+ android()
+
+ val iosTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget = when {
+ System.getenv("SDK_NAME")?.startsWith("iphoneos") == true -> ::iosArm64
+ else -> ::iosX64
+ }
+
+ iosTarget("ios") {}
+ macosX64("macos") {}
+ jvm {}
+
+ cocoapods {
+ summary = "Realm Kotlin Multiplatform Demo Shared Library"
+ homepage = "https://github.com/realm/realm-kotlin"
+ ios.deploymentTarget = "14.1"
+ osx.deploymentTarget = "11.0"
+ frameworkName = "shared"
+ }
+
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt")
+ implementation("io.realm.kotlin:library-sync:0.8.2")
+ }
+ }
+ val commonTest by getting {
+ dependencies {
+ implementation(kotlin("test-common"))
+ implementation(kotlin("test-annotations-common"))
+ }
+ }
+ val androidMain by getting
+ val androidAndroidTestRelease by getting
+ val androidTest by getting {
+ dependsOn(androidAndroidTestRelease)
+ dependencies {
+ implementation(kotlin("test-junit"))
+ implementation("junit:junit:4.13.2")
+ }
+ }
+
+ val iosMain by getting
+ val iosTest by getting
+ val macosMain by getting
+ val macosTest by getting
+ val jvmMain by getting
+ }
+}
+
+android {
+ compileSdk= 30
+ sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
+ defaultConfig {
+ minSdk = 21
+ targetSdk = 30
+ }
+}
diff --git a/MultiplatformDemoWithSync/shared/shared.podspec b/MultiplatformDemoWithSync/shared/shared.podspec
new file mode 100644
index 0000000..7d3cbd2
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/shared.podspec
@@ -0,0 +1,43 @@
+Pod::Spec.new do |spec|
+ spec.name = 'shared'
+ spec.version = '1.0'
+ spec.homepage = 'https://github.com/realm/realm-kotlin'
+ spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
+ spec.authors = ''
+ spec.license = ''
+ spec.summary = 'Realm Kotlin Multiplatform Demo Shared Library'
+
+ spec.vendored_frameworks = "build/cocoapods/framework/shared.framework"
+ spec.libraries = "c++"
+ spec.module_name = "#{spec.name}_umbrella"
+
+ spec.ios.deployment_target = '14.1'
+ spec.osx.deployment_target = '11.0'
+
+
+
+ spec.pod_target_xcconfig = {
+ 'KOTLIN_PROJECT_PATH' => ':shared',
+ 'PRODUCT_MODULE_NAME' => 'shared',
+ }
+
+ spec.script_phases = [
+ {
+ :name => 'Build shared',
+ :execution_position => :before_compile,
+ :shell_path => '/bin/sh',
+ :script => <<-SCRIPT
+ if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then
+ echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\""
+ exit 0
+ fi
+ set -ev
+ REPO_ROOT="$PODS_TARGET_SRCROOT"
+ "$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
+ SCRIPT
+ }
+ ]
+end
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/androidMain/AndroidManifest.xml b/MultiplatformDemoWithSync/shared/src/androidMain/AndroidManifest.xml
new file mode 100644
index 0000000..54d11c8
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/androidMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt b/MultiplatformDemoWithSync/shared/src/androidMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
new file mode 100644
index 0000000..677d5fb
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/androidMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+actual class Platform actual constructor() {
+ actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/model/CounterRepository.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/model/CounterRepository.kt
new file mode 100644
index 0000000..0a3b1c5
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/model/CounterRepository.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.model
+
+import io.realm.Realm
+import io.realm.internal.platform.runBlocking
+import io.realm.kotlin.demo.model.entity.Counter
+import io.realm.kotlin.demo.util.Constants.MONGODB_REALM_APP_ID
+import io.realm.kotlin.demo.util.Constants.MONGODB_REALM_APP_PASSWORD
+import io.realm.kotlin.demo.util.Constants.MONGODB_REALM_APP_USER
+import io.realm.log.LogLevel
+import io.realm.mongodb.App
+import io.realm.mongodb.AppConfiguration
+import io.realm.mongodb.Credentials
+import io.realm.mongodb.SyncConfiguration
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Repository class. Responsible for storing the io.realm.kotlin.demo.model.entity.Counter and expose updates to it.
+ */
+class CounterRepository {
+ private var realm: Realm
+ private val counterObj: Counter
+
+ private val app: App = App.create(AppConfiguration.Builder(MONGODB_REALM_APP_ID).build())
+
+ init {
+ realm = runBlocking {
+ // Enable Realm with Sync support
+ val user = app.login(Credentials.emailPassword(MONGODB_REALM_APP_USER, MONGODB_REALM_APP_PASSWORD))
+ val config = SyncConfiguration.Builder(
+ schema = setOf(Counter::class),
+ user = user,
+ partitionValue = "demo-parition",
+ )
+ .log(LogLevel.ALL)
+ .build()
+
+ Realm.open(config)
+ }
+
+ // With no support for setting up initial values, we just do it manually.
+ // WARNING: Writing directly on the UI thread is not encouraged.
+ counterObj = realm.writeBlocking {
+ val objects = objects(Counter::class)
+ when (objects.size) {
+ 0 -> copyToRealm(Counter())
+ 1 -> objects.first()
+ else -> throw IllegalStateException("Too many counters: ${objects.size}")
+ }
+ }
+ }
+
+ /**
+ * Adjust the counter up and down.
+ */
+ fun adjust(change: Int) {
+ CoroutineScope(Dispatchers.Default).launch {
+ realm.write {
+ findLatest(counterObj)?.run {
+ operations.add(change)
+ } ?: println("Could not update io.realm.kotlin.demo.model.entity.Counter")
+ }
+ }
+ }
+
+ /**
+ * Listen to changes to the counter.
+ */
+ fun observeCounter(): Flow {
+ return realm.objects(Counter::class).query("_id = 'primary'").observe()
+ .filter { it.size == 1 }
+ .map { it.first() }
+ .map {
+ it.operations.fold(0L,) { sum, el -> sum + el }
+ }
+ }
+}
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/model/entity/Counter.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/model/entity/Counter.kt
new file mode 100644
index 0000000..edbaf10
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/model/entity/Counter.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.model.entity
+
+import io.realm.RealmList
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+import io.realm.realmListOf
+
+class Counter: RealmObject {
+ @PrimaryKey
+ var _id: String = "primary"
+ var realm_id: String? = "demo-parition"
+ var operations: RealmList = realmListOf()
+}
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/SharedViewModel.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/SharedViewModel.kt
new file mode 100644
index 0000000..f3a3fc1
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/SharedViewModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui
+
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+
+/**
+ * Interface shared between all ViewModels.
+ * This is used to to have a common way of interacting with ViewModels.
+ */
+interface SharedViewModel {
+
+ // Instead of using e.g. `viewModelScope` from Android, we construct our own.
+ // This way, the scope is shared between iOS and Android and its lifecycle
+ // is controlled the same way.
+ val scope
+ get() = CoroutineScope(CoroutineName(""))
+
+ /**
+ * Cancels the current scope and any jobs in it.
+ * This should be called by the UI when it no longer need the
+ * ViewModel.
+ */
+ fun close() {
+ scope.cancel()
+ }
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/CounterViewModel.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/CounterViewModel.kt
new file mode 100644
index 0000000..7f05ee5
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/CounterViewModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+import io.realm.kotlin.demo.ui.SharedViewModel
+import io.realm.kotlin.demo.util.CommonFlow
+
+/**
+ * Interface describing the ViewModel on both the `shared` and `platform` side.
+ */
+interface CounterViewModel: SharedViewModel {
+ val platform: String
+ get() = Platform().platform
+ fun observeCounter(): CommonFlow
+ fun increment()
+ fun decrement()
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
new file mode 100644
index 0000000..51d5be5
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+expect class Platform() {
+ val platform: String
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/SharedCounterViewModel.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/SharedCounterViewModel.kt
new file mode 100644
index 0000000..91c5369
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/ui/counter/SharedCounterViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+import io.realm.kotlin.demo.model.CounterRepository
+import io.realm.kotlin.demo.util.CommonFlow
+import io.realm.kotlin.demo.util.asCommonFlow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Class for the shared parts of the ViewModel.
+ *
+ * ViewModels are split into two parts:
+ * - `SharedViewModel`, which contains the business logic and communication with
+ * the repository / model layer.
+ * - `PlatformViewModel`, which is only a thin wrapper for hooking the SharedViewModel
+ * up to either SwiftUI (through `@ObservedObject`) or to Compose (though Flows).
+ *
+ * The boundary between these two classes must only be [CommonFlow]'s, which emit
+ * on the UI or Main thread.
+ *
+ * This allows the UI to be fully tested by injecting a mocked ViewModel on the
+ * platform side.
+ */
+class SharedCounterViewModel: CounterViewModel {
+
+ // Implementation note: With a ViewModel this simple, just merging it with
+ // Repository would probably be simpler, but by splitting the Repository
+ // and ViewModel, we only need to enforce CommonFlows at the boundary, and
+ // it means the CounterViewModel can be mocked easily in the View Layer.
+ private val repository = CounterRepository()
+
+ override fun observeCounter(): CommonFlow {
+ return repository.observeCounter()
+ .map { count -> count.toString() }
+ .asCommonFlow()
+ }
+
+ override fun increment() {
+ repository.adjust(1)
+ }
+
+ override fun decrement() {
+ repository.adjust(-1)
+ }
+}
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Closeable.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Closeable.kt
new file mode 100644
index 0000000..06a6bdb
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Closeable.kt
@@ -0,0 +1,7 @@
+package io.realm.kotlin.demo.util
+
+// Remove when Kotlin's Closeable is supported in K/N https://youtrack.jetbrains.com/issue/KT-31066
+// Alternatively use Ktor Closeable which is K/N ready.
+interface Closeable {
+ fun close()
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Constants.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Constants.kt
new file mode 100644
index 0000000..b46bac0
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/Constants.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.util
+
+/**
+ * Replace with your own App's credentials to enable Sync.
+ * To setup the Realm Sync App on MongoDB Atlas follow the steps here
+ * https://docs.mongodb.com/realm/tutorial/realm-app/ or watch the video tutorial https://www.youtube.com/watch?v=lqo0Yf7lnyg
+ */
+object Constants {
+ val MONGODB_REALM_APP_ID = "[REPLACE ME]"
+ val MONGODB_REALM_APP_USER = "[REPLACE ME]"
+ val MONGODB_REALM_APP_PASSWORD = "[REPLACE ME]"
+}
diff --git a/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/FlowUtil.kt b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/FlowUtil.kt
new file mode 100644
index 0000000..6b0591d
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/commonMain/kotlin/io/realm/kotlin/demo/util/FlowUtil.kt
@@ -0,0 +1,29 @@
+package io.realm.kotlin.demo.util
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Wrapper to consume Flow based API from Obj-C/Swift
+ * Credit - https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/mobileMain/kotlin/org/jetbrains/kotlinconf/FlowUtils.kt
+ */
+class CommonFlow(private val origin: Flow) : Flow by origin {
+ fun watch(block: (T) -> Unit): Closeable {
+ val job = Job()
+ onEach {
+ block(it)
+ }.launchIn(CoroutineScope(Dispatchers.Main + job))
+
+ return object : Closeable {
+ override fun close() {
+ job.cancel()
+ }
+ }
+ }
+}
+// Helper extension
+internal fun Flow.asCommonFlow(): CommonFlow = CommonFlow(this)
diff --git a/MultiplatformDemoWithSync/shared/src/iosMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt b/MultiplatformDemoWithSync/shared/src/iosMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
new file mode 100644
index 0000000..faaa119
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/iosMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+import platform.UIKit.UIDevice
+
+actual class Platform actual constructor() {
+ actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/jvmMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt b/MultiplatformDemoWithSync/shared/src/jvmMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
new file mode 100644
index 0000000..615db68
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/jvmMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+actual class Platform actual constructor() {
+ actual val platform: String = "JVM (${System.getProperty("os.name")})"
+}
\ No newline at end of file
diff --git a/MultiplatformDemoWithSync/shared/src/macosMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt b/MultiplatformDemoWithSync/shared/src/macosMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
new file mode 100644
index 0000000..796bf1f
--- /dev/null
+++ b/MultiplatformDemoWithSync/shared/src/macosMain/kotlin/io/realm/kotlin/demo/ui/counter/Platform.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 Realm Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.realm.kotlin.demo.ui.counter
+
+import platform.Foundation.NSProcessInfo
+
+actual class Platform actual constructor() {
+ actual val platform: String = NSProcessInfo.processInfo.operatingSystemVersionString
+}
\ No newline at end of file