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