From 4753dbf6f15d5b23d7cf9dce38dd2c64687b0b34 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Thu, 6 Oct 2022 11:48:12 +0200 Subject: [PATCH 001/227] Upgrading to Kotlin 1.7 --- .../kotlin/observable/Observable.kt | 3 +- .../iosMain/kotlin/navigation/Navigator.kt | 20 +++++----- .../kotlin/scanner/ScanningState.kt | 4 +- .../device/DefaultDeviceConnectionManager.kt | 11 ++---- .../datetime/timer/RecurringTimerTest.kt | 2 +- gradle.properties | 6 ++- gradle/ext.gradle | 18 ++++----- resources/src/iosMain/kotlin/KalugaColor.kt | 2 +- .../device/MockDeviceConnectionManager.kt | 38 ++++++++++++------- 9 files changed, 56 insertions(+), 48 deletions(-) diff --git a/architecture/src/commonMain/kotlin/observable/Observable.kt b/architecture/src/commonMain/kotlin/observable/Observable.kt index fc4c45ed0..cbad22e80 100644 --- a/architecture/src/commonMain/kotlin/observable/Observable.kt +++ b/architecture/src/commonMain/kotlin/observable/Observable.kt @@ -97,10 +97,11 @@ open class ObservationDefault( ) : Observation>(initialValue), ReadWriteProperty, MutableDefaultInitialized { + constructor( defaultValue: R, initialValue: Value - ) : this(Value(defaultValue), initialValue) + ) : this(Value(defaultValue), initialValue) override fun getValue(thisRef: Any?, property: KProperty<*>): R = current override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { diff --git a/architecture/src/iosMain/kotlin/navigation/Navigator.kt b/architecture/src/iosMain/kotlin/navigation/Navigator.kt index 9cc2ac9e8..ae4c04205 100644 --- a/architecture/src/iosMain/kotlin/navigation/Navigator.kt +++ b/architecture/src/iosMain/kotlin/navigation/Navigator.kt @@ -153,21 +153,19 @@ class ViewControllerNavigator>( private fun embedNestedViewController(nestedSpec: NavigationSpec.Nested) { val parent = assertParent() ?: return - when (val type = nestedSpec.type) { - is NavigationSpec.Nested.Type.Replace -> { - parent.childViewControllers.map { it as UIViewController }.filter { it.view.tag.toLong() == type.tag }.forEach { - it.willMoveToParentViewController(null) - it.view.removeFromSuperview() - it.removeFromParentViewController() - } + val type = nestedSpec.type + if (type is NavigationSpec.Nested.Type.Replace) { + parent.childViewControllers.map { it as UIViewController }.filter { it.view.tag.toLong() == type.tag }.forEach { + it.willMoveToParentViewController(null) + it.view.removeFromSuperview() + it.removeFromParentViewController() } } val child = nestedSpec.nested() - when (val type = nestedSpec.type) { - is NavigationSpec.Nested.Type.Replace -> { - child.view.tag = type.tag as NSInteger - } + + if (type is NavigationSpec.Nested.Type.Replace) { + child.view.tag = type.tag as NSInteger } child.view.translatesAutoresizingMaskIntoConstraints = false parent.addChildViewController(child) diff --git a/bluetooth/src/commonMain/kotlin/scanner/ScanningState.kt b/bluetooth/src/commonMain/kotlin/scanner/ScanningState.kt index c976b71d9..8fafcf011 100644 --- a/bluetooth/src/commonMain/kotlin/scanner/ScanningState.kt +++ b/bluetooth/src/commonMain/kotlin/scanner/ScanningState.kt @@ -151,7 +151,7 @@ sealed class ScanningStateImpl { is ScanningState.Inactive -> { scanner.startMonitoringPermissions() } - is Active, is NoHardware -> {} + is ScanningState.Active, is ScanningState.NoHardware -> {} } } @@ -160,7 +160,7 @@ sealed class ScanningStateImpl { is ScanningState.Inactive -> { scanner.stopMonitoringPermissions() } - is Active, is NoHardware -> {} + is ScanningState.Active, is ScanningState.NoHardware -> {} } } abstract val scanner: Scanner diff --git a/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt b/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt index 79984a5df..e6c65f0b7 100644 --- a/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt +++ b/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt @@ -80,13 +80,10 @@ internal actual class DefaultDeviceConnectionManager( } override fun peripheral(peripheral: CBPeripheral, didUpdateNotificationStateForCharacteristic: CBCharacteristic, error: NSError?) { - when (val action = currentAction) { - is DeviceAction.Notification -> { - if (action.characteristic.wrapper.uuid == didUpdateNotificationStateForCharacteristic.UUID) { - launch { - handleCurrentActionCompleted(succeeded = error == null) - } - } + val action = currentAction + if (action is DeviceAction.Notification && action.characteristic.wrapper.uuid == didUpdateNotificationStateForCharacteristic.UUID) { + launch { + handleCurrentActionCompleted(succeeded = error == null) } } } diff --git a/date-time/src/commonTest/kotlin/com/splendo/kaluga/datetime/timer/RecurringTimerTest.kt b/date-time/src/commonTest/kotlin/com/splendo/kaluga/datetime/timer/RecurringTimerTest.kt index bafb107f9..7feda2432 100644 --- a/date-time/src/commonTest/kotlin/com/splendo/kaluga/datetime/timer/RecurringTimerTest.kt +++ b/date-time/src/commonTest/kotlin/com/splendo/kaluga/datetime/timer/RecurringTimerTest.kt @@ -111,7 +111,7 @@ class RecurringTimerTest { /** Provides mock time ticks. */ private class PredefinedTimeSource(val ticks: List) : TimeSource { override fun markNow(): TimeMark = - object : TimeMark() { + object : TimeMark { var index = 0 override fun elapsedNow(): Duration = if (index < ticks.size) { diff --git a/gradle.properties b/gradle.properties index 11811e7e1..e9c1f92c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,14 +5,16 @@ kotlin.mpp.stability.nowarn=true android.useAndroidX=true kaluga.androidGradlePluginVersion=7.3.0 -kaluga.kotlinVersion=1.6.10 +kaluga.kotlinVersion=1.7.20 kaluga.googleServicesGradlePluginVersion=4.3.10 -kaluga.ktLintGradlePluginVersion=10.1.0 +kaluga.ktLintGradlePluginVersion=11.0.0 org.gradle.jvmargs=-Xmx3048M -Dkotlin.daemon.jvm.options\="-Xmx3048M" #MPP kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.native.enableDependencyPropagation=false +# TODO: Disable +kotlin.native.binary.memoryModel=strict kotlin.mpp.enableCInteropCommonization=true ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## diff --git a/gradle/ext.gradle b/gradle/ext.gradle index aa62adb6b..bbfce4c85 100644 --- a/gradle/ext.gradle +++ b/gradle/ext.gradle @@ -23,14 +23,14 @@ String kotlinVersion = getProperty("kaluga.kotlinVersion") // set some global variables gradle.ext { kotlin_version = kotlinVersion - kotlinx_coroutines_version = '1.6.1-native-mt' - stately_version = '1.2.1' - stately_isolate_version = '1.2.1' - koin_version = '3.2.0-beta-1' - serialization_version = '1.3.2' + kotlinx_coroutines_version = '1.6.3-native-mt' + stately_version = '1.2.3' + stately_isolate_version = '1.2.3' + koin_version = '3.2.2' + serialization_version = '1.4.0' napier_version = '2.4.0' android_ble_scanner_version = '1.6.0' - library_version_base = '0.5.0' + library_version_base = '1.0.0' library_version = libraryVersionLocalProperties ?: "$library_version_base${System.properties.kaluga_branch_postfix}" android_min_sdk_version = 21 android_compile_sdk_version = 33 @@ -50,10 +50,10 @@ gradle.ext { androidx_browser_version = "1.4.0" androidx_constraint_layout_version = "2.1.4" - androidx_compose_compiler_version = "1.1.1" + androidx_compose_compiler_version = "1.3.2" androidx_compose_version = "1.2.1" - androidx_activity_compose_version = "1.5.1" - androidx_navigation_compose_version = "2.5.1" + androidx_activity_compose_version = "1.6.0" + androidx_navigation_compose_version = "2.5.2" material_version = "1.6.1" material_components_adapter_version = "1.1.17" diff --git a/resources/src/iosMain/kotlin/KalugaColor.kt b/resources/src/iosMain/kotlin/KalugaColor.kt index ecb18a5e3..704d0dfbe 100644 --- a/resources/src/iosMain/kotlin/KalugaColor.kt +++ b/resources/src/iosMain/kotlin/KalugaColor.kt @@ -31,7 +31,7 @@ actual val KalugaColor.green: Double get() = CGColorGetComponents(uiColor.CGColo actual val KalugaColor.blueInt: Int get() = (blue * 255.0).toInt() actual val KalugaColor.blue: Double get() = CGColorGetComponents(uiColor.CGColor)?.get(2)?.toDouble() ?: 0.0 actual val KalugaColor.greenInt: Int get() = (green * 255.0).toInt() -actual val KalugaColor.alpha: Double get() = CGColorGetAlpha(uiColor.CGColor)?.toDouble() +actual val KalugaColor.alpha: Double get() = CGColorGetAlpha(uiColor.CGColor).toDouble() actual val KalugaColor.alphaInt: Int get() = (alpha * 255.0).toInt() actual fun colorFrom(red: Double, green: Double, blue: Double, alpha: Double): KalugaColor = KalugaColor(UIColor.colorWithRed(red as CGFloat, green as CGFloat, blue as CGFloat, alpha as CGFloat)) diff --git a/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt b/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt index 59af7351b..eb5d7869b 100644 --- a/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt +++ b/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt @@ -178,22 +178,32 @@ class MockDeviceConnectionManager( override suspend fun performAction(action: DeviceAction): Unit = performActionMock.call(action) suspend fun handleCurrentAction() { - when (val action = currentAction) { - is DeviceAction.Read.Characteristic -> handleUpdatedCharacteristic(action.characteristic.uuid, willActionSucceed) { - debug("Mock Read: ${action.characteristic.uuid} value ${action.characteristic.wrapper.value?.asBytes?.toHexString()}") - } - is DeviceAction.Read.Descriptor -> handleUpdatedDescriptor(action.descriptor.uuid, willActionSucceed) - is DeviceAction.Write.Characteristic -> { - (action.characteristic.wrapper as MockCharacteristicWrapper).updateMockValue(action.newValue) - handleUpdatedCharacteristic(action.characteristic.uuid, willActionSucceed) { - debug("Mock Write: ${action.characteristic.uuid} value ${action.characteristic.wrapper.value?.asBytes?.toHexString()}") + currentAction?.let { action -> + when (action) { + is DeviceAction.Read.Characteristic -> handleUpdatedCharacteristic( + action.characteristic.uuid, + willActionSucceed + ) { + debug("Mock Read: ${action.characteristic.uuid} value ${action.characteristic.wrapper.value?.asBytes?.toHexString()}") } + is DeviceAction.Read.Descriptor -> handleUpdatedDescriptor( + action.descriptor.uuid, + willActionSucceed + ) + is DeviceAction.Write.Characteristic -> { + (action.characteristic.wrapper as MockCharacteristicWrapper).updateMockValue( + action.newValue + ) + handleUpdatedCharacteristic(action.characteristic.uuid, willActionSucceed) { + debug("Mock Write: ${action.characteristic.uuid} value ${action.characteristic.wrapper.value?.asBytes?.toHexString()}") + } + } + is DeviceAction.Write.Descriptor -> { + (action.descriptor.wrapper as MockDescriptorWrapper).updateMockValue(action.newValue) + handleUpdatedDescriptor(action.descriptor.uuid, willActionSucceed) + } + is DeviceAction.Notification -> handleCurrentActionCompleted(willActionSucceed) } - is DeviceAction.Write.Descriptor -> { - (action.descriptor.wrapper as MockDescriptorWrapper).updateMockValue(action.newValue) - handleUpdatedDescriptor(action.descriptor.uuid, willActionSucceed) - } - is DeviceAction.Notification -> handleCurrentActionCompleted(willActionSucceed) } } From b00872a753157dd743d14622cf60fd7ee201faf0 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 7 Oct 2022 17:49:56 +0200 Subject: [PATCH 002/227] WIP gradle migration to kts --- .github/workflows/emulator.yaml | 2 +- DEVELOP.md | 4 +- adding-a-new-module/template/build.gradle.kts | 2 +- alerts/build.gradle.kts | 16 +- architecture-compose/build.gradle.kts | 2 +- architecture/build.gradle.kts | 2 +- base-permissions/build.gradle.kts | 2 +- base/build.gradle.kts | 2 +- beacons/build.gradle.kts | 2 +- bluetooth-permissions/build.gradle.kts | 2 +- bluetooth/build.gradle.kts | 2 +- build.gradle | 69 ------- build.gradle.kts | 73 ++++++++ buildSrc/build.gradle.kts | 16 ++ buildSrc/settings/gradle.kts | 54 ++++++ buildSrc/src/main/kotlin/AndroidCommon.kt | 107 +++++++++++ buildSrc/src/main/kotlin/Component.kt | 142 ++++++++++++++ buildSrc/src/main/kotlin/Dependencies.kt | 164 ++++++++++++++++ buildSrc/src/main/kotlin/GitBranch.kt | 52 +++++ buildSrc/src/main/kotlin/Library.kt | 138 ++++++++++++++ calendar-permissions/build.gradle.kts | 2 +- camera-permissions/build.gradle.kts | 2 +- contacts-permissions/build.gradle.kts | 2 +- convention-plugins/build.gradle.kts | 2 +- date-time-picker/build.gradle.kts | 2 +- date-time/build.gradle.kts | 2 +- example/android/build.gradle | 2 +- example/android/proguard-rules.pro | 2 +- .../ios/Supporting Files/settings.gradle.kts | 2 +- example/shared/build.gradle.kts | 2 +- gradle/android_common.gradle | 112 ----------- gradle/android_common.gradle.kts | 128 +++++++++++++ gradle/android_compose.gradle | 42 ----- gradle/android_compose.gradle.kts | 49 +++++ gradle/component.gradle | 177 ------------------ gradle/component.gradle.kts | 55 ++++++ gradle/componentskt.gradle.kts | 28 +-- gradle/ext.gradle | 140 -------------- gradle/ext.gradle.kts | 157 ++++++++++++++++ gradle/gitBranch.gradle.kts | 62 +++--- gradle/newModule.gradle.kts | 2 +- gradle/publish.gradle | 33 ---- gradle/publish.gradle.kts | 32 ++++ gradle/publishable_component.gradle | 3 - gradle/publishable_component.gradle.kts | 3 + hud/build.gradle.kts | 2 +- keyboard/build.gradle.kts | 2 +- links/build.gradle.kts | 2 +- location-permissions/build.gradle.kts | 2 +- location/build.gradle.kts | 2 +- logging/build.gradle.kts | 2 +- microphone-permissions/build.gradle.kts | 2 +- notifications-permissions/build.gradle.kts | 2 +- permissions/build.gradle.kts | 2 +- resources/build.gradle.kts | 2 +- review/build.gradle.kts | 2 +- scientific/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- storage-permissions/build.gradle.kts | 2 +- system/build.gradle.kts | 2 +- test-utils-alerts/build.gradle.kts | 2 +- test-utils-architecture/build.gradle.kts | 2 +- test-utils-base/build.gradle.kts | 2 +- test-utils-bluetooth/build.gradle.kts | 2 +- test-utils-hud/build.gradle.kts | 2 +- test-utils-keyboard/build.gradle.kts | 2 +- test-utils-koin/build.gradle.kts | 2 +- test-utils-location/build.gradle.kts | 2 +- test-utils-permissions/build.gradle.kts | 2 +- test-utils-resources/build.gradle.kts | 2 +- test-utils/build.gradle.kts | 2 +- 71 files changed, 1270 insertions(+), 678 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/settings/gradle.kts create mode 100644 buildSrc/src/main/kotlin/AndroidCommon.kt create mode 100644 buildSrc/src/main/kotlin/Component.kt create mode 100644 buildSrc/src/main/kotlin/Dependencies.kt create mode 100644 buildSrc/src/main/kotlin/GitBranch.kt create mode 100644 buildSrc/src/main/kotlin/Library.kt delete mode 100644 gradle/android_common.gradle create mode 100644 gradle/android_common.gradle.kts delete mode 100644 gradle/android_compose.gradle create mode 100644 gradle/android_compose.gradle.kts delete mode 100644 gradle/component.gradle create mode 100644 gradle/component.gradle.kts delete mode 100644 gradle/ext.gradle create mode 100644 gradle/ext.gradle.kts delete mode 100644 gradle/publish.gradle create mode 100644 gradle/publish.gradle.kts delete mode 100644 gradle/publishable_component.gradle create mode 100644 gradle/publishable_component.gradle.kts diff --git a/.github/workflows/emulator.yaml b/.github/workflows/emulator.yaml index 66f5a8305..bf2c94cb4 100644 --- a/.github/workflows/emulator.yaml +++ b/.github/workflows/emulator.yaml @@ -1,4 +1,4 @@ -#### for keeping up to date with settings.gradle.kts see instructions in `test:` job +#### for keeping up to date with settings.build.gradle.kts see instructions in `test:` job name: Android Emulator tests on: pull_request env: diff --git a/DEVELOP.md b/DEVELOP.md index 665935e71..fdca1ea6f 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -1,6 +1,6 @@ ## Build instructions -This project uses Android Studio. You might need a canary version at times. Check [gradle/ext.gradle](gradle/ext.gradle) to see which Android gradle plugin is used, as this determines compatability with Android Studio versions. You can check the [Android Gradle plugin release notes](https://developer.android.com/studio/releases/gradle-plugin), the [Android Studio release notes](https://developer.android.com/studio/releases) and the [Android Studio Preview Release Updates](https://androidstudio.googleblog.com/) pages to see which version to use. +This project uses Android Studio. You might need a canary version at times. Check [gradle/ext.gradle](gradle/ext.gradle.kts) to see which Android gradle plugin is used, as this determines compatability with Android Studio versions. You can check the [Android Gradle plugin release notes](https://developer.android.com/studio/releases/gradle-plugin), the [Android Studio release notes](https://developer.android.com/studio/releases) and the [Android Studio Preview Release Updates](https://androidstudio.googleblog.com/) pages to see which version to use. You can also check the Kotlin version, and if needed align this with the Kotlin plugin version for Android Studio under `Android Studio` -> `Preferences` -> `Languages & Frameworks` -> `Kotlin`. @@ -133,7 +133,7 @@ Projects publishing to Sonatype's release repository need to be manually closed #### Increase version after publishing -In case this has not been done yet, bump the version at [gradle/ext.gradle](gradle/ext.gradle) in the `develop` branch to start the next development iteration. +In case this has not been done yet, bump the version at [gradle/ext.gradle](gradle/ext.gradle.kts) in the `develop` branch to start the next development iteration. ```sh library_version = 'X.X.X' diff --git a/adding-a-new-module/template/build.gradle.kts b/adding-a-new-module/template/build.gradle.kts index ac6377ed6..151b61933 100644 --- a/adding-a-new-module/template/build.gradle.kts +++ b/adding-a-new-module/template/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/alerts/build.gradle.kts b/alerts/build.gradle.kts index 0fa0baf1f..b66f5c421 100644 --- a/alerts/build.gradle.kts +++ b/alerts/build.gradle.kts @@ -6,26 +6,24 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +group = Library.group +version = Library.version dependencies { val ext = (gradle as ExtensionAware).extra + val androidx_fragment_version: String by ext - implementation("androidx.fragment:fragment:${ext["androidx_fragment_version"]}") - debugImplementation("androidx.fragment:fragment-ktx:${ext["androidx_fragment_version"]}") + implementation(Dependencies.AndroidX.Fragment.notation) + testImplementation(Dependencies.AndroidX.FragmentKtx.notation) } +commonComponent() + kotlin { sourceSets { getByName("commonMain") { dependencies { - val ext = (gradle as ExtensionAware).extra implementation(project(":architecture", "")) implementation(project(":base", "")) implementation(project(":resources", "")) diff --git a/architecture-compose/build.gradle.kts b/architecture-compose/build.gradle.kts index a40fd2716..6490880b1 100644 --- a/architecture-compose/build.gradle.kts +++ b/architecture-compose/build.gradle.kts @@ -31,7 +31,7 @@ ext["component_type"] = ext["component_type_compose"] val path_prefix = if (file("../gradle/componentskt.gradle.kts").exists()) ".." else "../.." -apply(from = "$path_prefix/gradle/android_compose.gradle") +apply(from = "$path_prefix/gradle/android_compose.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/architecture/build.gradle.kts b/architecture/build.gradle.kts index 573aa292e..d1aa553cf 100644 --- a/architecture/build.gradle.kts +++ b/architecture/build.gradle.kts @@ -9,7 +9,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/base-permissions/build.gradle.kts b/base-permissions/build.gradle.kts index b315673eb..a75815832 100644 --- a/base-permissions/build.gradle.kts +++ b/base-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/base/build.gradle.kts b/base/build.gradle.kts index f7856c4ee..eea324c02 100644 --- a/base/build.gradle.kts +++ b/base/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/beacons/build.gradle.kts b/beacons/build.gradle.kts index c8d72f613..ff11a0d7c 100644 --- a/beacons/build.gradle.kts +++ b/beacons/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/bluetooth-permissions/build.gradle.kts b/bluetooth-permissions/build.gradle.kts index ac76bebd8..459b1b9a1 100644 --- a/bluetooth-permissions/build.gradle.kts +++ b/bluetooth-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/bluetooth/build.gradle.kts b/bluetooth/build.gradle.kts index f602bfe03..1793ae034 100644 --- a/bluetooth/build.gradle.kts +++ b/bluetooth/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 0844df4cf..000000000 --- a/build.gradle +++ /dev/null @@ -1,69 +0,0 @@ -buildscript { - - repositories { - google() - mavenCentral() - } - dependencies { - // mostly migrated to new style plugin declarations, but some cross plugin interaction still requires this - classpath "com.android.tools.build:gradle:${project.'kaluga.androidGradlePluginVersion'}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${project.'kaluga.kotlinVersion'}" - } -} - -plugins { - id "convention.publication" - id 'org.jlleitschuh.gradle.ktlint' - id 'org.jlleitschuh.gradle.ktlint-idea' - id 'org.jetbrains.kotlin.multiplatform' apply false - id 'org.jetbrains.kotlinx.binary-compatibility-validator' -} - -// TODO: To be removed once we will migrate to kotlin version 1.6.20 -// https://youtrack.jetbrains.com/issue/KT-49109#focus=Comments-27-5667134.0-0 -rootProject.plugins.withType(Class.forName("org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin")) { - rootProject.extensions.getByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.class).nodeVersion = "16.13.2" -} - -allprojects { - repositories { - mavenCentral() - google() - // only enable temporarily if needed: - / * mavenLocal() */ - } - - tasks.withType(Test) { - testLogging { - events "standardOut", "started", "passed", "skipped", "failed" - } - testLogging.exceptionFormat = 'full' - } - - tasks.withType(org.jlleitschuh.gradle.ktlint.tasks.BaseKtLintCheckTask).configureEach { - it.workerMaxHeapSize.set("512m") - } -} - -apiValidation { - - subprojects.forEach { - def name = it.name - - ignoredClasses += ["com.splendo.kaluga.${name}.BuildConfig".toString()] - ignoredClasses += ["com.splendo.kaluga.${name.replaceAll('-', ".")}.BuildConfig".toString()] - ignoredClasses += ["com.splendo.kaluga.${name.replaceAll('-', "")}.BuildConfig".toString()] - ignoredClasses += ["com.splendo.kaluga.permissions.${name.replaceAll("-permissions", "")}.BuildConfig".toString()] - } - - ignoredClasses += ['com.splendo.kaluga.permissions.BuildConfig'] - ignoredClasses += ['com.splendo.kaluga.test.BuildConfig'] - ignoredClasses += ['com.splendo.kaluga.datetime.timer.BuildConfig'] -} - -apply from:"gradle/ext.gradle" -apply from:"gradle/newModule.gradle.kts" -apply from:"gradle/copyReports.gradle.kts" - -group 'com.splendo.kaluga' -version gradle.ext.library_version diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..77f56ba5a --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,73 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jlleitschuh.gradle.ktlint.tasks.BaseKtLintCheckTask + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + // mostly migrated to new style plugin declarations, but some cross plugin interaction still requires this + classpath("com.android.tools.build:gradle:${project.extra["kaluga.androidGradlePluginVersion"]}") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.extra["kaluga.kotlinVersion"]}") + } +} + +plugins { + id("convention.publication") + id("org.jlleitschuh.gradle.ktlint") + id("org.jlleitschuh.gradle.ktlint-idea") + id("org.jetbrains.kotlin.multiplatform") apply false + id("org.jetbrains.kotlinx.binary-compatibility-validator") +} + +// TODO: To be removed once we will migrate to kotlin version 1.6.20 +// https://youtrack.jetbrains.com/issue/KT-49109#focus=Comments-27-5667134.0-0 +rootProject.plugins.withType() { + rootProject.extensions.getByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension::class.java).nodeVersion = "16.13.2" +} + +allprojects { + repositories { + mavenCentral() + google() + // only enable temporarily if needed: + /* mavenLocal() */ + } + + tasks.withType() { + testLogging { + events = setOf(TestLogEvent.STANDARD_OUT, TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } + testLogging.exceptionFormat = TestExceptionFormat.FULL + } + + tasks.withType().configureEach { + // it.workerMaxHeapSize.set("512m") + } +} + +apiValidation { + + subprojects.forEach { + val name = it.name + + ignoredClasses.add("com.splendo.kaluga.${name}.BuildConfig".toString()) + ignoredClasses.add("com.splendo.kaluga.${name.replace("-", ".")}.BuildConfig".toString()) + ignoredClasses.add("com.splendo.kaluga.${name.replace("-", "")}.BuildConfig".toString()) + ignoredClasses.add("com.splendo.kaluga.permissions.${name.replace("-permissions", "")}.BuildConfig".toString()) + } + + ignoredClasses.add("com.splendo.kaluga.permissions.BuildConfig") + ignoredClasses.add("com.splendo.kaluga.test.BuildConfig") + ignoredClasses.add("com.splendo.kaluga.datetime.timer.BuildConfig") +} + +apply(from = "gradle/ext.gradle.kts") +apply(from = "gradle/newModule.gradle.kts") +apply(from = "gradle/copyReports.gradle.kts") + +group = "com.splendo.kaluga" +version = Library.version diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..caa676a5b --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + `java-gradle-plugin` + `kotlin-dsl` + `kotlin-dsl-precompiled-script-plugins` +} + +repositories { + gradlePluginPortal() // To use 'maven-publish' and 'signing' plugins in our own plugin + google() +} + +dependencies { + // mostly migrated to new style plugin declarations, but some cross plugin interaction still requires this + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20") + implementation("com.android.tools.build:gradle:7.3.0") +} diff --git a/buildSrc/settings/gradle.kts b/buildSrc/settings/gradle.kts new file mode 100644 index 000000000..39e6b7b2a --- /dev/null +++ b/buildSrc/settings/gradle.kts @@ -0,0 +1,54 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +pluginManagement { + + repositories { + gradlePluginPortal() + google() + } + + // resolutionStrategy { + // eachPlugin { + // + // val kalugaAndroidGradlePluginVersion = + // settings.extra["kaluga.androidGradlePluginVersion"] + // val kalugaKotlinVersion = settings.extra["kaluga.kotlinVersion"] + // val kalugaKtLintGradlePluginVersion = settings.extra["kaluga.ktLintGradlePluginVersion"] + // val kalugaGoogleServicesGradlePluginVersion = settings.extra["kaluga.googleServicesGradlePluginVersion"] + // val kalugaBinaryCompatibilityValidatorVersion = settings.extra["kaluga.binaryCompatibilityValidatorVersion"] + // + // when (requested.id.id) { + // "org.jetbrains.kotlin.multiplatform", + // "org.jetbrains.kotlin.plugin.serialization", + // "org.jetbrains.kotlin.android", + // "org.jetbrains.kotlin.kapt", + // -> useVersion("$kalugaKotlinVersion") + // "com.android.library", + // "com.android.application", + // -> useVersion("$kalugaAndroidGradlePluginVersion") + // "org.jlleitschuh.gradle.ktlint", + // "org.jlleitschuh.gradle.ktlint-idea", + // -> useVersion("$kalugaKtLintGradlePluginVersion") + // "com.google.gms:google-services" + // -> useVersion("com.google.gms:google-services:$kalugaGoogleServicesGradlePluginVersion") + // "org.jetbrains.kotlinx.binary-compatibility-validator" + // -> useVersion("$kalugaBinaryCompatibilityValidatorVersion") + // } + // } + // } +} diff --git a/buildSrc/src/main/kotlin/AndroidCommon.kt b/buildSrc/src/main/kotlin/AndroidCommon.kt new file mode 100644 index 000000000..f9483a29f --- /dev/null +++ b/buildSrc/src/main/kotlin/AndroidCommon.kt @@ -0,0 +1,107 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Action +import org.gradle.api.JavaVersion +import org.gradle.kotlin.dsl.dependencies + +enum class ComponentType { + DEFAULT, + COMPOSE, + APP, + COMPOSE_APP +} + +fun org.gradle.api.Project.commonAndroidComponent(type: ComponentType = ComponentType.DEFAULT) { + val action = object : Action { + override fun execute(t: LibraryExtension) { + t.androidCommon(type) + } + } + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", action) + dependencies { + + } +} + +fun LibraryExtension.androidCommon(componentType: ComponentType = ComponentType.DEFAULT) { + compileSdk = Library.Android.compileSdk + buildToolsVersion = Library.Android.buildTools + + defaultConfig { + minSdk = Library.Android.minSdk + targetSdk = Library.Android.targetSdk + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + + when (componentType) { + ComponentType.APP, + ComponentType.COMPOSE_APP -> { + println("Android sourcesets for this project module are configured using defaults (for an app)") + } + ComponentType.COMPOSE, + ComponentType.DEFAULT -> { + println("Android sourcesets for this project module are configured as a library") + sourceSets { + getByName("main") { + manifest.srcFile("src/androidLibMain/AndroidManifest.xml") + res.srcDir("src/androidLibMain/res") + if (componentType == ComponentType.COMPOSE) { + java.srcDir("src/androidLibMain/kotlin") + } + } + getByName("androidTest") { + manifest.srcFile("src/androidLibAndroidTest/AndroidManifest.xml") + java.srcDir("src/androidLibAndroidTest/kotlin") + res.srcDir("src/androidLibAndroidTest/res") + } + + getByName("test") { + java.srcDir("src/androidLibUnitTest/kotlin") + } + } + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + when (componentType) { + ComponentType.COMPOSE, + ComponentType.COMPOSE_APP -> { + println("This project module is a Compose only module") + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = Library.Android.composeCompiler + } + } + ComponentType.DEFAULT, + ComponentType.APP -> {} + } +} diff --git a/buildSrc/src/main/kotlin/Component.kt b/buildSrc/src/main/kotlin/Component.kt new file mode 100644 index 000000000..e7b31703b --- /dev/null +++ b/buildSrc/src/main/kotlin/Component.kt @@ -0,0 +1,142 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import org.gradle.api.Action +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTests + +fun org.gradle.api.Project.commonComponent() { + val action = object : Action { + override fun execute(t: KotlinMultiplatformExtension) { + t.commonMultiplatformComponent() + } + } + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin", action) +} + +fun KotlinMultiplatformExtension.commonMultiplatformComponent() { + targets { + configureEach { + compilations.configureEach { + (kotlinOptions as? KotlinJvmOptions)?.jvmTarget = "1.8" + } + } + } + + android("androidLib").publishAllLibraryVariants() + val target: KotlinNativeTarget.() -> Unit = + { + binaries { + getTest("DEBUG").apply { + freeCompilerArgs = freeCompilerArgs + listOf("-e", "com.splendo.kaluga.test.base.mainBackground") + } + } + } + Library.IOS.targets.forEach { iosTarget -> + when (iosTarget) { + IOSTarget.X64 -> iosX64(target).applyTestDevice() + IOSTarget.Arm64 -> iosArm64(target) + IOSTarget.SimulatorArm64 -> iosSimulatorArm64(target).applyTestDevice() + } + } + + jvm() + js(KotlinJsCompilerType.IR) { + nodejs() + compilations.configureEach { + kotlinOptions { + metaInfo = true + sourceMap = true + moduleKind = "umd" + } + } + } + + val commonMain = sourceSets.maybeCreate("commonMain").apply { + dependencies { + implement(Dependencies.KotlinX.Coroutines.Core) + } + } + + val commonTest = sourceSets.maybeCreate("commonTest").apply { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + + val jvmMain = sourceSets.maybeCreate("jvmMain").apply { + dependencies { + implementation(kotlin("stdlib")) + implement(Dependencies.KotlinX.Coroutines.Swing) + } + } + + val jvmTest = sourceSets.maybeCreate("jvmTest").apply { + dependsOn(commonTest) + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + + val jsMain = sourceSets.maybeCreate("jsMain").apply { + dependencies { + implementation(kotlin("stdlib-js")) + implement(Dependencies.KotlinX.Coroutines.Js) + } + } + + val jsTest = sourceSets.maybeCreate("jsTest").apply { + dependencies { + implementation(kotlin("test-js")) + } + } + + val iosMain = sourceSets.maybeCreate("iosMain").apply { + dependsOn(commonMain) + } + + val iosTest = sourceSets.maybeCreate("iosTest").apply { + dependsOn(commonTest) + } + + sourceSets.all { + languageSettings { + optIn("kotlinx.coroutines.DelicateCoroutinesApi") + optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") + optIn("kotlinx.coroutines.ObsoleteCoroutinesApi") + optIn("kotlinx.coroutines.InternalCoroutinesApi") + optIn("kotlinx.coroutines.FlowPreview") + optIn("kotlin.ExperimentalUnsignedTypes") + optIn("kotlin.ExperimentalStdlibApi") + optIn("kotlin.time.ExperimentalTime") + optIn("kotlin.ExperimentalStdlibApi") + enableLanguageFeature("InlineClasses") + } + } +} + +fun KotlinNativeTargetWithSimulatorTests.applyTestDevice() { + testRuns.all { + deviceId = Library.IOS.TestRunnerDeviceId + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt new file mode 100644 index 000000000..3142a2f1d --- /dev/null +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -0,0 +1,164 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler + +data class Dependency(private val group: String, private val name: String, private val version: String? = null) { + val notation = "$group:$name${version?.let { ":$it" } ?: ""}" +} + +fun KotlinDependencyHandler.expose(dependency: Dependency) = api(dependency.notation) +fun KotlinDependencyHandler.implement(dependency: Dependency) = implementation(dependency.notation) + +object Dependencies { + + object KotlinX { + object Coroutines { + private const val version = "1.6.3-native-mt" + private const val group = "org.jetbrains.kotlinx" + val Core = Dependency(group, "kotlinx-coroutines-core", version) + val Swing = Dependency(group, "kotlinx-coroutines-swing", version) + val Js = Dependency(group, "kotlinx-coroutines-js", version) + } + + object Serialization { + private const val version = "1.4.0" + private const val group = "org.jetbrains.kotlinx" + val Core = Dependency(group, "kotlinx-serialization-core", version) + val Json = Dependency(group, "kotlinx-serialization-json", version) + } + } + + object Android { + internal const val groupBase = "com.google.android" + private const val materialBase = "$groupBase.material" + + val Material = Dependency(materialBase, "material", "1.6.1") + val MaterialComposeThemeAdapter = Dependency(materialBase, "compose-theme-adapter", "1.1.17") + + object Play { + private const val group = "$groupBase.play" + val Core = Dependency(group, "core", "1.10.3") + val Ktx = Dependency(group, "core-ktx", "1.8.1") + } + object PlayServices { + private const val group = "$groupBase.gms" + private const val version = "20.0.0" + val Location = Dependency(group, "play-services-location", version) + } + } + + object AndroidX { + + private const val groupBase = "androidx" + private const val fragmentGroup = "$groupBase.fragment" + private const val fragmentVersion = "1.5.3" + + object Activity { + private const val group = "$groupBase.activity" + val Compose = Dependency(group, "activity-compose", "1.6.0") + } + val AppCompat = Dependency("$groupBase.appcompat", "appcompat", "1.5.1") + val ArchCore = Dependency("$groupBase.arch.core", "core-testing", "2.1.0") + val Browser = Dependency("$groupBase.browser", "browser", "1.4.0") + object Compose { + private const val version = "1.2.1" + private const val composeGroupBase = "$groupBase.compose" + + val Foundation = Dependency("$composeGroupBase.foundation", "foundation", version) + val Material = Dependency("$composeGroupBase.material", "material", version) + val UI = Dependency("$composeGroupBase.ui", "ui", version) + val UITooling = Dependency("$composeGroupBase.ui", "ui-tooling", version) + } + val ConstraintLayout = Dependency("$groupBase.constraintlayout", "constraintlayout", "2.1.4") + val Core = Dependency("$groupBase.core", "core", "1.9.0") + val Fragment = Dependency(fragmentGroup, "fragment", fragmentVersion) + val FragmentKtx = Dependency(fragmentGroup, "fragment-ktx", fragmentVersion) + object Lifecycle { + private const val group = "$groupBase.lifecycle" + private const val version = "2.5.1" + + val LiveData = Dependency(group, "lifecycle-livedata-ktx", version) + val Runtime = Dependency(group, "lifecycle-runtime-ktx", version) + val Service = Dependency(group, "lifecycle-service", version) + val ViewModel = Dependency(group, "lifecycle-viewmodel-ktx", version) + val ViewModelCompose = Dependency(group, "lifecycle-viewmodel-compose", "2.5.1") + } + object Navigation { + private const val group = "$groupBase.navigation" + private const val version = "2.5.2" + + val Compose = Dependency(group, "navigation-compose", version) + } + object Test { + private const val group = "$groupBase.test" + private const val versionPostfix = "" + private const val version = "1.4.0$versionPostfix" + private const val espressoVersion = "3.4.0$versionPostfix" + private const val junitVersion = "1.1.3$versionPostfix" + private const val uiAutomatorVersion = "2.2.0" + + val Core = Dependency(group, "core", version) + val CoreKtx = Dependency(group, "core-ktx", version) + val Espresso = Dependency("$group.espresso", "espresso-core", version) + val JUnit = Dependency("$group.ext", "junit", junitVersion) + val Rules = Dependency(group, "rules", version) + val Runner = Dependency(group, "runner", version) + val UIAutomator = Dependency("$group.uiautomator", "uiautomator", uiAutomatorVersion) + } + } + + val BLEScanner = Dependency("no.nordicsemi.android.support.v18", "scanner", "1.6.0") + + object Koin { + private const val group = "io.insert-koin" + private const val version = "3.2.2" + val Android = Dependency(group, "koin-android", version) + val Core = Dependency(group, "koin-core", version) + } + + val Napier = Dependency("io.github.aakira", "napier", "2.4.0") + + object Stately { + private const val group = "co.touchlab" + private const val version = "1.2.3" + private const val isolateVersion = "1.2.3" + val Common = Dependency(group, "stately-common", version) + val Concurrency = Dependency(group, "stately-concurrency", version) + val Isolate = Dependency(group, "stately-isolate", isolateVersion) + val IsoCollections = Dependency(group, "stately-iso-collections", isolateVersion) + } + + val JUnit = Dependency("junit", "junit", "4.13.2") + + object Mockito { + private const val group = "org.mockito" + private const val version = "3.11.2" + + val Android = Dependency(group, "mockito-android", version) + val Core = Dependency(group, "mockito-core", version) + } + + object ByteBuddy { + private const val group = "net.bytebuddy" + private const val version = "1.11.3" + + val Android = Dependency(group, "byte-buddy-android", version) + val Agent = Dependency(group, "byte-buddy-agent", version) + } +} diff --git a/buildSrc/src/main/kotlin/GitBranch.kt b/buildSrc/src/main/kotlin/GitBranch.kt new file mode 100644 index 000000000..96cce8d6d --- /dev/null +++ b/buildSrc/src/main/kotlin/GitBranch.kt @@ -0,0 +1,52 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import org.slf4j.LoggerFactory + +object GitBranch { + private val logger = LoggerFactory.getLogger(this::class.java) + val BITRISE_GIT_BRANCH = java.lang.System.getenv("BITRISE_GIT_BRANCH") + val kaluga_branch = System.getProperty("kaluga_branch") + val MAVEN_CENTRAL_RELEASE = System.getenv("MAVEN_CENTRAL_RELEASE") + val release = MAVEN_CENTRAL_RELEASE?.toLowerCase()?.trim() == "true" + val branchFromGit = run { + try { + val process = ProcessBuilder().command("git rev-parse --abbrev-ref HEAD".split(" ")).start() + process.inputStream.bufferedReader().readText() + } catch (e: Exception) { + logger.info("Unable to determine current branch through git CLI: ${e.message}") + "unknown" + } + } + + // favour user definition of kaluga_branch (if present), otherwise take it from GIT branch: + // - if running on CI: favour bitrise's branch detection + // - else: try to get it via the `git` CLI. + val branch = (kaluga_branch ?: BITRISE_GIT_BRANCH ?: branchFromGit ).replace('/', '-').trim().toLowerCase().also { + if (it == "HEAD") { + logger.warn("Unable to determine current branch: Project is checked out with detached head!") + } + } + + val kalugaBranchPostfix = (when(branch) { + "master", "main", "develop" -> "" + else -> "-"+branch + } + if (!release) "-SNAPSHOT" else "").also { + + println("decided branch: '$branch' to postfix '$it', isRelease: $release (from: BITRISE_GIT_BRANCH env: $BITRISE_GIT_BRANCH, kaluga_branch property: $kaluga_branch , MAVEN_CENTRAL_RELEASE env: $MAVEN_CENTRAL_RELEASE , git cli: $branchFromGit)") + } +} diff --git a/buildSrc/src/main/kotlin/Library.kt b/buildSrc/src/main/kotlin/Library.kt new file mode 100644 index 000000000..f5bb8870f --- /dev/null +++ b/buildSrc/src/main/kotlin/Library.kt @@ -0,0 +1,138 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import java.io.File +import java.io.FileInputStream +import java.util.* + +object Library { + + private val props = Properties() + private const val baseVersion = "1.0.0" + val version: String by lazy { + println("------Properties" + props.entries.joinToString(" ") { (key, value) -> "$key to $value" }) + val libraryVersionLocalProperties: String? = props["kaluga.libraryVersion"] as? String + (libraryVersionLocalProperties ?: "$baseVersion${GitBranch.kalugaBranchPostfix}").also { + println("Library version $it") + } + } + const val group = "com.splendo.kaluga" + + init { + val propFile = File("../local.properties") + if (propFile.exists()) { + val inputStream = FileInputStream(propFile.absolutePath) + props.load(inputStream) + } + } + + object Android { + const val minSdk = 21 + const val compileSdk = 33 + const val targetSdk = 33 + const val buildTools = "33.0.0" + const val composeCompiler = "1.3.2" + } + + object IOS { + // based on https://github.com/Kotlin/xcode-compat/blob/d677a43edc46c50888bca0a7890a81f976a42809/xcode-compat/src/main/kotlin/org/jetbrains/kotlin/xcodecompat/XcodeCompatPlugin.kt#L16 + val sdkName = System.getenv("SDK_NAME") ?: "unknown" + val isRealIOSDevice = sdkName.startsWith("iphoneos").also { + println("Run on real ios device: $it from sdk: $sdkName") + } + + // Run on IntelliJ + val ideaActive = (System.getProperty("idea.active") == "true").also { + println("Run on IntelliJ: $it") + } + + // Run on apple silicon + val isAppleSilicon = (System.getProperty("os.arch") == "aarch64").also { + println("Run on apple silicon: $it") + } + + val targets = when { + !ideaActive -> IOSTarget.values().toSet() + isRealIOSDevice -> setOf(IOSTarget.Arm64) + isAppleSilicon -> setOf(IOSTarget.SimulatorArm64) + else -> setOf(IOSTarget.X64) + }.also { targets -> + println("Run on ios targets: ${targets.joinToString(" ") { it.name }}") + } + + val TestRunnerDeviceId by lazy { + if (System.getenv().containsKey("IOS_TEST_RUNNER_DEVICE_ID")) { + System.getenv()["IOS_TEST_RUNNER_DEVICE_ID"].also { + println("System env IOS_TEST_RUNNER_DEVICE_ID set to ${System.getenv()["IOS_TEST_RUNNER_DEVICE_ID"]}, using $it") + }!! + } else { + // load some more from local.properties or set defaults. + val iosTestRunnerDeviceIdLocalProperty: String? = + props["kaluga.iosTestRunnerDeviceIdLocalProperty"] as? String + iosTestRunnerDeviceIdLocalProperty?.also { + println("local.properties read (kaluga.iosTestRunnerDeviceIdLocalProperty=$iosTestRunnerDeviceIdLocalProperty, using $it)") + } + ?: "iPhone 14".also { + println("local.properties not found, using default value ($it)") + } + } + } + } + + val exampleEmbeddingMethod by lazy { + if (System.getenv().containsKey("EXAMPLE_EMBEDDING_METHOD")) { + System.getenv()["EXAMPLE_EMBEDDING_METHOD"].also { + println("System env EXAMPLE_EMBEDDING_METHOD set to ${System.getenv()["EXAMPLE_EMBEDDING_METHOD"]}, using $it") + }!! + } else { + val exampleEmbeddingMethodLocalProperties = props["kaluga.exampleEmbeddingMethod"] as? String + (exampleEmbeddingMethodLocalProperties ?: "composite").also { + println("local.properties read (kaluga.exampleEmbeddingMethod=$exampleEmbeddingMethodLocalProperties, using $it)") + } + } + } + + val exampleMavenRepo by lazy { + if (System.getenv().containsKey("EXAMPLE_MAVEN_REPO")) { + System.getenv()["EXAMPLE_MAVEN_REPO"].also { + println("System env EXAMPLE_MAVEN_REPO set to ${System.getenv()["EXAMPLE_MAVEN_REPO"]}, using $it") + }!! + } else { + // load some more from local.properties or set defaults. + val exampleMavenRepoLocalProperties: String? = + props["kaluga.exampleMavenRepo"] as? String + exampleMavenRepoLocalProperties?.also { + println("local.properties read (kaluga.exampleMavenRepo=$exampleMavenRepoLocalProperties, using $it)") + } + ?: "local".also { + println("local.properties not found, using default value ($it)") + } + } + } + + val connectCheckExpansion = (System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") or System.getenv().containsKey("CI")).also { + if (it) { + println("Adding extra dependend task to connected checks of similarly named modules (CONNECTED_CHECK_EXPANSION env present: ${ System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") })") + } + } +} + +enum class IOSTarget { + X64, + Arm64, + SimulatorArm64 +} diff --git a/calendar-permissions/build.gradle.kts b/calendar-permissions/build.gradle.kts index 90d5840d9..7135eda09 100644 --- a/calendar-permissions/build.gradle.kts +++ b/calendar-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/camera-permissions/build.gradle.kts b/camera-permissions/build.gradle.kts index 90d5840d9..7135eda09 100644 --- a/camera-permissions/build.gradle.kts +++ b/camera-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/contacts-permissions/build.gradle.kts b/contacts-permissions/build.gradle.kts index 90d5840d9..7135eda09 100644 --- a/contacts-permissions/build.gradle.kts +++ b/contacts-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/convention-plugins/build.gradle.kts b/convention-plugins/build.gradle.kts index ca6517939..56578aa20 100644 --- a/convention-plugins/build.gradle.kts +++ b/convention-plugins/build.gradle.kts @@ -16,7 +16,7 @@ */ plugins { - `kotlin-dsl` // Is needed to turn our build logic written in Kotlin into Gralde Plugin + `kotlin-dsl` // Is needed to turn our build logic written in Kotlin into Gradle Plugin } repositories { diff --git a/date-time-picker/build.gradle.kts b/date-time-picker/build.gradle.kts index 8d2245b72..e6144a36b 100644 --- a/date-time-picker/build.gradle.kts +++ b/date-time-picker/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/date-time/build.gradle.kts b/date-time/build.gradle.kts index 6aac0e821..601c3c95c 100644 --- a/date-time/build.gradle.kts +++ b/date-time/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/example/android/build.gradle b/example/android/build.gradle index 50c9a5d74..9d15f9d28 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -64,7 +64,7 @@ kotlin { gradle.ext.component_type = gradle.ext.component_type_composeApp -apply from: "../../gradle/android_compose.gradle" +apply from: "../../gradle/android_compose.gradle.kts" gradle.ext.component_type = gradle.ext.component_type_default diff --git a/example/android/proguard-rules.pro b/example/android/proguard-rules.pro index 2f9dc5a47..e3345f14a 100644 --- a/example/android/proguard-rules.pro +++ b/example/android/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle.kts. +# proguardFiles setting in build.build.gradle.kts.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/example/ios/Supporting Files/settings.gradle.kts b/example/ios/Supporting Files/settings.gradle.kts index 24a4da1ff..0d17cfa52 100644 --- a/example/ios/Supporting Files/settings.gradle.kts +++ b/example/ios/Supporting Files/settings.gradle.kts @@ -39,7 +39,7 @@ pluginManagement { } includeBuild("../../../convention-plugins") -apply("../../../gradle/ext.gradle") +apply("../../../gradle/ext.gradle.kts") val ext = (gradle as ExtensionAware).extra diff --git a/example/shared/build.gradle.kts b/example/shared/build.gradle.kts index c2855329f..be70d1e5e 100644 --- a/example/shared/build.gradle.kts +++ b/example/shared/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -apply(from = "../../gradle/component.gradle") +apply(from = "../../gradle/component.gradle.kts") kotlin { sourceSets { diff --git a/gradle/android_common.gradle b/gradle/android_common.gradle deleted file mode 100644 index 3a9f30d8a..000000000 --- a/gradle/android_common.gradle +++ /dev/null @@ -1,112 +0,0 @@ -/* - Copyright 2020 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -repositories { - mavenCentral() -} - -android { - - compileSdkVersion gradle.android_compile_sdk_version - buildToolsVersion gradle.android_build_tools_version - - defaultConfig { - minSdkVersion gradle.android_min_sdk_version - targetSdkVersion gradle.android_target_sdk_version - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - } - } - - if (!gradle.ext.component_type.toLowerCase().contains("app")) { - logger.lifecycle("Android sourcesets for this project module are configured as a library") - sourceSets { - main { - manifest.srcFile 'src/androidLibMain/AndroidManifest.xml' - res.srcDir 'src/androidLibMain/res' - if (gradle.component_type.contains("compose")) { - java.srcDir 'src/androidLibMain/kotlin' - } - } - androidTest { - manifest.srcFile 'src/androidLibAndroidTest/AndroidManifest.xml' - java.srcDir 'src/androidLibAndroidTest/kotlin' - res.srcDir 'src/androidLibAndroidTest/res' - } - - - test { - java.srcDir 'src/androidLibUnitTest/kotlin' - } - - } - } else { - logger.lifecycle("Android sourcesets for this project module are configured using defaults (for an app)") - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs += ['-XXLanguage:+InlineClasses', '-Xjvm-default=all'] - } - - if (gradle.component_type.contains("compose")) { - logger.lifecycle("This project module is a Compose only module") - buildFeatures { - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = "$gradle.androidx_compose_compiler_version" - } - } - -} - -dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$gradle.kotlinx_coroutines_version" - implementation("androidx.appcompat:appcompat:$gradle.androidx_appcompat_version") { - exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib' - } - - testImplementation 'junit:junit:4.13.2' - testImplementation "org.mockito:mockito-core:$gradle.mockito_version" - testImplementation 'org.jetbrains.kotlin:kotlin-test' - testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' - - androidTestImplementation "org.mockito:mockito-android:$gradle.mockito_version" - androidTestImplementation "net.bytebuddy:byte-buddy-android:$gradle.bytebuddy_version!!" - androidTestImplementation "net.bytebuddy:byte-buddy-agent:$gradle.bytebuddy_version!!" - - androidTestImplementation "androidx.test:core:$gradle.androidx_test_version" - androidTestImplementation "androidx.test:core-ktx:$gradle.androidx_test_version" - androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0" - androidTestImplementation "androidx.test:rules:$gradle.androidx_test_version" - androidTestImplementation "androidx.test.ext:junit:$gradle.androidx_test_junit_version" - androidTestImplementation "androidx.test:runner:$gradle.androidx_test_version" - androidTestImplementation "androidx.test.espresso:espresso-core:$gradle.androidx_test_espresso_version" - androidTestImplementation 'org.jetbrains.kotlin:kotlin-test' - androidTestImplementation 'org.jetbrains.kotlin:kotlin-test-junit' -} diff --git a/gradle/android_common.gradle.kts b/gradle/android_common.gradle.kts new file mode 100644 index 000000000..cd1769b81 --- /dev/null +++ b/gradle/android_common.gradle.kts @@ -0,0 +1,128 @@ +/* + Copyright 2020 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +repositories { + mavenCentral() +} + +android { + + val android_compile_sdk_version: Int by extra + val android_min_sdk_version: Int by extra + val android_target_sdk_version: Int by extra + val android_build_tools_version: String by extra + + compileSdk = android_compile_sdk_version + buildToolsVersion = android_build_tools_version + + defaultConfig { + minSdk = android_min_sdk_version + targetSdk = android_target_sdk_version + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled = false + } + } + + val component_type: String by extra + if (!component_type.toLowerCase().contains("app")) { + logger.lifecycle("Android sourcesets for this project module are configured as a library") + sourceSets { + main { + manifest.srcFile "src/androidLibMain/AndroidManifest.xml" + res.srcDir "src/androidLibMain/res" + if (component_type.contains("compose")) { + java.srcDir = "src/androidLibMain/kotlin" + } + } + androidTest { + manifest.srcFile = "src/androidLibAndroidTest/AndroidManifest.xml" + java.srcDir = "src/androidLibAndroidTest/kotlin" + res.srcDir = "src/androidLibAndroidTest/res" + } + + + test { + java.srcDir = "src/androidLibUnitTest/kotlin" + } + + } + } else { + logger.lifecycle("Android sourcesets for this project module are configured using defaults (for an app)") + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += ['-XXLanguage:+InlineClasses', '-Xjvm-default=all'] + } + + if (component_type.contains("compose")) { + logger.lifecycle("This project module is a Compose only module") + buildFeatures { + compose = true + } + composeOptions { + val androidx_compose_compiler_version: String by extra + kotlinCompilerExtensionVersion = androidx_compose_compiler_version + } + } + +} + +dependencies { + val kotlinx_coroutines_version: String by extra + val androidx_appcompat_version: String by extra + + val junit_version: String by extra + val mockito_version: String by extra + val bytebuddy_version: String by extra + val androidx_test_version: String by extra + val androidx_test_uiautomator_version: String by extra + val androidx_test_junit_version: String by extra + val androidx_test_espresso_version: String by extra + + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version") + implementation("androidx.appcompat:appcompat:$androidx_appcompat_version") + + testImplementation("junit:junit:$junit_version") + testImplementation("org.mockito:mockito-core:$mockito_version") + testImplementation("org.jetbrains.kotlin:kotlin-test" + testImplementation("org.jetbrains.kotlin:kotlin-test-junit") + + androidTestImplementation("org.mockito:mockito-android:$mockito_version") + androidTestImplementation("net.bytebuddy:byte-buddy-android:$bytebuddy_version!!") + androidTestImplementation("net.bytebuddy:byte-buddy-agent:$bytebuddy_version!!") + + androidTestImplementation("androidx.test:core:$androidx_test_version") + androidTestImplementation("androidx.test:core-ktx:$androidx_test_version") + androidTestImplementation("androidx.test.uiautomator:uiautomator:$androidx_test_uiautomator_version") + androidTestImplementation("androidx.test:rules:$androidx_test_version") + androidTestImplementation("androidx.test.ext:junit:$androidx_test_junit_version") + androidTestImplementation("androidx.test:runner:$androidx_test_version") + androidTestImplementation("androidx.test.espresso:espresso-core:$androidx_test_espresso_version") + androidTestImplementation("org.jetbrains.kotlin:kotlin-test") + androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit") +} diff --git a/gradle/android_compose.gradle b/gradle/android_compose.gradle deleted file mode 100644 index afc504759..000000000 --- a/gradle/android_compose.gradle +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2021 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -// if the include is made from the example project shared module we need to go up one more directory -String path_prefix = file("../gradle/componentskt.gradle.kts").exists() ? ".." : "../.." - -apply from: "$path_prefix/gradle/android_common.gradle" - -dependencies { - implementation("androidx.compose.foundation:foundation:$gradle.androidx_compose_version") - implementation("androidx.compose.ui:ui:$gradle.androidx_compose_version") - implementation("androidx.compose.ui:ui-tooling:$gradle.androidx_compose_version") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$gradle.androidx_lifecycle_viewmodel_compose_version") - implementation("androidx.activity:activity-compose:$gradle.androidx_activity_compose_version") - -} - -kotlin { - sourceSets.all { - languageSettings { - optIn 'androidx.compose.material.ExperimentalMaterialApi' - } - } -} - -if (!gradle.ext.component_type.toLowerCase().contains("app")) { - apply from: "$path_prefix/gradle/publish.gradle" -} diff --git a/gradle/android_compose.gradle.kts b/gradle/android_compose.gradle.kts new file mode 100644 index 000000000..dfbbc115e --- /dev/null +++ b/gradle/android_compose.gradle.kts @@ -0,0 +1,49 @@ +/* + Copyright 2021 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +// if the include is made from the example project shared module we need to go up one more directory +val path_prefix = if (file("../gradle/android_common.gradle.kts").exists()) ".." else "../.." + +apply("$path_prefix/gradle/android_common.gradle.kts") + +dependencies { + val ext = (gradle as ExtensionAware).extra + val androidx_compose_version: String by ext + val androidx_lifecycle_viewmodel_compose_version: String by ext + val androidx_activity_compose_version: String by ext + + implementation("androidx.compose.foundation:foundation:$androidx_compose_version") + implementation("androidx.compose.ui:ui:$androidx_compose_version") + implementation("androidx.compose.ui:ui-tooling:$androidx_compose_version") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$androidx_lifecycle_viewmodel_compose_version") + implementation("androidx.activity:activity-compose:$androidx_activity_compose_version") + +} + +kotlin { + sourceSets.all { + languageSettings { + optIn("androidx.compose.material.ExperimentalMaterialApi") + } + } +} + +val ext = (gradle as ExtensionAware).extra +val component_type: String by ext +if (!component_type.toLowerCase().contains("app")) { + apply("$path_prefix/gradle/publish.gradle.kts") +} diff --git a/gradle/component.gradle b/gradle/component.gradle deleted file mode 100644 index 152150330..000000000 --- a/gradle/component.gradle +++ /dev/null @@ -1,177 +0,0 @@ -repositories { - google() - mavenCentral() -} - -// if the include is made from the example project shared module we need to go up one more directory -String path_prefix = file("../gradle/componentskt.gradle.kts").exists() ? ".." : "../.." - -kotlin { - - targets { - configure([]) { - tasks.getByName(compilations.test.compileKotlinTaskName).kotlinOptions { - jvmTarget = "1.8" - } - } - } - - android("androidLib") { - publishAllLibraryVariants() - } - - gradle.ext.ios_targets.each { - "$it" { - binaries { - // Use this entry point to turn the thread tests are run to a background thread instead of the main thread - // This better allows testing the main dispatcher, and testing cross thread access - binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "com.splendo.kaluga.test.base.mainBackground"] - } - } - } - - jvm() - js(IR) { - // Disable JS browser tests for now - // See https://github.com/splendo/kaluga/issues/97 - // browser() - nodejs() - configure([compilations.main, compilations.test]) { - tasks.getByName(compileKotlinTaskName).kotlinOptions { - metaInfo = true - sourceMap = true - moduleKind = 'umd' - } - } - } - - sourceSets { - - commonMain { - dependencies { - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${gradle.kotlinx_coroutines_version}!!" - } - } - - commonTest { - dependencies { - implementation kotlin('test') - implementation kotlin("test-common") - implementation kotlin("test-annotations-common") - } - } - - jvmMain { - dependencies { - implementation kotlin("stdlib") - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-swing:$gradle.kotlinx_coroutines_version" - } - } - - jvmTest { - dependsOn commonTest - dependencies { - implementation kotlin("test") - implementation kotlin("test-junit") - } - } - - jsMain { - dependencies { - implementation kotlin('stdlib-js') - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$gradle.kotlinx_coroutines_version" - } - } - - jsTest { - dependencies { - implementation kotlin('test-js') - } - } - - iosMain { - dependsOn(commonMain) - } - - iosTest { - dependsOn(commonTest) - } - - gradle.ext.ios_targets.each { - "${it}Main" { - dependsOn(iosMain) - } - "${it}Test" { - dependsOn(iosTest) - } - } - - sourceSets.all { - languageSettings { - optIn 'kotlinx.coroutines.DelicateCoroutinesApi' - optIn 'kotlinx.coroutines.ExperimentalCoroutinesApi' - optIn 'kotlinx.coroutines.ObsoleteCoroutinesApi' - optIn 'kotlinx.coroutines.InternalCoroutinesApi' - optIn 'kotlinx.coroutines.FlowPreview' - optIn 'kotlin.ExperimentalUnsignedTypes' - optIn 'kotlin.ExperimentalStdlibApi' - optIn 'kotlin.time.ExperimentalTime' - optIn 'kotlin.ExperimentalStdlibApi' - enableLanguageFeature("InlineClasses") - } - } - - // Android dependencies must be declared below outside of the kotlin block - - } -} - -apply from: "$path_prefix/gradle/android_common.gradle" - -android { - - testOptions { - unitTests.returnDefaultValues = true - } - - packagingOptions { - exclude 'META-INF/kotlinx-coroutines-core.kotlin_module' - exclude 'META-INF/shared_debug.kotlin_module' - exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module' - exclude 'META-INF/AL2.0' - exclude 'META-INF/LGPL2.1' - // bytebuddy 🤡 - exclude 'win32-x86-64/attach_hotspot_windows.dll' - exclude 'win32-x86/attach_hotspot_windows.dll' - // - exclude 'META-INF/licenses/ASM' - } -} - -task printConfigurations { - doLast { - configurations.each { println it } - } -} - -afterEvaluate { - gradle.ext.ios_targets.each { target -> - if (tasks.getNames().contains("linkDebugTest${target.capitalize()}")) - // creating copy task for the target - tasks.create(name: "copy${target.capitalize()}TestResources", type: Copy) { - from file('src/iosTest/resources/.') - into file("$buildDir/bin/$target/debugTest") - } - - // apply copy task to the target - tasks.named("linkDebugTest${target.capitalize()}").configure { - dependsOn("copy${target.capitalize()}TestResources") - } - } -} - -ktlint { - disabledRules=["no-wildcard-imports","filename","import-ordering"] -} - -apply from: "$path_prefix/gradle/componentskt.gradle.kts" diff --git a/gradle/component.gradle.kts b/gradle/component.gradle.kts new file mode 100644 index 000000000..a6dcf40b1 --- /dev/null +++ b/gradle/component.gradle.kts @@ -0,0 +1,55 @@ +repositories { + google() + mavenCentral() +} + +// apply("$path_prefix/gradle/android_common.gradle.kts") +// +// android { +// +// testOptions { +// unitTests.returnDefaultValues = true +// } +// +// packagingOptions { +// resources.excludes.add("META-INF/kotlinx-coroutines-core.kotlin_module") +// resources.excludes.add("META-INF/shared_debug.kotlin_module") +// resources.excludes.add("META-INF/kotlinx-serialization-runtime.kotlin_module") +// resources.excludes.add("META-INF/AL2.0") +// resources.excludes.add("META-INF/LGPL2.1") +// // bytebuddy 🤡 +// resources.excludes.add("win32-x86-64/attach_hotspot_windows.dll") +// resources.excludes.add("win32-x86/attach_hotspot_windows.dll") +// // +// resources.excludes.add("META-INF/licenses/ASM") +// } +// } +// +// task printConfigurations { +// doLast { +// configurations.each { println(it) } +// } +// } +// +// afterEvaluate { +// val ios_targets: List by extra +// ios_targets.each { target -> +// if (tasks.getNames().contains("linkDebugTest${target.capitalize()}")) +// // creating copy task for the target +// tasks.create(name = "copy${target.capitalize()}TestResources", type = Copy) { +// from(file('src/iosTest/resources/.')) +// into(file("$buildDir/bin/$target/debugTest")) +// } +// +// // apply copy task to the target +// tasks.named("linkDebugTest${target.capitalize()}").configure { +// dependsOn("copy${target.capitalize()}TestResources") +// } +// } +// } +// +// ktlint { +// disabledRules = listOf("no-wildcard-imports","filename","import-ordering") +// } + +// apply(from = "$path_prefix/gradle/componentskt.gradle.kts") diff --git a/gradle/componentskt.gradle.kts b/gradle/componentskt.gradle.kts index 582a803ea..adf45d9b1 100644 --- a/gradle/componentskt.gradle.kts +++ b/gradle/componentskt.gradle.kts @@ -19,17 +19,17 @@ import kotlin.text.* -if ((gradle as ExtensionAware).extra["connect_check_expansion"] == true) { - - val mymodules = project.parent?.subprojects?.filter { - it.name.startsWith("${project.name}-") || it.name.endsWith("-${project.name}") - } - - mymodules?.forEach() { module -> - afterEvaluate { - logger.info("[connect_check_expansion] :${project.name}:connectedDebugAndroidTest dependsOn:${module.name}:connectedDebugAndroidTest") - tasks.getByPath("connectedDebugAndroidTest") - .dependsOn(":${module.name}:connectedDebugAndroidTest") - } - } -} +// if ((gradle as ExtensionAware).extra["connect_check_expansion"] == true) { +// +// val mymodules = project.parent?.subprojects?.filter { +// it.name.startsWith("${project.name}-") || it.name.endsWith("-${project.name}") +// } +// +// mymodules?.forEach() { module -> +// afterEvaluate { +// logger.info("[connect_check_expansion] :${project.name}:connectedDebugAndroidTest dependsOn:${module.name}:connectedDebugAndroidTest") +// tasks.getByPath("connectedDebugAndroidTest") +// .dependsOn(":${module.name}:connectedDebugAndroidTest") +// } +// } +// } diff --git a/gradle/ext.gradle b/gradle/ext.gradle deleted file mode 100644 index bbfce4c85..000000000 --- a/gradle/ext.gradle +++ /dev/null @@ -1,140 +0,0 @@ -println "tasks: $gradle.startParameter.taskNames" - -// if the include is made from the example project shared module we need to go up a few directories -// and depending on when we are included, we can be in the `gradle` folder or not. -String path_prefix = - file("componentskt.gradle.kts").exists() ? "" : - file("gradle/componentskt.gradle.kts").exists() ? "gradle/" : "../../../gradle/" -def f2 = file("${path_prefix}gitBranch.gradle.kts") - -apply from: f2.path - -def props = new Properties() -def propFile = file("../local.properties") -if (propFile.exists()) { - propFile.withInputStream { props.load(it) } -} - -String libraryVersionLocalProperties = props["kaluga.libraryVersion"] -String exampleEmbeddingMethodLocalProperties = props["kaluga.exampleEmbeddingMethod"] -String exampleMavenRepoLocalProperties = props["kaluga.exampleMavenRepo"] -String kotlinVersion = getProperty("kaluga.kotlinVersion") - -// set some global variables -gradle.ext { - kotlin_version = kotlinVersion - kotlinx_coroutines_version = '1.6.3-native-mt' - stately_version = '1.2.3' - stately_isolate_version = '1.2.3' - koin_version = '3.2.2' - serialization_version = '1.4.0' - napier_version = '2.4.0' - android_ble_scanner_version = '1.6.0' - library_version_base = '1.0.0' - library_version = libraryVersionLocalProperties ?: "$library_version_base${System.properties.kaluga_branch_postfix}" - android_min_sdk_version = 21 - android_compile_sdk_version = 33 - android_target_sdk_version = 33 - android_build_tools_version = "33.0.0" - - play_services_version = "20.0.0" - play_core_version = "1.10.3" - play_core_ktx_version = "1.8.1" - - androidx_appcompat_version = "1.5.1" - androidx_fragment_version = "1.5.3" - androidx_core_version = "1.9.0" - androidx_lifecycle_version = "2.5.1" - androidx_lifecycle_viewmodel_compose_version = "2.5.1" - androidx_arch_core_testing_version = "2.1.0" - androidx_browser_version = "1.4.0" - androidx_constraint_layout_version = "2.1.4" - - androidx_compose_compiler_version = "1.3.2" - androidx_compose_version = "1.2.1" - androidx_activity_compose_version = "1.6.0" - androidx_navigation_compose_version = "2.5.2" - - material_version = "1.6.1" - material_components_adapter_version = "1.1.17" - - // sub packages of test have different versions, but alpha/beta/rc releases are harmonized - androidx_test_version_postfix = "" - androidx_test_version = "1.4.0$androidx_test_version_postfix" - androidx_test_espresso_version = "3.4.0$androidx_test_version_postfix" - androidx_test_junit_version = "1.1.3$androidx_test_version_postfix" - - // mockito and bytebuddy need to be upgraded in lockstep - mockito_version = "3.11.2" - bytebuddy_version = "1.11.3" - - // Javascript - js_bigdecimal_version="1.0.26" - - // used/modified at runtime. - component_type_default = "default" - component_type_compose = "compose" - component_type_app = "app" - component_type_composeApp = "composeApp" - component_type = component_type_default -} - -if (System.env.containsKey("EXAMPLE_EMBEDDING_METHOD")) { - gradle.ext.example_embedding_method = System.env.EXAMPLE_EMBEDDING_METHOD - logger.lifecycle "System env EXAMPLE_EMBEDDING_METHOD set to $System.env.EXAMPLE_EMBEDDING_METHOD, using $gradle.ext.example_embedding_method" -} else { - gradle.ext.example_embedding_method = exampleEmbeddingMethodLocalProperties ?: "composite" - logger.lifecycle "local.properties read (kaluga.exampleEmbeddingMethod=$exampleEmbeddingMethodLocalProperties, using $gradle.ext.example_embedding_method)" -} - -if (System.env.containsKey("EXAMPLE_MAVEN_REPO")) { - gradle.ext.example_maven_repo = System.env.EXAMPLE_MAVEN_REPO - logger.lifecycle "System env EXAMPLE_MAVEN_REPO set to $System.env.EXAMPLE_MAVEN_REPO, using $gradle.ext.example_maven_repo" -} else { - // load some more from local.properties or set defaults. - if (exampleMavenRepoLocalProperties) { - gradle.ext.example_maven_repo = exampleMavenRepoLocalProperties - logger.lifecycle "local.properties read (kaluga.exampleMavenRepo=$exampleMavenRepoLocalProperties, using $gradle.ext.example_maven_repo)" - } else { - gradle.ext.example_maven_repo = "local" - logger.lifecycle "local.properties not found, using default value ($gradle.ext.example_maven_repo)" - } -} - -def CONNECTED_CHECK_EXPANSION = System.env.containsKey("CONNECTED_CHECK_EXPANSION") -gradle.ext.connect_check_expansion = CONNECTED_CHECK_EXPANSION | System.env.containsKey("CI") - -if (gradle.ext.connect_check_expansion) - logger.lifecycle "Adding extra dependend task to connected checks of similarly named modules (CONNECTED_CHECK_EXPANSION env present: $CONNECTED_CHECK_EXPANSION)" - -// based on https://github.com/Kotlin/xcode-compat/blob/d677a43edc46c50888bca0a7890a81f976a42809/xcode-compat/src/main/kotlin/org/jetbrains/kotlin/xcodecompat/XcodeCompatPlugin.kt#L16 -def sdkName = System.getenv("SDK_NAME") ?: "unknown" -gradle.ext.is_real_ios_device = sdkName.startsWith("iphoneos") -logger.lifecycle "Run on real ios device: $gradle.ext.is_real_ios_device from sdk: $sdkName" - -// Run on IntelliJ -def ideaActive = System.getProperty("idea.active") == "true" -gradle.ext.idea_active = ideaActive -logger.lifecycle "Run on IntelliJ: $gradle.ext.idea_active" - -// Run on apple silicon -def isAppleSilicon = System.getProperty("os.arch") == "aarch64" -gradle.ext.is_apple_silicon = isAppleSilicon -logger.lifecycle "Run on apple silicon: $gradle.ext.is_apple_silicon" - -def iosX64 = "iosX64" -def iosArm64 = "iosArm64" -def iosSimulatorArm64 = "iosSimulatorArm64" -def allIosTargets = [iosX64, iosArm64, iosSimulatorArm64] -if (!gradle.ext.idea_active) { - gradle.ext.ios_targets = allIosTargets -} else { - if (gradle.ext.is_real_ios_device) { - gradle.ext.ios_targets = [iosArm64] - } else if (gradle.ext.is_apple_silicon) { - gradle.ext.ios_targets = [iosSimulatorArm64] - } else { - gradle.ext.ios_targets = [iosX64] - } -} -logger.lifecycle "Run on ios targets: $gradle.ext.ios_targets" diff --git a/gradle/ext.gradle.kts b/gradle/ext.gradle.kts new file mode 100644 index 000000000..decfe47c5 --- /dev/null +++ b/gradle/ext.gradle.kts @@ -0,0 +1,157 @@ +// import java.util.Properties +// import java.io.* +// +// println("tasks: $gradle.startParameter.taskNames") +// +// // if the include is made from the example project shared module we need to go up a few directories +// // and depending on when we are included, we can be in the `gradle` folder or not. +// val path_prefix = when { +// file("componentskt.gradle.kts").exists() -> "" +// file("gradle/componentskt.gradle.kts").exists() -> "gradle/" +// else -> "../../../gradle/" +// } +// val f2 = file("${path_prefix}gitBranch.gradle.kts") +// +// apply(from = f2.path) +// val systemProperties = System.getProperties() +// +// val props = Properties() +// val propFile = file("../local.properties") +// if (propFile.exists()) { +// val inputStream = FileInputStream(propFile.absolutePath) +// props.load(inputStream) +// } +// +// val libraryVersionLocalProperties: String? = props["kaluga.libraryVersion"] as? String +// val exampleEmbeddingMethodLocalProperties: String? = props["kaluga.exampleEmbeddingMethod"] as? String +// val exampleMavenRepoLocalProperties: String? = props["kaluga.exampleMavenRepo"] as? String +// val kotlinVersion: String = extra["kaluga.kotlinVersion"] as String +// +// // set some global variables +// val ext = (gradle as ExtensionAware).extra +// ext.apply { +// set("kotlin_version", kotlinVersion) +// set("kotlinx_coroutines_version", "1.6.3-native-mt") +// set("stately_version", "1.2.3") +// set("stately_isolate_version", "1.2.3") +// set("koin_version", "3.2.2") +// set("serialization_version", "1.4.0") +// set("napier_version", "2.4.0") +// set("android_ble_scanner_version", "1.6.0") +// val library_version_base = "1.0.0" +// set("library_version_base", library_version_base) +// set("library_version", libraryVersionLocalProperties ?: "$library_version_base${systemProperties["kaluga_branch_postfix"]}") +// set("android_min_sdk_version", 21) +// set("android_compile_sdk_version", 33) +// set("android_target_sdk_version", 33) +// set("android_build_tools_version", "33.0.0") +// +// set("play_services_version", "20.0.0") +// set("play_core_version", "1.10.3") +// set("play_core_ktx_version", "1.8.1") +// +// set("androidx_appcompat_version", "1.5.1") +// set("androidx_fragment_version", "1.5.3") +// set("androidx_core_version", "1.9.0") +// set("androidx_lifecycle_version", "2.5.1") +// set("androidx_lifecycle_viewmodel_compose_version", "2.5.1") +// set("androidx_arch_core_testing_version", "2.1.0") +// set("androidx_browser_version", "1.4.0") +// set("androidx_constraint_layout_version", "2.1.4") +// +// set("androidx_compose_compiler_version", "1.3.2") +// set("androidx_compose_version", "1.2.1") +// set("androidx_activity_compose_version", "1.6.0") +// set("androidx_navigation_compose_version", "2.5.2") +// +// set("material_version", "1.6.1") +// set("material_components_adapter_version", "1.1.17") +// +// // sub packages of test have different versions, but alpha/beta/rc releases are harmonized +// set("junit_version", "4.13.2") +// +// val androidx_test_version_postfix = "" +// set("androidx_test_version_postfix", androidx_test_version_postfix) +// set("androidx_test_version", "1.4.0$androidx_test_version_postfix") +// set("androidx_test_espresso_version", "3.4.0$androidx_test_version_postfix") +// set("androidx_test_junit_version", "1.1.3$androidx_test_version_postfix") +// set("androidx_test_uiautomator_version", "2.2.0") +// +// // mockito and bytebuddy need to be upgraded in lockstep +// set("mockito_version", "3.11.2") +// set("bytebuddy_version", "1.11.3") +// +// // Javascript +// set("js_bigdecimal_version", "1.0.26") +// +// // used/modified at runtime. +// val component_type_default = "default" +// set("component_type_default", component_type_default) +// set("component_type_compose", "compose") +// set("component_type_app", "app") +// set("component_type_composeApp", "composeApp") +// set("component_type", component_type_default) +// } +// +// if (System.getenv().containsKey("EXAMPLE_EMBEDDING_METHOD")) { +// ext.set("example_embedding_method", System.getenv()["EXAMPLE_EMBEDDING_METHOD"]) +// logger.lifecycle("System env EXAMPLE_EMBEDDING_METHOD set to ${System.getenv()["EXAMPLE_EMBEDDING_METHOD"]}, using ${ext["example_embedding_method"]}") +// } else { +// ext.set("example_embedding_method", exampleEmbeddingMethodLocalProperties ?: "composite") +// logger.lifecycle("local.properties read (kaluga.exampleEmbeddingMethod=$exampleEmbeddingMethodLocalProperties, using ${ext["example_embedding_method"]})") +// } +// +// if (System.getenv().containsKey("EXAMPLE_MAVEN_REPO")) { +// ext.set("example_maven_repo", System.getenv()["EXAMPLE_MAVEN_REPO"]) +// logger.lifecycle("System env EXAMPLE_MAVEN_REPO set to ${System.getenv()["EXAMPLE_MAVEN_REPO"]}, using ${ext["example_maven_repo"]}") +// } else { +// // load some more from local.properties or set defaults. +// if (exampleMavenRepoLocalProperties != null) { +// ext.set("example_maven_repo", exampleMavenRepoLocalProperties) +// logger.lifecycle("local.properties read (kaluga.exampleMavenRepo=$exampleMavenRepoLocalProperties, using ${ext["example_maven_repo"]})") +// } else { +// ext.set("example_maven_repo", "local") +// logger.lifecycle("local.properties not found, using default value (${ext["example_maven_repo"]})") +// } +// } +// +// val CONNECTED_CHECK_EXPANSION = System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") +// ext.set("connect_check_expansion", CONNECTED_CHECK_EXPANSION or System.getenv().containsKey("CI")) +// +// val connect_check_expansion: Boolean by ext +// if (connect_check_expansion) +// logger.lifecycle("Adding extra dependend task to connected checks of similarly named modules (CONNECTED_CHECK_EXPANSION env present: $CONNECTED_CHECK_EXPANSION)") +// +// // based on https://github.com/Kotlin/xcode-compat/blob/d677a43edc46c50888bca0a7890a81f976a42809/xcode-compat/src/main/kotlin/org/jetbrains/kotlin/xcodecompat/XcodeCompatPlugin.kt#L16 +// val sdkName = System.getenv("SDK_NAME") ?: "unknown" +// ext.set("is_real_ios_device", sdkName.startsWith("iphoneos")) +// logger.lifecycle("Run on real ios device: ${ext["is_real_ios_device"]} from sdk: $sdkName") +// +// // Run on IntelliJ +// val ideaActive = System.getProperty("idea.active") == "true" +// ext.set("idea_active", ideaActive) +// logger.lifecycle("Run on IntelliJ: ${ext["idea_active"]}") +// +// // Run on apple silicon +// val isAppleSilicon = System.getProperty("os.arch") == "aarch64" +// ext.set("is_apple_silicon", isAppleSilicon) +// logger.lifecycle("Run on apple silicon: ${ext["is_apple_silicon"]}") +// +// val iosX64 = "iosX64" +// val iosArm64 = "iosArm64" +// val iosSimulatorArm64 = "iosSimulatorArm64" +// val allIosTargets = listOf(iosX64, iosArm64, iosSimulatorArm64) +// val idea_active: Boolean by ext +// val is_real_ios_device: Boolean by ext +// val is_apple_silicon: Boolean by ext +// +// ext.set( +// "ios_targets", +// when { +// !idea_active -> allIosTargets +// is_real_ios_device -> listOf(iosArm64) +// is_apple_silicon -> listOf(iosSimulatorArm64) +// else -> listOf(iosX64) +// } +// ) +// logger.lifecycle("Run on ios targets: ${ext["ios_targets"]}") diff --git a/gradle/gitBranch.gradle.kts b/gradle/gitBranch.gradle.kts index eb5946265..0f3e7e864 100644 --- a/gradle/gitBranch.gradle.kts +++ b/gradle/gitBranch.gradle.kts @@ -14,34 +14,34 @@ limitations under the License. */ -import org.codehaus.groovy.runtime.ProcessGroovyMethods - -val BITRISE_GIT_BRANCH = System.getenv("BITRISE_GIT_BRANCH") -val kaluga_branch = System.getProperty("kaluga_branch") -val MAVEN_CENTRAL_RELEASE = System.getenv("MAVEN_CENTRAL_RELEASE") -val release = MAVEN_CENTRAL_RELEASE?.toLowerCase()?.trim() == "true" -val branchFromGit = run { - try { - ProcessGroovyMethods.getText(ProcessGroovyMethods.execute("git rev-parse --abbrev-ref HEAD")) - } catch (e: Exception) { - logger.info("Unable to determine current branch through git CLI: ${e.message}") - "unknown" - } -} - -// favour user definition of kaluga_branch (if present), otherwise take it from GIT branch: -// - if running on CI: favour bitrise's branch detection -// - else: try to get it via the `git` CLI. -val branch = (kaluga_branch ?: BITRISE_GIT_BRANCH ?: branchFromGit ).replace('/', '-').trim().toLowerCase().also { - if (it == "HEAD") - logger.warn("Unable to determine current branch: Project is checked out with detached head!") - } - -val kaluga_branch_postfix = when(branch) { - "master", "main", "develop" -> "" - else -> "-"+branch -} + if (!release) "-SNAPSHOT" else "" - -logger.lifecycle("decided branch: '$branch' to postfix '$kaluga_branch_postfix', isRelease: $release (from: BITRISE_GIT_BRANCH env: $BITRISE_GIT_BRANCH, kaluga_branch property: $kaluga_branch , MAVEN_CENTRAL_RELEASE env: $MAVEN_CENTRAL_RELEASE , git cli: $branchFromGit)") - -System.setProperty("kaluga_branch_postfix", kaluga_branch_postfix) +// import org.codehaus.groovy.runtime.ProcessGroovyMethods +// +// val BITRISE_GIT_BRANCH = System.getenv("BITRISE_GIT_BRANCH") +// val kaluga_branch = System.getProperty("kaluga_branch") +// val MAVEN_CENTRAL_RELEASE = System.getenv("MAVEN_CENTRAL_RELEASE") +// val release = MAVEN_CENTRAL_RELEASE?.toLowerCase()?.trim() == "true" +// val branchFromGit = run { +// try { +// ProcessGroovyMethods.getText(ProcessGroovyMethods.execute("git rev-parse --abbrev-ref HEAD")) +// } catch (e: Exception) { +// logger.info("Unable to determine current branch through git CLI: ${e.message}") +// "unknown" +// } +// } +// +// // favour user definition of kaluga_branch (if present), otherwise take it from GIT branch: +// // - if running on CI: favour bitrise's branch detection +// // - else: try to get it via the `git` CLI. +// val branch = (kaluga_branch ?: BITRISE_GIT_BRANCH ?: branchFromGit ).replace('/', '-').trim().toLowerCase().also { +// if (it == "HEAD") +// logger.warn("Unable to determine current branch: Project is checked out with detached head!") +// } +// +// val kaluga_branch_postfix = when(branch) { +// "master", "main", "develop" -> "" +// else -> "-"+branch +// } + if (!release) "-SNAPSHOT" else "" +// +// logger.lifecycle("decided branch: '$branch' to postfix '$kaluga_branch_postfix', isRelease: $release (from: BITRISE_GIT_BRANCH env: $BITRISE_GIT_BRANCH, kaluga_branch property: $kaluga_branch , MAVEN_CENTRAL_RELEASE env: $MAVEN_CENTRAL_RELEASE , git cli: $branchFromGit)") +// +// System.setProperty("kaluga_branch_postfix", kaluga_branch_postfix) diff --git a/gradle/newModule.gradle.kts b/gradle/newModule.gradle.kts index 619fd06e1..0afa01508 100644 --- a/gradle/newModule.gradle.kts +++ b/gradle/newModule.gradle.kts @@ -24,7 +24,7 @@ abstract class NewModule : DefaultTask() { const val VALID_MODULE_NAME_REGEX = "^[^\\dA-Z-][a-z]+[a-z-]*\\Z" const val VALID_PACKAGE_NAME = "^[a-z]+(\\.[a-z]+)*\\Z" const val TEMPLATE_PATH = "adding-a-new-module/template" - const val BUILD_GRADLE_KTS = "build.gradle.kts" + const val BUILD_GRADLE_KTS = "build.build.gradle.kts.kts" val CREATE_DIRS = listOf( "androidLibAndroidTest" to listOf("kotlin/TestActivity.kt", "AndroidManifest.xml"), "androidLibMain" to listOf("AndroidManifest.xml"), diff --git a/gradle/publish.gradle b/gradle/publish.gradle deleted file mode 100644 index ab3e503f8..000000000 --- a/gradle/publish.gradle +++ /dev/null @@ -1,33 +0,0 @@ - -String type = gradle.ext.component_type - -afterEvaluate { - publishing { - publications { - logger.lifecycle("This project module will be published as: $gradle.ext.component_type") - if (type.contains("compose")) { - release(MavenPublication) { - from components.release - - artifactId = project.name - groupId = "com.splendo.kaluga" - version = gradle.ext.library_version - } - // Creates a Maven publication called “debug”. - debug(MavenPublication) { - from components.debug - - artifactId = project.name - groupId = "com.splendo.kaluga" - version = gradle.ext.library_version - } - } else { - kotlinMultiplatform { publication -> - artifactId = project.name - groupId = "com.splendo.kaluga" - version = gradle.ext.library_version - } - } - } - } -} diff --git a/gradle/publish.gradle.kts b/gradle/publish.gradle.kts new file mode 100644 index 000000000..5e688d7ad --- /dev/null +++ b/gradle/publish.gradle.kts @@ -0,0 +1,32 @@ +afterEvaluate { + // publishing { + // publications { + // val component_type: String by extra + // val library_version: String by extra + // logger.lifecycle("This project module will be published as: $component_type") + // if (type.contains("compose")) { + // release(MavenPublication) { + // from(components.release) + // + // artifactId = project.name + // groupId = "com.splendo.kaluga" + // version = library_version + // } + // // Creates a Maven publication called “debug”. + // debug(MavenPublication) { + // from(components.debug) + // + // artifactId = project.name + // groupId = "com.splendo.kaluga" + // version = library_version + // } + // } else { + // kotlinMultiplatform { publication -> + // artifactId = project.name + // groupId = "com.splendo.kaluga" + // version = library_version + // } + // } + // } + // } +} diff --git a/gradle/publishable_component.gradle b/gradle/publishable_component.gradle deleted file mode 100644 index ea4571356..000000000 --- a/gradle/publishable_component.gradle +++ /dev/null @@ -1,3 +0,0 @@ -apply from: "../gradle/component.gradle" -apply from: "../gradle/jacoco.gradle" -apply from: "../gradle/publish.gradle" diff --git a/gradle/publishable_component.gradle.kts b/gradle/publishable_component.gradle.kts new file mode 100644 index 000000000..130f6e847 --- /dev/null +++ b/gradle/publishable_component.gradle.kts @@ -0,0 +1,3 @@ +apply(from = "../gradle/component.gradle.kts") +apply(from = "../gradle/jacoco.gradle") +apply(from = "../gradle/publish.gradle.kts") diff --git a/hud/build.gradle.kts b/hud/build.gradle.kts index d8114e8a8..6c6b33fbf 100644 --- a/hud/build.gradle.kts +++ b/hud/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/keyboard/build.gradle.kts b/keyboard/build.gradle.kts index 69ac0ba34..60b3092ab 100644 --- a/keyboard/build.gradle.kts +++ b/keyboard/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/links/build.gradle.kts b/links/build.gradle.kts index ab346a126..11d8e6fc1 100644 --- a/links/build.gradle.kts +++ b/links/build.gradle.kts @@ -26,7 +26,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/location-permissions/build.gradle.kts b/location-permissions/build.gradle.kts index 90d5840d9..7135eda09 100644 --- a/location-permissions/build.gradle.kts +++ b/location-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/location/build.gradle.kts b/location/build.gradle.kts index 642a74528..9710fc232 100644 --- a/location/build.gradle.kts +++ b/location/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/logging/build.gradle.kts b/logging/build.gradle.kts index effc9e9cc..8685d502a 100644 --- a/logging/build.gradle.kts +++ b/logging/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/microphone-permissions/build.gradle.kts b/microphone-permissions/build.gradle.kts index 90d5840d9..7135eda09 100644 --- a/microphone-permissions/build.gradle.kts +++ b/microphone-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/notifications-permissions/build.gradle.kts b/notifications-permissions/build.gradle.kts index 90d5840d9..7135eda09 100644 --- a/notifications-permissions/build.gradle.kts +++ b/notifications-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/permissions/build.gradle.kts b/permissions/build.gradle.kts index 0c746620f..d8be8917b 100644 --- a/permissions/build.gradle.kts +++ b/permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/resources/build.gradle.kts b/resources/build.gradle.kts index 44368a2d2..5e705248b 100644 --- a/resources/build.gradle.kts +++ b/resources/build.gradle.kts @@ -9,7 +9,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/review/build.gradle.kts b/review/build.gradle.kts index 877d673d0..69842b7c7 100644 --- a/review/build.gradle.kts +++ b/review/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/scientific/build.gradle.kts b/scientific/build.gradle.kts index 42a73f62c..68e1d7415 100644 --- a/scientific/build.gradle.kts +++ b/scientific/build.gradle.kts @@ -9,7 +9,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/settings.gradle.kts b/settings.gradle.kts index 5a29f0e16..040d0154b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,7 +44,7 @@ pluginManagement { } } -apply("gradle/ext.gradle") +apply("gradle/ext.gradle.kts") includeBuild("convention-plugins") rootProject.name = "Kaluga" diff --git a/storage-permissions/build.gradle.kts b/storage-permissions/build.gradle.kts index 90d5840d9..7135eda09 100644 --- a/storage-permissions/build.gradle.kts +++ b/storage-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/system/build.gradle.kts b/system/build.gradle.kts index a95221eab..1d02e0dda 100644 --- a/system/build.gradle.kts +++ b/system/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-alerts/build.gradle.kts b/test-utils-alerts/build.gradle.kts index fb32638f8..758be76a9 100644 --- a/test-utils-alerts/build.gradle.kts +++ b/test-utils-alerts/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-architecture/build.gradle.kts b/test-utils-architecture/build.gradle.kts index 109f1e24d..869b0f540 100644 --- a/test-utils-architecture/build.gradle.kts +++ b/test-utils-architecture/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-base/build.gradle.kts b/test-utils-base/build.gradle.kts index 0502733c4..bea107930 100644 --- a/test-utils-base/build.gradle.kts +++ b/test-utils-base/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-bluetooth/build.gradle.kts b/test-utils-bluetooth/build.gradle.kts index 9431a27ac..514bee8f4 100644 --- a/test-utils-bluetooth/build.gradle.kts +++ b/test-utils-bluetooth/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-hud/build.gradle.kts b/test-utils-hud/build.gradle.kts index ad563fdf2..15a0d34a8 100644 --- a/test-utils-hud/build.gradle.kts +++ b/test-utils-hud/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-keyboard/build.gradle.kts b/test-utils-keyboard/build.gradle.kts index 4bb39b2a1..2f6d6ce4d 100644 --- a/test-utils-keyboard/build.gradle.kts +++ b/test-utils-keyboard/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-koin/build.gradle.kts b/test-utils-koin/build.gradle.kts index 4e1c0c01a..226c3a650 100644 --- a/test-utils-koin/build.gradle.kts +++ b/test-utils-koin/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-location/build.gradle.kts b/test-utils-location/build.gradle.kts index 8f2d270c3..3e32bc3fb 100644 --- a/test-utils-location/build.gradle.kts +++ b/test-utils-location/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-permissions/build.gradle.kts b/test-utils-permissions/build.gradle.kts index ff8dcd066..2f087c8af 100644 --- a/test-utils-permissions/build.gradle.kts +++ b/test-utils-permissions/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils-resources/build.gradle.kts b/test-utils-resources/build.gradle.kts index 444fa0e33..c31e0e675 100644 --- a/test-utils-resources/build.gradle.kts +++ b/test-utils-resources/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index 181ed6ccd..fe43c23dc 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -8,7 +8,7 @@ plugins { val ext = (gradle as ExtensionAware).extra -apply(from = "../gradle/publishable_component.gradle") +apply(from = "../gradle/publishable_component.gradle.kts") group = "com.splendo.kaluga" version = ext["library_version"]!! From aea0787b0de3d9fc256bad04d4327e0ce69c0dcd Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 10 Oct 2022 09:35:48 +0200 Subject: [PATCH 003/227] Gradle migration continued --- alerts/build.gradle.kts | 13 +- architecture-compose/build.gradle.kts | 21 +-- architecture/build.gradle.kts | 21 +-- base-permissions/build.gradle.kts | 13 +- base/build.gradle.kts | 19 +-- .../kotlin/text/NumberFormatterTest.kt | 4 +- beacons/build.gradle.kts | 7 +- bluetooth-permissions/build.gradle.kts | 15 +- bluetooth/build.gradle.kts | 13 +- build.gradle.kts | 1 - buildSrc/build.gradle.kts | 17 +- buildSrc/src/main/kotlin/Accessors.kt | 55 ++++++ buildSrc/src/main/kotlin/AndroidCommon.kt | 96 ++++++----- buildSrc/src/main/kotlin/AndroidCompose.kt | 43 +++++ buildSrc/src/main/kotlin/Component.kt | 131 ++++++++++++--- buildSrc/src/main/kotlin/Dependencies.kt | 28 +++- buildSrc/src/main/kotlin/GitBranch.kt | 9 +- buildSrc/src/main/kotlin/Library.kt | 65 ++++---- buildSrc/src/main/kotlin/Publish.kt | 56 +++++++ .../src/main/kotlin/PublishableComponent.kt | 23 +++ calendar-permissions/build.gradle.kts | 13 +- camera-permissions/build.gradle.kts | 15 +- contacts-permissions/build.gradle.kts | 15 +- date-time-picker/build.gradle.kts | 16 +- date-time/build.gradle.kts | 8 +- gradle/android_common.gradle.kts | 128 -------------- gradle/android_compose.gradle.kts | 49 ------ gradle/component.gradle.kts | 55 ------ gradle/componentskt.gradle.kts | 35 ---- gradle/ext.gradle.kts | 157 ------------------ gradle/gitBranch.gradle.kts | 47 ------ gradle/publish.gradle.kts | 32 ---- gradle/publishable_component.gradle.kts | 3 - gradle/wrapper/gradle-wrapper.properties | 5 +- hud/build.gradle.kts | 14 +- keyboard-compose/build.gradle.kts | 21 +-- keyboard/build.gradle.kts | 7 +- links/build.gradle.kts | 10 +- location-permissions/build.gradle.kts | 13 +- location/build.gradle.kts | 12 +- logging/build.gradle.kts | 18 +- microphone-permissions/build.gradle.kts | 15 +- notifications-permissions/build.gradle.kts | 15 +- permissions/build.gradle.kts | 9 +- resources-compose/build.gradle.kts | 25 +-- resources/build.gradle.kts | 16 +- review/build.gradle.kts | 15 +- scientific/build.gradle.kts | 17 +- settings.gradle.kts | 1 - storage-permissions/build.gradle.kts | 15 +- system/build.gradle.kts | 13 +- test-utils-alerts/build.gradle.kts | 9 +- test-utils-architecture/build.gradle.kts | 9 +- test-utils-base/build.gradle.kts | 22 +-- test-utils-bluetooth/build.gradle.kts | 9 +- test-utils-hud/build.gradle.kts | 9 +- test-utils-keyboard/build.gradle.kts | 9 +- test-utils-koin/build.gradle.kts | 11 +- test-utils-location/build.gradle.kts | 9 +- test-utils-permissions/build.gradle.kts | 9 +- test-utils-resources/build.gradle.kts | 9 +- test-utils/build.gradle.kts | 9 +- 62 files changed, 519 insertions(+), 1059 deletions(-) create mode 100644 buildSrc/src/main/kotlin/Accessors.kt create mode 100644 buildSrc/src/main/kotlin/AndroidCompose.kt create mode 100644 buildSrc/src/main/kotlin/Publish.kt create mode 100644 buildSrc/src/main/kotlin/PublishableComponent.kt delete mode 100644 gradle/android_common.gradle.kts delete mode 100644 gradle/android_compose.gradle.kts delete mode 100644 gradle/component.gradle.kts delete mode 100644 gradle/componentskt.gradle.kts delete mode 100644 gradle/ext.gradle.kts delete mode 100644 gradle/gitBranch.gradle.kts delete mode 100644 gradle/publish.gradle.kts delete mode 100644 gradle/publishable_component.gradle.kts diff --git a/alerts/build.gradle.kts b/alerts/build.gradle.kts index b66f5c421..09602afd9 100644 --- a/alerts/build.gradle.kts +++ b/alerts/build.gradle.kts @@ -6,19 +6,12 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -group = Library.group -version = Library.version - dependencies { - - val ext = (gradle as ExtensionAware).extra - val androidx_fragment_version: String by ext - - implementation(Dependencies.AndroidX.Fragment.notation) - testImplementation(Dependencies.AndroidX.FragmentKtx.notation) + implement(Dependencies.AndroidX.Fragment) + implementForTest(Dependencies.AndroidX.FragmentKtx) } -commonComponent() +publishableComponent() kotlin { sourceSets { diff --git a/architecture-compose/build.gradle.kts b/architecture-compose/build.gradle.kts index 6490880b1..1b311bbdb 100644 --- a/architecture-compose/build.gradle.kts +++ b/architecture-compose/build.gradle.kts @@ -24,25 +24,12 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra -ext["component_type"] = ext["component_type_compose"] - -// if the include is made from the example project shared module we need to go up one more directory -val path_prefix = if (file("../gradle/componentskt.gradle.kts").exists()) - ".." else "../.." - -apply(from = "$path_prefix/gradle/android_compose.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -ext["component_type"] = ext["component_type_default"] +composeAndroidComponent() dependencies { api(project(":base")) api(project(":architecture")) - val ext = (gradle as ExtensionAware).extra - implementation("androidx.compose.material:material:" + ext["androidx_compose_version"]) - implementation("androidx.navigation:navigation-compose:" + ext["androidx_navigation_compose_version"]) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${ext["kotlinx_coroutines_version"]}!!") + implement(Dependencies.AndroidX.Compose.Material) + implement(Dependencies.AndroidX.Navigation.Compose) + implement(Dependencies.KotlinX.Coroutines.Core) } diff --git a/architecture/build.gradle.kts b/architecture/build.gradle.kts index d1aa553cf..b539bf767 100644 --- a/architecture/build.gradle.kts +++ b/architecture/build.gradle.kts @@ -7,12 +7,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() dependencies { val ext = (gradle as ExtensionAware).extra @@ -20,11 +15,11 @@ dependencies { val androidx_lifecycle_version: String by ext val androidx_browser_version: String by ext - api("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - api("androidx.lifecycle:lifecycle-runtime-ktx:$androidx_lifecycle_version") - api("androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_lifecycle_version") - api("androidx.lifecycle:lifecycle-livedata-ktx:$androidx_lifecycle_version") - implementation("androidx.browser:browser:$androidx_browser_version") + api("org.jetbrains.kotlin:kotlin-reflect:${Library.kotlinVersion}") + expose(Dependencies.AndroidX.Lifecycle.Runtime) + expose(Dependencies.AndroidX.Lifecycle.ViewModel) + expose(Dependencies.AndroidX.Lifecycle.LiveData) + implement(Dependencies.AndroidX.Browser) } kotlin { @@ -35,8 +30,8 @@ kotlin { getByName("commonMain") { dependencies { implementation(project(":base", "")) - api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version") + expose(Dependencies.KotlinX.Serialization.Core) + expose(Dependencies.KotlinX.Serialization.Json) } } diff --git a/base-permissions/build.gradle.kts b/base-permissions/build.gradle.kts index a75815832..23ee29621 100644 --- a/base-permissions/build.gradle.kts +++ b/base-permissions/build.gradle.kts @@ -6,18 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { diff --git a/base/build.gradle.kts b/base/build.gradle.kts index eea324c02..f965cef72 100644 --- a/base/build.gradle.kts +++ b/base/build.gradle.kts @@ -6,12 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() kotlin { sourceSets { @@ -21,17 +16,17 @@ kotlin { getByName("commonMain") { dependencies { implementation(project(":logging", "")) - api("co.touchlab:stately-common:${ext["stately_version"]}") - api("co.touchlab:stately-isolate:${ext["stately_isolate_version"]}") - api("co.touchlab:stately-iso-collections:${ext["stately_isolate_version"]}") - api("co.touchlab:stately-concurrency:${ext["stately_version"]}") + expose(Dependencies.Stately.Common) + expose(Dependencies.Stately.Isolate) + expose(Dependencies.Stately.IsoCollections) + expose(Dependencies.Stately.Concurrency) } } getByName("jsMain") { dependencies { - implementation(kotlin("stdlib-common", "${ext["kotlin_version"]}")) + implementation(kotlin("stdlib-common", Library.kotlinVersion)) // JavaScript BigDecimal lib based on native BigInt - implementation(npm("@splendo/bigdecimal", "${ext["js_bigdecimal_version"]}")) + implementation(npm("@splendo/bigdecimal", "1.0.26")) } } getByName("jsTest") { diff --git a/base/src/commonTest/kotlin/text/NumberFormatterTest.kt b/base/src/commonTest/kotlin/text/NumberFormatterTest.kt index a32421d73..5bba4ea05 100644 --- a/base/src/commonTest/kotlin/text/NumberFormatterTest.kt +++ b/base/src/commonTest/kotlin/text/NumberFormatterTest.kt @@ -4,9 +4,9 @@ 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. diff --git a/beacons/build.gradle.kts b/beacons/build.gradle.kts index ff11a0d7c..b9d2292be 100644 --- a/beacons/build.gradle.kts +++ b/beacons/build.gradle.kts @@ -6,12 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() kotlin { sourceSets { diff --git a/bluetooth-permissions/build.gradle.kts b/bluetooth-permissions/build.gradle.kts index 459b1b9a1..ce7e10c83 100644 --- a/bluetooth-permissions/build.gradle.kts +++ b/bluetooth-permissions/build.gradle.kts @@ -6,24 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/bluetooth/build.gradle.kts b/bluetooth/build.gradle.kts index 1793ae034..837d47951 100644 --- a/bluetooth/build.gradle.kts +++ b/bluetooth/build.gradle.kts @@ -6,16 +6,10 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() dependencies { - val ext = (gradle as ExtensionAware).extra - implementation("no.nordicsemi.android.support.v18:scanner:${ext["android_ble_scanner_version"]}") + implement(Dependencies.BLEScanner) implementation(project(":location", "")) } @@ -23,9 +17,8 @@ kotlin { sourceSets { commonMain { dependencies { - val ext = (gradle as ExtensionAware).extra api(project(":bluetooth-permissions", "")) - implementation("co.touchlab:stately-concurrency:${ext["stately_version"]}") + implement(Dependencies.Stately.Concurrency) } } commonTest { diff --git a/build.gradle.kts b/build.gradle.kts index 77f56ba5a..471734431 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,7 +65,6 @@ apiValidation { ignoredClasses.add("com.splendo.kaluga.datetime.timer.BuildConfig") } -apply(from = "gradle/ext.gradle.kts") apply(from = "gradle/newModule.gradle.kts") apply(from = "gradle/copyReports.gradle.kts") diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index caa676a5b..d22264d20 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,16 +1,29 @@ +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.konan.properties.loadProperties + plugins { `java-gradle-plugin` `kotlin-dsl` `kotlin-dsl-precompiled-script-plugins` + `maven-publish` } repositories { gradlePluginPortal() // To use 'maven-publish' and 'signing' plugins in our own plugin google() + mavenCentral() } dependencies { + + val properties = File("${rootDir.absolutePath}/../gradle.properties").loadProperties() + val kotlinVersion = properties["kaluga.kotlinVersion"] as String + val androidGradleVersion = properties["kaluga.androidGradlePluginVersion"] as String + val ktLintVersion = properties["kaluga.ktLintGradlePluginVersion"] as String + logger.lifecycle("Kotlin version $kotlinVersion") + // mostly migrated to new style plugin declarations, but some cross plugin interaction still requires this - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20") - implementation("com.android.tools.build:gradle:7.3.0") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + implementation("com.android.tools.build:gradle:$androidGradleVersion") + implementation("org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin:$ktLintVersion") } diff --git a/buildSrc/src/main/kotlin/Accessors.kt b/buildSrc/src/main/kotlin/Accessors.kt new file mode 100644 index 000000000..ae5f0538e --- /dev/null +++ b/buildSrc/src/main/kotlin/Accessors.kt @@ -0,0 +1,55 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Action +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +fun Project.android(action: LibraryExtension.() -> Unit) { + configureAction("android", action) +} + +fun Project.kotlinAndroid(action: KotlinAndroidProjectExtension.() -> Unit) { + configureAction("kotlin", action) +} + +fun Project.kotlinMultiplatform(action: KotlinMultiplatformExtension.() -> Unit) { + configureAction("kotlin", action) +} + +fun Project.ktlint(action: org.jlleitschuh.gradle.ktlint.KtlintExtension.() -> Unit) { + configureAction("ktlint", action) +} + +fun Project.publishing(action: org.gradle.api.publish.PublishingExtension.() -> Unit) { + configureAction("publishing", action) +} + +fun LibraryExtension.kotlinOptions(action: org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions.() -> Unit) { + (this as org.gradle.api.plugins.ExtensionAware).configureAction("kotlinOptions", action) +} + +private inline fun org.gradle.api.plugins.ExtensionAware.configureAction(fieldName: String, crossinline action: T.() -> Unit) { + val optionsAction = object : Action { + override fun execute(t: T) { + t.action() + } + } + extensions.configure(fieldName, optionsAction) +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/AndroidCommon.kt b/buildSrc/src/main/kotlin/AndroidCommon.kt index f9483a29f..365e527c1 100644 --- a/buildSrc/src/main/kotlin/AndroidCommon.kt +++ b/buildSrc/src/main/kotlin/AndroidCommon.kt @@ -16,30 +16,40 @@ */ import com.android.build.gradle.LibraryExtension -import org.gradle.api.Action import org.gradle.api.JavaVersion import org.gradle.kotlin.dsl.dependencies -enum class ComponentType { - DEFAULT, - COMPOSE, - APP, - COMPOSE_APP -} - -fun org.gradle.api.Project.commonAndroidComponent(type: ComponentType = ComponentType.DEFAULT) { - val action = object : Action { - override fun execute(t: LibraryExtension) { - t.androidCommon(type) - } +fun org.gradle.api.Project.commonAndroidComponent(type: ComponentType = ComponentType.Default()) { + android { + androidCommon(this@commonAndroidComponent, type) } - (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", action) - dependencies { + dependencies { + implement(Dependencies.KotlinX.Coroutines.Android) + implement(Dependencies.AndroidX.AppCompat) + + implementForTest(Dependencies.JUnit) + implementForTest(Dependencies.Mockito.Core) + implementForTest(Dependencies.Kotlin.Test) + implementForTest(Dependencies.Kotlin.JUnit) + + implementForAndroidTest(Dependencies.Mockito.Android) + implementForAndroidTest(Dependencies.ByteBuddy.Android) + implementForAndroidTest(Dependencies.ByteBuddy.Agent) + + implementForAndroidTest(Dependencies.AndroidX.Test.Core) + implementForAndroidTest(Dependencies.AndroidX.Test.CoreKtx) + implementForAndroidTest(Dependencies.AndroidX.Test.UIAutomator) + implementForAndroidTest(Dependencies.AndroidX.Test.Rules) + implementForAndroidTest(Dependencies.AndroidX.Test.JUnit) + implementForAndroidTest(Dependencies.AndroidX.Test.Runner) + implementForAndroidTest(Dependencies.AndroidX.Test.Espresso) + implementForAndroidTest(Dependencies.Kotlin.Test) + implementForAndroidTest(Dependencies.Kotlin.JUnit) } } -fun LibraryExtension.androidCommon(componentType: ComponentType = ComponentType.DEFAULT) { +fun LibraryExtension.androidCommon(project: org.gradle.api.Project, componentType: ComponentType = ComponentType.Default()) { compileSdk = Library.Android.compileSdk buildToolsVersion = Library.Android.buildTools @@ -56,31 +66,26 @@ fun LibraryExtension.androidCommon(componentType: ComponentType = ComponentType. } } - when (componentType) { - ComponentType.APP, - ComponentType.COMPOSE_APP -> { - println("Android sourcesets for this project module are configured using defaults (for an app)") - } - ComponentType.COMPOSE, - ComponentType.DEFAULT -> { - println("Android sourcesets for this project module are configured as a library") - sourceSets { - getByName("main") { - manifest.srcFile("src/androidLibMain/AndroidManifest.xml") - res.srcDir("src/androidLibMain/res") - if (componentType == ComponentType.COMPOSE) { - java.srcDir("src/androidLibMain/kotlin") - } - } - getByName("androidTest") { - manifest.srcFile("src/androidLibAndroidTest/AndroidManifest.xml") - java.srcDir("src/androidLibAndroidTest/kotlin") - res.srcDir("src/androidLibAndroidTest/res") + if (componentType.isApp) { + project.logger.lifecycle("Android sourcesets for this project module are configured using defaults (for an app)") + } else { + project.logger.lifecycle("Android sourcesets for this project module are configured as a library") + sourceSets { + getByName("main") { + manifest.srcFile("src/androidLibMain/AndroidManifest.xml") + res.srcDir("src/androidLibMain/res") + if (componentType is ComponentType.Compose) { + java.srcDir("src/androidLibMain/kotlin") } + } + getByName("androidTest") { + manifest.srcFile("src/androidLibAndroidTest/AndroidManifest.xml") + java.srcDir("src/androidLibAndroidTest/kotlin") + res.srcDir("src/androidLibAndroidTest/res") + } - getByName("test") { - java.srcDir("src/androidLibUnitTest/kotlin") - } + getByName("test") { + java.srcDir("src/androidLibUnitTest/kotlin") } } } @@ -90,10 +95,14 @@ fun LibraryExtension.androidCommon(componentType: ComponentType = ComponentType. targetCompatibility = JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + listOf("-XXLanguage:+InlineClasses", "-Xjvm-default=all") + } + when (componentType) { - ComponentType.COMPOSE, - ComponentType.COMPOSE_APP -> { - println("This project module is a Compose only module") + is ComponentType.Compose -> { + project.logger.lifecycle("This project module is a Compose only module") buildFeatures { compose = true } @@ -101,7 +110,6 @@ fun LibraryExtension.androidCommon(componentType: ComponentType = ComponentType. kotlinCompilerExtensionVersion = Library.Android.composeCompiler } } - ComponentType.DEFAULT, - ComponentType.APP -> {} + is ComponentType.Default-> {} } } diff --git a/buildSrc/src/main/kotlin/AndroidCompose.kt b/buildSrc/src/main/kotlin/AndroidCompose.kt new file mode 100644 index 000000000..2f3997033 --- /dev/null +++ b/buildSrc/src/main/kotlin/AndroidCompose.kt @@ -0,0 +1,43 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import org.gradle.kotlin.dsl.dependencies + +fun org.gradle.api.Project.composeAndroidComponent(type: ComponentType.Compose = ComponentType.Compose()) { + group = Library.group + version = Library.version + commonAndroidComponent(type) + dependencies { + implement(Dependencies.AndroidX.Compose.Foundation) + implement(Dependencies.AndroidX.Compose.UI) + implement(Dependencies.AndroidX.Compose.UITooling) + implement(Dependencies.AndroidX.Lifecycle.ViewModelCompose) + implement(Dependencies.AndroidX.Activity.Compose) + } + + kotlinAndroid { + sourceSets.all { + languageSettings { + optIn("androidx.compose.material.ExperimentalMaterialApi") + } + } + } + + if (!type.isApp) { + publish(type) + } +} diff --git a/buildSrc/src/main/kotlin/Component.kt b/buildSrc/src/main/kotlin/Component.kt index e7b31703b..e2c5c0813 100644 --- a/buildSrc/src/main/kotlin/Component.kt +++ b/buildSrc/src/main/kotlin/Component.kt @@ -15,23 +15,73 @@ */ -import org.gradle.api.Action +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Project +import org.gradle.api.tasks.Copy import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTests +import org.jetbrains.kotlin.konan.file.File -fun org.gradle.api.Project.commonComponent() { - val action = object : Action { - override fun execute(t: KotlinMultiplatformExtension) { - t.commonMultiplatformComponent() +sealed class ComponentType { + abstract val isApp: Boolean + + data class Default(override val isApp: Boolean = false) : ComponentType() + data class Compose(override val isApp: Boolean = false) : ComponentType() +} + +fun Project.commonComponent() { + group = Library.group + version = Library.version + kotlinMultiplatform { + commonMultiplatformComponent(this@commonComponent) + } + + commonAndroidComponent() + android { + commonMultiplatformComponentAndroid(this@commonComponent) + } + + task("printConfigurations") { + doLast { + configurations.all { println(this) } + } + } + + afterEvaluate { + Library.IOS.targets.forEach { + val targetName = it.sourceSetName + if (tasks.names.contains("linkDebugTest${targetName.capitalize() }")) { + // creating copy task for the target + val copyTask = tasks.create("copy${targetName.capitalize() }TestResources", Copy::class.java) { + from(File("src/iosTest/resources/.")) + into(File("$buildDir/bin/$targetName/debugTest")) + } + + // apply copy task to the target + tasks.named("linkDebugTest${targetName.capitalize()}") { + dependsOn(copyTask) + } + } + } + } + + ktlint { disabledRules.set(listOf("no-wildcard-imports", "filename", "import-ordering")) } + + if (Library.connectCheckExpansion) { + parent?.subprojects?.filter { + it.name.startsWith("${project.name}-") || it.name.endsWith("-${project.name}") + }?.forEach { + logger.info("[connect_check_expansion] :${project.name}:connectedDebugAndroidTest dependsOn:${name}:connectedDebugAndroidTest") + tasks.getByPath("connectedDebugAndroidTest") + .dependsOn(":${name}:connectedDebugAndroidTest") } } - (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin", action) } -fun KotlinMultiplatformExtension.commonMultiplatformComponent() { +fun KotlinMultiplatformExtension.commonMultiplatformComponent(currentProject: Project) { targets { configureEach { compilations.configureEach { @@ -49,16 +99,20 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent() { } } } - Library.IOS.targets.forEach { iosTarget -> + val targets = currentProject.Library.IOS.targets + targets.forEach { iosTarget -> when (iosTarget) { - IOSTarget.X64 -> iosX64(target).applyTestDevice() + IOSTarget.X64 -> iosX64(target).applyTestDevice(currentProject) IOSTarget.Arm64 -> iosArm64(target) - IOSTarget.SimulatorArm64 -> iosSimulatorArm64(target).applyTestDevice() + IOSTarget.SimulatorArm64 -> iosSimulatorArm64(target).applyTestDevice(currentProject) } } jvm() js(KotlinJsCompilerType.IR) { + // Disable JS browser tests for now + // See https://github.com/splendo/kaluga/issues/97 + // browser() nodejs() compilations.configureEach { kotlinOptions { @@ -69,13 +123,13 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent() { } } - val commonMain = sourceSets.maybeCreate("commonMain").apply { + val commonMain = sourceSets.getByName("commonMain").apply { dependencies { implement(Dependencies.KotlinX.Coroutines.Core) } } - val commonTest = sourceSets.maybeCreate("commonTest").apply { + val commonTest = sourceSets.getByName("commonTest").apply { dependencies { implementation(kotlin("test")) implementation(kotlin("test-common")) @@ -83,14 +137,14 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent() { } } - val jvmMain = sourceSets.maybeCreate("jvmMain").apply { + val jvmMain = sourceSets.getByName("jvmMain").apply { dependencies { implementation(kotlin("stdlib")) implement(Dependencies.KotlinX.Coroutines.Swing) } } - val jvmTest = sourceSets.maybeCreate("jvmTest").apply { + val jvmTest = sourceSets.getByName("jvmTest").apply { dependsOn(commonTest) dependencies { implementation(kotlin("test")) @@ -98,14 +152,14 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent() { } } - val jsMain = sourceSets.maybeCreate("jsMain").apply { + val jsMain = sourceSets.getByName("jsMain").apply { dependencies { implementation(kotlin("stdlib-js")) implement(Dependencies.KotlinX.Coroutines.Js) } } - val jsTest = sourceSets.maybeCreate("jsTest").apply { + val jsTest = sourceSets.getByName("jsTest").apply { dependencies { implementation(kotlin("test-js")) } @@ -119,6 +173,17 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent() { dependsOn(commonTest) } + targets.forEach { + val sourceSetName = it.sourceSetName + + sourceSets.getByName("${sourceSetName}Main").apply { + dependsOn(iosMain) + } + sourceSets.getByName("${sourceSetName}Test").apply { + dependsOn(iosTest) + } + } + sourceSets.all { languageSettings { optIn("kotlinx.coroutines.DelicateCoroutinesApi") @@ -135,8 +200,36 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent() { } } -fun KotlinNativeTargetWithSimulatorTests.applyTestDevice() { +fun LibraryExtension.commonMultiplatformComponentAndroid(project: Project) { + testOptions { + unitTests.isReturnDefaultValues = true + } + + packagingOptions { + resources.excludes.addAll( + listOf( + "META-INF/kotlinx-coroutines-core.kotlin_module", + "META-INF/shared_debug.kotlin_module", + "META-INF/kotlinx-serialization-runtime.kotlin_module", + "META-INF/AL2.0", + "META-INF/LGPL2.1", + // bytebuddy 🤡 + "win32-x86-64/attach_hotspot_windows.dll", + "win32-x86/attach_hotspot_windows.dll", + "META-INF/licenses/ASM" + ) + ) + } +} + +fun KotlinNativeTargetWithSimulatorTests.applyTestDevice(project: Project) { testRuns.all { - deviceId = Library.IOS.TestRunnerDeviceId + deviceId = project.Library.IOS.TestRunnerDeviceId } -} \ No newline at end of file +} + +val IOSTarget.sourceSetName: String get() = when (this) { + IOSTarget.X64 -> "iosX64" + IOSTarget.Arm64 -> "iosArm64" + IOSTarget.SimulatorArm64 -> "iosSimulatorArm64" +} diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 3142a2f1d..c3c6c3dd6 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -25,15 +25,41 @@ data class Dependency(private val group: String, private val name: String, priva fun KotlinDependencyHandler.expose(dependency: Dependency) = api(dependency.notation) fun KotlinDependencyHandler.implement(dependency: Dependency) = implementation(dependency.notation) +fun DependencyHandler.expose(dependency: Dependency) { + add("api", dependency.notation) +} +fun DependencyHandler.implement(dependency: Dependency) { + add("implementation", dependency.notation) +} +fun DependencyHandler.implementForDebug(dependency: Dependency) { + add("debugImplementation", dependency.notation) +} +fun DependencyHandler.implementForTest(dependency: Dependency) { + add("testImplementation", dependency.notation) +} +fun DependencyHandler.implementForAndroidTest(dependency: Dependency) { + add("androidTestImplementation", dependency.notation) +} + object Dependencies { + object Kotlin { + private const val group = "org.jetbrains.kotlin" + val Test = Dependency(group, "kotlin-test") + val JUnit = Dependency(group, "kotlin-test-junit") + } + object KotlinX { object Coroutines { private const val version = "1.6.3-native-mt" private const val group = "org.jetbrains.kotlinx" + val Android = Dependency(group, "kotlinx-coroutines-android", version) val Core = Dependency(group, "kotlinx-coroutines-core", version) val Swing = Dependency(group, "kotlinx-coroutines-swing", version) val Js = Dependency(group, "kotlinx-coroutines-js", version) + val PlayServices = Dependency(group, "kotlinx-coroutines-play-services", version) + val Test = Dependency(group, "kotlinx-coroutines-test", version) + val Debug = Dependency(group, "kotlinx-coroutines-debug", version) } object Serialization { @@ -54,7 +80,7 @@ object Dependencies { object Play { private const val group = "$groupBase.play" val Core = Dependency(group, "core", "1.10.3") - val Ktx = Dependency(group, "core-ktx", "1.8.1") + val CoreKtx = Dependency(group, "core-ktx", "1.8.1") } object PlayServices { private const val group = "$groupBase.gms" diff --git a/buildSrc/src/main/kotlin/GitBranch.kt b/buildSrc/src/main/kotlin/GitBranch.kt index 96cce8d6d..2005ec957 100644 --- a/buildSrc/src/main/kotlin/GitBranch.kt +++ b/buildSrc/src/main/kotlin/GitBranch.kt @@ -15,10 +15,11 @@ */ -import org.slf4j.LoggerFactory +import org.gradle.api.Project -object GitBranch { - private val logger = LoggerFactory.getLogger(this::class.java) +data class GitBranch(val branch: String, val kalugaBranchPostfix: String) + +val Project.GitBranch: GitBranch get() { val BITRISE_GIT_BRANCH = java.lang.System.getenv("BITRISE_GIT_BRANCH") val kaluga_branch = System.getProperty("kaluga_branch") val MAVEN_CENTRAL_RELEASE = System.getenv("MAVEN_CENTRAL_RELEASE") @@ -49,4 +50,6 @@ object GitBranch { println("decided branch: '$branch' to postfix '$it', isRelease: $release (from: BITRISE_GIT_BRANCH env: $BITRISE_GIT_BRANCH, kaluga_branch property: $kaluga_branch , MAVEN_CENTRAL_RELEASE env: $MAVEN_CENTRAL_RELEASE , git cli: $branchFromGit)") } + + return GitBranch(branch, kalugaBranchPostfix) } diff --git a/buildSrc/src/main/kotlin/Library.kt b/buildSrc/src/main/kotlin/Library.kt index f5bb8870f..72556cc8b 100644 --- a/buildSrc/src/main/kotlin/Library.kt +++ b/buildSrc/src/main/kotlin/Library.kt @@ -15,29 +15,33 @@ */ -import java.io.File -import java.io.FileInputStream -import java.util.* +import org.gradle.api.Project +import org.gradle.kotlin.dsl.extra +import org.gradle.api.logging.Logger +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.konan.properties.loadProperties +import org.jetbrains.kotlin.konan.properties.Properties +import java.io.IOException -object Library { +private val libraries: MutableMap = mutableMapOf() - private val props = Properties() - private const val baseVersion = "1.0.0" +val Project.Library get() = libraries.getOrPut(this) { Library(this) } + +class Library(project: Project) { + + private val props: Properties = File("${project.rootProject.buildDir.absolutePath}/../local.properties").loadProperties() + private val logger = project.logger + private val baseVersion = "1.0.0" + val group = "com.splendo.kaluga" val version: String by lazy { - println("------Properties" + props.entries.joinToString(" ") { (key, value) -> "$key to $value" }) val libraryVersionLocalProperties: String? = props["kaluga.libraryVersion"] as? String - (libraryVersionLocalProperties ?: "$baseVersion${GitBranch.kalugaBranchPostfix}").also { + (libraryVersionLocalProperties ?: "$baseVersion${project.GitBranch.kalugaBranchPostfix}").also { println("Library version $it") } } - const val group = "com.splendo.kaluga" - - init { - val propFile = File("../local.properties") - if (propFile.exists()) { - val inputStream = FileInputStream(propFile.absolutePath) - props.load(inputStream) - } + val kotlinVersion = project.extra["kaluga.kotlinVersion"] as? String ?: kotlin.run { + logger.lifecycle("Missing kotlin version") + throw IOException("Provide kaluga.kotlinVersion in your gradle.properties") } object Android { @@ -48,21 +52,21 @@ object Library { const val composeCompiler = "1.3.2" } - object IOS { + class IOSLibrary(props: Properties, logger: Logger) { // based on https://github.com/Kotlin/xcode-compat/blob/d677a43edc46c50888bca0a7890a81f976a42809/xcode-compat/src/main/kotlin/org/jetbrains/kotlin/xcodecompat/XcodeCompatPlugin.kt#L16 val sdkName = System.getenv("SDK_NAME") ?: "unknown" val isRealIOSDevice = sdkName.startsWith("iphoneos").also { - println("Run on real ios device: $it from sdk: $sdkName") + logger.lifecycle("Run on real ios device: $it from sdk: $sdkName") } // Run on IntelliJ val ideaActive = (System.getProperty("idea.active") == "true").also { - println("Run on IntelliJ: $it") + logger.lifecycle("Run on IntelliJ: $it") } // Run on apple silicon val isAppleSilicon = (System.getProperty("os.arch") == "aarch64").also { - println("Run on apple silicon: $it") + logger.lifecycle("Run on apple silicon: $it") } val targets = when { @@ -71,37 +75,38 @@ object Library { isAppleSilicon -> setOf(IOSTarget.SimulatorArm64) else -> setOf(IOSTarget.X64) }.also { targets -> - println("Run on ios targets: ${targets.joinToString(" ") { it.name }}") + logger.lifecycle("Run on ios targets: ${targets.joinToString(" ") { it.name }}") } val TestRunnerDeviceId by lazy { if (System.getenv().containsKey("IOS_TEST_RUNNER_DEVICE_ID")) { System.getenv()["IOS_TEST_RUNNER_DEVICE_ID"].also { - println("System env IOS_TEST_RUNNER_DEVICE_ID set to ${System.getenv()["IOS_TEST_RUNNER_DEVICE_ID"]}, using $it") + logger.lifecycle("System env IOS_TEST_RUNNER_DEVICE_ID set to ${System.getenv()["IOS_TEST_RUNNER_DEVICE_ID"]}, using $it") }!! } else { // load some more from local.properties or set defaults. val iosTestRunnerDeviceIdLocalProperty: String? = props["kaluga.iosTestRunnerDeviceIdLocalProperty"] as? String iosTestRunnerDeviceIdLocalProperty?.also { - println("local.properties read (kaluga.iosTestRunnerDeviceIdLocalProperty=$iosTestRunnerDeviceIdLocalProperty, using $it)") + logger.lifecycle("local.properties read (kaluga.iosTestRunnerDeviceIdLocalProperty=$iosTestRunnerDeviceIdLocalProperty, using $it)") } ?: "iPhone 14".also { - println("local.properties not found, using default value ($it)") + logger.lifecycle("local.properties not found, using default value ($it)") } } } } + val IOS = IOSLibrary(props, logger) val exampleEmbeddingMethod by lazy { if (System.getenv().containsKey("EXAMPLE_EMBEDDING_METHOD")) { System.getenv()["EXAMPLE_EMBEDDING_METHOD"].also { - println("System env EXAMPLE_EMBEDDING_METHOD set to ${System.getenv()["EXAMPLE_EMBEDDING_METHOD"]}, using $it") + logger.lifecycle("System env EXAMPLE_EMBEDDING_METHOD set to ${System.getenv()["EXAMPLE_EMBEDDING_METHOD"]}, using $it") }!! } else { val exampleEmbeddingMethodLocalProperties = props["kaluga.exampleEmbeddingMethod"] as? String (exampleEmbeddingMethodLocalProperties ?: "composite").also { - println("local.properties read (kaluga.exampleEmbeddingMethod=$exampleEmbeddingMethodLocalProperties, using $it)") + logger.lifecycle("local.properties read (kaluga.exampleEmbeddingMethod=$exampleEmbeddingMethodLocalProperties, using $it)") } } } @@ -109,24 +114,24 @@ object Library { val exampleMavenRepo by lazy { if (System.getenv().containsKey("EXAMPLE_MAVEN_REPO")) { System.getenv()["EXAMPLE_MAVEN_REPO"].also { - println("System env EXAMPLE_MAVEN_REPO set to ${System.getenv()["EXAMPLE_MAVEN_REPO"]}, using $it") + logger.lifecycle("System env EXAMPLE_MAVEN_REPO set to ${System.getenv()["EXAMPLE_MAVEN_REPO"]}, using $it") }!! } else { // load some more from local.properties or set defaults. val exampleMavenRepoLocalProperties: String? = props["kaluga.exampleMavenRepo"] as? String exampleMavenRepoLocalProperties?.also { - println("local.properties read (kaluga.exampleMavenRepo=$exampleMavenRepoLocalProperties, using $it)") + logger.lifecycle("local.properties read (kaluga.exampleMavenRepo=$exampleMavenRepoLocalProperties, using $it)") } ?: "local".also { - println("local.properties not found, using default value ($it)") + logger.lifecycle("local.properties not found, using default value ($it)") } } } val connectCheckExpansion = (System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") or System.getenv().containsKey("CI")).also { if (it) { - println("Adding extra dependend task to connected checks of similarly named modules (CONNECTED_CHECK_EXPANSION env present: ${ System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") })") + logger.lifecycle("Adding extra dependend task to connected checks of similarly named modules (CONNECTED_CHECK_EXPANSION env present: ${ System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") })") } } } diff --git a/buildSrc/src/main/kotlin/Publish.kt b/buildSrc/src/main/kotlin/Publish.kt new file mode 100644 index 000000000..909e636a4 --- /dev/null +++ b/buildSrc/src/main/kotlin/Publish.kt @@ -0,0 +1,56 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import org.gradle.api.Project +import org.gradle.api.publish.maven.MavenPublication + +fun Project.publish(componentType: ComponentType = ComponentType.Default()) { + afterEvaluate { + publishing { + publications { + logger.lifecycle("This project module will be published as: $componentType") + when (componentType) { + is ComponentType.Compose -> { + create("release", MavenPublication::class.java) { + from(components.getByName("release")) + + artifactId = name + groupId = Library.group + version = Library.version + } + create("debug", MavenPublication::class.java) { + from(components.getByName("debug")) + + artifactId = name + groupId = Library.group + version = Library.version + } + } + is ComponentType.Default -> { + getByName("kotlinMultiplatform") { + (this as MavenPublication).let { + artifactId = name + groupId = Library.group + version = Library.version + } + } + } + } + } + } + } +} diff --git a/buildSrc/src/main/kotlin/PublishableComponent.kt b/buildSrc/src/main/kotlin/PublishableComponent.kt new file mode 100644 index 000000000..c713fd46c --- /dev/null +++ b/buildSrc/src/main/kotlin/PublishableComponent.kt @@ -0,0 +1,23 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import org.gradle.api.Project + +fun Project.publishableComponent() { + commonComponent() + publish() +} \ No newline at end of file diff --git a/calendar-permissions/build.gradle.kts b/calendar-permissions/build.gradle.kts index 7135eda09..cbf121810 100644 --- a/calendar-permissions/build.gradle.kts +++ b/calendar-permissions/build.gradle.kts @@ -6,18 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { diff --git a/camera-permissions/build.gradle.kts b/camera-permissions/build.gradle.kts index 7135eda09..e149787d0 100644 --- a/camera-permissions/build.gradle.kts +++ b/camera-permissions/build.gradle.kts @@ -6,24 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/contacts-permissions/build.gradle.kts b/contacts-permissions/build.gradle.kts index 7135eda09..e149787d0 100644 --- a/contacts-permissions/build.gradle.kts +++ b/contacts-permissions/build.gradle.kts @@ -6,24 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/date-time-picker/build.gradle.kts b/date-time-picker/build.gradle.kts index e6144a36b..b00b0eb02 100644 --- a/date-time-picker/build.gradle.kts +++ b/date-time-picker/build.gradle.kts @@ -6,26 +6,12 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { - - val ext = (gradle as ExtensionAware).extra - - implementation("androidx.fragment:fragment:${ext["androidx_fragment_version"]}") - debugImplementation("androidx.fragment:fragment-ktx:${ext["androidx_fragment_version"]}") -} +publishableComponent() kotlin { sourceSets { getByName("commonMain") { dependencies { - val ext = (gradle as ExtensionAware).extra implementation(project(":architecture", "")) implementation(project(":base", "")) } diff --git a/date-time/build.gradle.kts b/date-time/build.gradle.kts index 601c3c95c..d18e64f74 100644 --- a/date-time/build.gradle.kts +++ b/date-time/build.gradle.kts @@ -6,18 +6,12 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() kotlin { sourceSets { getByName("commonMain") { dependencies { - val ext = (gradle as ExtensionAware).extra implementation(project(":base", "")) } } diff --git a/gradle/android_common.gradle.kts b/gradle/android_common.gradle.kts deleted file mode 100644 index cd1769b81..000000000 --- a/gradle/android_common.gradle.kts +++ /dev/null @@ -1,128 +0,0 @@ -/* - Copyright 2020 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -repositories { - mavenCentral() -} - -android { - - val android_compile_sdk_version: Int by extra - val android_min_sdk_version: Int by extra - val android_target_sdk_version: Int by extra - val android_build_tools_version: String by extra - - compileSdk = android_compile_sdk_version - buildToolsVersion = android_build_tools_version - - defaultConfig { - minSdk = android_min_sdk_version - targetSdk = android_target_sdk_version - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled = false - } - } - - val component_type: String by extra - if (!component_type.toLowerCase().contains("app")) { - logger.lifecycle("Android sourcesets for this project module are configured as a library") - sourceSets { - main { - manifest.srcFile "src/androidLibMain/AndroidManifest.xml" - res.srcDir "src/androidLibMain/res" - if (component_type.contains("compose")) { - java.srcDir = "src/androidLibMain/kotlin" - } - } - androidTest { - manifest.srcFile = "src/androidLibAndroidTest/AndroidManifest.xml" - java.srcDir = "src/androidLibAndroidTest/kotlin" - res.srcDir = "src/androidLibAndroidTest/res" - } - - - test { - java.srcDir = "src/androidLibUnitTest/kotlin" - } - - } - } else { - logger.lifecycle("Android sourcesets for this project module are configured using defaults (for an app)") - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs += ['-XXLanguage:+InlineClasses', '-Xjvm-default=all'] - } - - if (component_type.contains("compose")) { - logger.lifecycle("This project module is a Compose only module") - buildFeatures { - compose = true - } - composeOptions { - val androidx_compose_compiler_version: String by extra - kotlinCompilerExtensionVersion = androidx_compose_compiler_version - } - } - -} - -dependencies { - val kotlinx_coroutines_version: String by extra - val androidx_appcompat_version: String by extra - - val junit_version: String by extra - val mockito_version: String by extra - val bytebuddy_version: String by extra - val androidx_test_version: String by extra - val androidx_test_uiautomator_version: String by extra - val androidx_test_junit_version: String by extra - val androidx_test_espresso_version: String by extra - - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version") - implementation("androidx.appcompat:appcompat:$androidx_appcompat_version") - - testImplementation("junit:junit:$junit_version") - testImplementation("org.mockito:mockito-core:$mockito_version") - testImplementation("org.jetbrains.kotlin:kotlin-test" - testImplementation("org.jetbrains.kotlin:kotlin-test-junit") - - androidTestImplementation("org.mockito:mockito-android:$mockito_version") - androidTestImplementation("net.bytebuddy:byte-buddy-android:$bytebuddy_version!!") - androidTestImplementation("net.bytebuddy:byte-buddy-agent:$bytebuddy_version!!") - - androidTestImplementation("androidx.test:core:$androidx_test_version") - androidTestImplementation("androidx.test:core-ktx:$androidx_test_version") - androidTestImplementation("androidx.test.uiautomator:uiautomator:$androidx_test_uiautomator_version") - androidTestImplementation("androidx.test:rules:$androidx_test_version") - androidTestImplementation("androidx.test.ext:junit:$androidx_test_junit_version") - androidTestImplementation("androidx.test:runner:$androidx_test_version") - androidTestImplementation("androidx.test.espresso:espresso-core:$androidx_test_espresso_version") - androidTestImplementation("org.jetbrains.kotlin:kotlin-test") - androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit") -} diff --git a/gradle/android_compose.gradle.kts b/gradle/android_compose.gradle.kts deleted file mode 100644 index dfbbc115e..000000000 --- a/gradle/android_compose.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright 2021 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -// if the include is made from the example project shared module we need to go up one more directory -val path_prefix = if (file("../gradle/android_common.gradle.kts").exists()) ".." else "../.." - -apply("$path_prefix/gradle/android_common.gradle.kts") - -dependencies { - val ext = (gradle as ExtensionAware).extra - val androidx_compose_version: String by ext - val androidx_lifecycle_viewmodel_compose_version: String by ext - val androidx_activity_compose_version: String by ext - - implementation("androidx.compose.foundation:foundation:$androidx_compose_version") - implementation("androidx.compose.ui:ui:$androidx_compose_version") - implementation("androidx.compose.ui:ui-tooling:$androidx_compose_version") - implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$androidx_lifecycle_viewmodel_compose_version") - implementation("androidx.activity:activity-compose:$androidx_activity_compose_version") - -} - -kotlin { - sourceSets.all { - languageSettings { - optIn("androidx.compose.material.ExperimentalMaterialApi") - } - } -} - -val ext = (gradle as ExtensionAware).extra -val component_type: String by ext -if (!component_type.toLowerCase().contains("app")) { - apply("$path_prefix/gradle/publish.gradle.kts") -} diff --git a/gradle/component.gradle.kts b/gradle/component.gradle.kts deleted file mode 100644 index a6dcf40b1..000000000 --- a/gradle/component.gradle.kts +++ /dev/null @@ -1,55 +0,0 @@ -repositories { - google() - mavenCentral() -} - -// apply("$path_prefix/gradle/android_common.gradle.kts") -// -// android { -// -// testOptions { -// unitTests.returnDefaultValues = true -// } -// -// packagingOptions { -// resources.excludes.add("META-INF/kotlinx-coroutines-core.kotlin_module") -// resources.excludes.add("META-INF/shared_debug.kotlin_module") -// resources.excludes.add("META-INF/kotlinx-serialization-runtime.kotlin_module") -// resources.excludes.add("META-INF/AL2.0") -// resources.excludes.add("META-INF/LGPL2.1") -// // bytebuddy 🤡 -// resources.excludes.add("win32-x86-64/attach_hotspot_windows.dll") -// resources.excludes.add("win32-x86/attach_hotspot_windows.dll") -// // -// resources.excludes.add("META-INF/licenses/ASM") -// } -// } -// -// task printConfigurations { -// doLast { -// configurations.each { println(it) } -// } -// } -// -// afterEvaluate { -// val ios_targets: List by extra -// ios_targets.each { target -> -// if (tasks.getNames().contains("linkDebugTest${target.capitalize()}")) -// // creating copy task for the target -// tasks.create(name = "copy${target.capitalize()}TestResources", type = Copy) { -// from(file('src/iosTest/resources/.')) -// into(file("$buildDir/bin/$target/debugTest")) -// } -// -// // apply copy task to the target -// tasks.named("linkDebugTest${target.capitalize()}").configure { -// dependsOn("copy${target.capitalize()}TestResources") -// } -// } -// } -// -// ktlint { -// disabledRules = listOf("no-wildcard-imports","filename","import-ordering") -// } - -// apply(from = "$path_prefix/gradle/componentskt.gradle.kts") diff --git a/gradle/componentskt.gradle.kts b/gradle/componentskt.gradle.kts deleted file mode 100644 index adf45d9b1..000000000 --- a/gradle/componentskt.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2020 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -// in preparation for a full port to kts, new functionality can go here if it's in kts - -import kotlin.text.* - -// if ((gradle as ExtensionAware).extra["connect_check_expansion"] == true) { -// -// val mymodules = project.parent?.subprojects?.filter { -// it.name.startsWith("${project.name}-") || it.name.endsWith("-${project.name}") -// } -// -// mymodules?.forEach() { module -> -// afterEvaluate { -// logger.info("[connect_check_expansion] :${project.name}:connectedDebugAndroidTest dependsOn:${module.name}:connectedDebugAndroidTest") -// tasks.getByPath("connectedDebugAndroidTest") -// .dependsOn(":${module.name}:connectedDebugAndroidTest") -// } -// } -// } diff --git a/gradle/ext.gradle.kts b/gradle/ext.gradle.kts deleted file mode 100644 index decfe47c5..000000000 --- a/gradle/ext.gradle.kts +++ /dev/null @@ -1,157 +0,0 @@ -// import java.util.Properties -// import java.io.* -// -// println("tasks: $gradle.startParameter.taskNames") -// -// // if the include is made from the example project shared module we need to go up a few directories -// // and depending on when we are included, we can be in the `gradle` folder or not. -// val path_prefix = when { -// file("componentskt.gradle.kts").exists() -> "" -// file("gradle/componentskt.gradle.kts").exists() -> "gradle/" -// else -> "../../../gradle/" -// } -// val f2 = file("${path_prefix}gitBranch.gradle.kts") -// -// apply(from = f2.path) -// val systemProperties = System.getProperties() -// -// val props = Properties() -// val propFile = file("../local.properties") -// if (propFile.exists()) { -// val inputStream = FileInputStream(propFile.absolutePath) -// props.load(inputStream) -// } -// -// val libraryVersionLocalProperties: String? = props["kaluga.libraryVersion"] as? String -// val exampleEmbeddingMethodLocalProperties: String? = props["kaluga.exampleEmbeddingMethod"] as? String -// val exampleMavenRepoLocalProperties: String? = props["kaluga.exampleMavenRepo"] as? String -// val kotlinVersion: String = extra["kaluga.kotlinVersion"] as String -// -// // set some global variables -// val ext = (gradle as ExtensionAware).extra -// ext.apply { -// set("kotlin_version", kotlinVersion) -// set("kotlinx_coroutines_version", "1.6.3-native-mt") -// set("stately_version", "1.2.3") -// set("stately_isolate_version", "1.2.3") -// set("koin_version", "3.2.2") -// set("serialization_version", "1.4.0") -// set("napier_version", "2.4.0") -// set("android_ble_scanner_version", "1.6.0") -// val library_version_base = "1.0.0" -// set("library_version_base", library_version_base) -// set("library_version", libraryVersionLocalProperties ?: "$library_version_base${systemProperties["kaluga_branch_postfix"]}") -// set("android_min_sdk_version", 21) -// set("android_compile_sdk_version", 33) -// set("android_target_sdk_version", 33) -// set("android_build_tools_version", "33.0.0") -// -// set("play_services_version", "20.0.0") -// set("play_core_version", "1.10.3") -// set("play_core_ktx_version", "1.8.1") -// -// set("androidx_appcompat_version", "1.5.1") -// set("androidx_fragment_version", "1.5.3") -// set("androidx_core_version", "1.9.0") -// set("androidx_lifecycle_version", "2.5.1") -// set("androidx_lifecycle_viewmodel_compose_version", "2.5.1") -// set("androidx_arch_core_testing_version", "2.1.0") -// set("androidx_browser_version", "1.4.0") -// set("androidx_constraint_layout_version", "2.1.4") -// -// set("androidx_compose_compiler_version", "1.3.2") -// set("androidx_compose_version", "1.2.1") -// set("androidx_activity_compose_version", "1.6.0") -// set("androidx_navigation_compose_version", "2.5.2") -// -// set("material_version", "1.6.1") -// set("material_components_adapter_version", "1.1.17") -// -// // sub packages of test have different versions, but alpha/beta/rc releases are harmonized -// set("junit_version", "4.13.2") -// -// val androidx_test_version_postfix = "" -// set("androidx_test_version_postfix", androidx_test_version_postfix) -// set("androidx_test_version", "1.4.0$androidx_test_version_postfix") -// set("androidx_test_espresso_version", "3.4.0$androidx_test_version_postfix") -// set("androidx_test_junit_version", "1.1.3$androidx_test_version_postfix") -// set("androidx_test_uiautomator_version", "2.2.0") -// -// // mockito and bytebuddy need to be upgraded in lockstep -// set("mockito_version", "3.11.2") -// set("bytebuddy_version", "1.11.3") -// -// // Javascript -// set("js_bigdecimal_version", "1.0.26") -// -// // used/modified at runtime. -// val component_type_default = "default" -// set("component_type_default", component_type_default) -// set("component_type_compose", "compose") -// set("component_type_app", "app") -// set("component_type_composeApp", "composeApp") -// set("component_type", component_type_default) -// } -// -// if (System.getenv().containsKey("EXAMPLE_EMBEDDING_METHOD")) { -// ext.set("example_embedding_method", System.getenv()["EXAMPLE_EMBEDDING_METHOD"]) -// logger.lifecycle("System env EXAMPLE_EMBEDDING_METHOD set to ${System.getenv()["EXAMPLE_EMBEDDING_METHOD"]}, using ${ext["example_embedding_method"]}") -// } else { -// ext.set("example_embedding_method", exampleEmbeddingMethodLocalProperties ?: "composite") -// logger.lifecycle("local.properties read (kaluga.exampleEmbeddingMethod=$exampleEmbeddingMethodLocalProperties, using ${ext["example_embedding_method"]})") -// } -// -// if (System.getenv().containsKey("EXAMPLE_MAVEN_REPO")) { -// ext.set("example_maven_repo", System.getenv()["EXAMPLE_MAVEN_REPO"]) -// logger.lifecycle("System env EXAMPLE_MAVEN_REPO set to ${System.getenv()["EXAMPLE_MAVEN_REPO"]}, using ${ext["example_maven_repo"]}") -// } else { -// // load some more from local.properties or set defaults. -// if (exampleMavenRepoLocalProperties != null) { -// ext.set("example_maven_repo", exampleMavenRepoLocalProperties) -// logger.lifecycle("local.properties read (kaluga.exampleMavenRepo=$exampleMavenRepoLocalProperties, using ${ext["example_maven_repo"]})") -// } else { -// ext.set("example_maven_repo", "local") -// logger.lifecycle("local.properties not found, using default value (${ext["example_maven_repo"]})") -// } -// } -// -// val CONNECTED_CHECK_EXPANSION = System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") -// ext.set("connect_check_expansion", CONNECTED_CHECK_EXPANSION or System.getenv().containsKey("CI")) -// -// val connect_check_expansion: Boolean by ext -// if (connect_check_expansion) -// logger.lifecycle("Adding extra dependend task to connected checks of similarly named modules (CONNECTED_CHECK_EXPANSION env present: $CONNECTED_CHECK_EXPANSION)") -// -// // based on https://github.com/Kotlin/xcode-compat/blob/d677a43edc46c50888bca0a7890a81f976a42809/xcode-compat/src/main/kotlin/org/jetbrains/kotlin/xcodecompat/XcodeCompatPlugin.kt#L16 -// val sdkName = System.getenv("SDK_NAME") ?: "unknown" -// ext.set("is_real_ios_device", sdkName.startsWith("iphoneos")) -// logger.lifecycle("Run on real ios device: ${ext["is_real_ios_device"]} from sdk: $sdkName") -// -// // Run on IntelliJ -// val ideaActive = System.getProperty("idea.active") == "true" -// ext.set("idea_active", ideaActive) -// logger.lifecycle("Run on IntelliJ: ${ext["idea_active"]}") -// -// // Run on apple silicon -// val isAppleSilicon = System.getProperty("os.arch") == "aarch64" -// ext.set("is_apple_silicon", isAppleSilicon) -// logger.lifecycle("Run on apple silicon: ${ext["is_apple_silicon"]}") -// -// val iosX64 = "iosX64" -// val iosArm64 = "iosArm64" -// val iosSimulatorArm64 = "iosSimulatorArm64" -// val allIosTargets = listOf(iosX64, iosArm64, iosSimulatorArm64) -// val idea_active: Boolean by ext -// val is_real_ios_device: Boolean by ext -// val is_apple_silicon: Boolean by ext -// -// ext.set( -// "ios_targets", -// when { -// !idea_active -> allIosTargets -// is_real_ios_device -> listOf(iosArm64) -// is_apple_silicon -> listOf(iosSimulatorArm64) -// else -> listOf(iosX64) -// } -// ) -// logger.lifecycle("Run on ios targets: ${ext["ios_targets"]}") diff --git a/gradle/gitBranch.gradle.kts b/gradle/gitBranch.gradle.kts deleted file mode 100644 index 0f3e7e864..000000000 --- a/gradle/gitBranch.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2021 Splendo Consulting B.V. The Netherlands - - 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. - - */ -// import org.codehaus.groovy.runtime.ProcessGroovyMethods -// -// val BITRISE_GIT_BRANCH = System.getenv("BITRISE_GIT_BRANCH") -// val kaluga_branch = System.getProperty("kaluga_branch") -// val MAVEN_CENTRAL_RELEASE = System.getenv("MAVEN_CENTRAL_RELEASE") -// val release = MAVEN_CENTRAL_RELEASE?.toLowerCase()?.trim() == "true" -// val branchFromGit = run { -// try { -// ProcessGroovyMethods.getText(ProcessGroovyMethods.execute("git rev-parse --abbrev-ref HEAD")) -// } catch (e: Exception) { -// logger.info("Unable to determine current branch through git CLI: ${e.message}") -// "unknown" -// } -// } -// -// // favour user definition of kaluga_branch (if present), otherwise take it from GIT branch: -// // - if running on CI: favour bitrise's branch detection -// // - else: try to get it via the `git` CLI. -// val branch = (kaluga_branch ?: BITRISE_GIT_BRANCH ?: branchFromGit ).replace('/', '-').trim().toLowerCase().also { -// if (it == "HEAD") -// logger.warn("Unable to determine current branch: Project is checked out with detached head!") -// } -// -// val kaluga_branch_postfix = when(branch) { -// "master", "main", "develop" -> "" -// else -> "-"+branch -// } + if (!release) "-SNAPSHOT" else "" -// -// logger.lifecycle("decided branch: '$branch' to postfix '$kaluga_branch_postfix', isRelease: $release (from: BITRISE_GIT_BRANCH env: $BITRISE_GIT_BRANCH, kaluga_branch property: $kaluga_branch , MAVEN_CENTRAL_RELEASE env: $MAVEN_CENTRAL_RELEASE , git cli: $branchFromGit)") -// -// System.setProperty("kaluga_branch_postfix", kaluga_branch_postfix) diff --git a/gradle/publish.gradle.kts b/gradle/publish.gradle.kts deleted file mode 100644 index 5e688d7ad..000000000 --- a/gradle/publish.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -afterEvaluate { - // publishing { - // publications { - // val component_type: String by extra - // val library_version: String by extra - // logger.lifecycle("This project module will be published as: $component_type") - // if (type.contains("compose")) { - // release(MavenPublication) { - // from(components.release) - // - // artifactId = project.name - // groupId = "com.splendo.kaluga" - // version = library_version - // } - // // Creates a Maven publication called “debug”. - // debug(MavenPublication) { - // from(components.debug) - // - // artifactId = project.name - // groupId = "com.splendo.kaluga" - // version = library_version - // } - // } else { - // kotlinMultiplatform { publication -> - // artifactId = project.name - // groupId = "com.splendo.kaluga" - // version = library_version - // } - // } - // } - // } -} diff --git a/gradle/publishable_component.gradle.kts b/gradle/publishable_component.gradle.kts deleted file mode 100644 index 130f6e847..000000000 --- a/gradle/publishable_component.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -apply(from = "../gradle/component.gradle.kts") -apply(from = "../gradle/jacoco.gradle") -apply(from = "../gradle/publish.gradle.kts") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee..1855ed1e9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Mon Oct 10 09:58:47 CEST 2022 distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip -zipStoreBase=GRADLE_USER_HOME +distributionPath=wrapper/dists zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/hud/build.gradle.kts b/hud/build.gradle.kts index 6c6b33fbf..bc017b374 100644 --- a/hud/build.gradle.kts +++ b/hud/build.gradle.kts @@ -6,25 +6,17 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() dependencies { - - val ext = (gradle as ExtensionAware).extra - implementation("androidx.fragment:fragment:${ext["androidx_fragment_version"]}") - debugImplementation("androidx.fragment:fragment-ktx:${ext["androidx_fragment_version"]}") + implement(Dependencies.AndroidX.Fragment) + implementForDebug(Dependencies.AndroidX.FragmentKtx) } kotlin { sourceSets { getByName("commonMain") { dependencies { - val ext = (gradle as ExtensionAware).extra implementation(project(":architecture", "")) implementation(project(":base", "")) } diff --git a/keyboard-compose/build.gradle.kts b/keyboard-compose/build.gradle.kts index 7b626d1cc..31e426241 100644 --- a/keyboard-compose/build.gradle.kts +++ b/keyboard-compose/build.gradle.kts @@ -6,25 +6,12 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra -ext["component_type"] = ext["component_type_compose"] - -// if the include is made from the example project shared module we need to go up one more directory -val path_prefix = if (file("../gradle/componentskt.gradle.kts").exists()) - ".." else "../.." - -apply(from = "$path_prefix/gradle/android_compose.gradle") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -ext["component_type"] = ext["component_type_default"] +composeAndroidComponent() dependencies { implementation(project(":base")) api(project(":keyboard")) - val ext = (gradle as ExtensionAware).extra - implementation("androidx.compose.ui:ui:" + ext["androidx_compose_version"]) - implementation("androidx.compose.ui:ui-tooling:" + ext["androidx_compose_version"]) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${ext["kotlinx_coroutines_version"]}!!") + implement(Dependencies.AndroidX.Compose.UI) + implement(Dependencies.AndroidX.Compose.UITooling) + implement(Dependencies.KotlinX.Coroutines.Core) } diff --git a/keyboard/build.gradle.kts b/keyboard/build.gradle.kts index 60b3092ab..a4b76534b 100644 --- a/keyboard/build.gradle.kts +++ b/keyboard/build.gradle.kts @@ -6,12 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() kotlin { sourceSets { diff --git a/links/build.gradle.kts b/links/build.gradle.kts index 11d8e6fc1..047717774 100644 --- a/links/build.gradle.kts +++ b/links/build.gradle.kts @@ -24,22 +24,16 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() kotlin { sourceSets { commonMain { dependencies { - val ext = (gradle as ExtensionAware).extra implementation(project(":base", "")) implementation(project(":logging", "")) implementation(project(":architecture", "")) - api("org.jetbrains.kotlinx:kotlinx-serialization-core:${ext["serialization_version"]}") + expose(Dependencies.KotlinX.Serialization.Core) } } commonTest { diff --git a/location-permissions/build.gradle.kts b/location-permissions/build.gradle.kts index 7135eda09..83a274afd 100644 --- a/location-permissions/build.gradle.kts +++ b/location-permissions/build.gradle.kts @@ -6,24 +6,15 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! +publishableComponent() dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") + implement(Dependencies.Android.PlayServices.Location) } kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/location/build.gradle.kts b/location/build.gradle.kts index 9710fc232..cd8654057 100644 --- a/location/build.gradle.kts +++ b/location/build.gradle.kts @@ -6,17 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9") + implement(Dependencies.Android.PlayServices.Location) + implement(Dependencies.KotlinX.Coroutines.PlayServices) } kotlin { diff --git a/logging/build.gradle.kts b/logging/build.gradle.kts index 8685d502a..b06cc6050 100644 --- a/logging/build.gradle.kts +++ b/logging/build.gradle.kts @@ -6,28 +6,20 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() kotlin { sourceSets { commonMain { - val ext = (gradle as ExtensionAware).extra dependencies { - implementation("io.github.aakira:napier:${ext["napier_version"]}") - implementation("co.touchlab:stately-concurrency:${ext["stately_version"]}") + implement(Dependencies.Napier) + implement(Dependencies.Stately.Concurrency) } } commonTest { dependencies { - val ext = (gradle as ExtensionAware).extra - // Stately Isolite is in flux and not part of the current statelyVersion. Upgrade this when tracked properly - implementation("co.touchlab:stately-isolate:${ext["stately_isolate_version"]}") - implementation("co.touchlab:stately-iso-collections:${ext["stately_isolate_version"]}") + implement(Dependencies.Stately.Isolate) + implement(Dependencies.Stately.IsoCollections) api(project(":test-utils-base", "")) } } diff --git a/microphone-permissions/build.gradle.kts b/microphone-permissions/build.gradle.kts index 7135eda09..e149787d0 100644 --- a/microphone-permissions/build.gradle.kts +++ b/microphone-permissions/build.gradle.kts @@ -6,24 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/notifications-permissions/build.gradle.kts b/notifications-permissions/build.gradle.kts index 7135eda09..e149787d0 100644 --- a/notifications-permissions/build.gradle.kts +++ b/notifications-permissions/build.gradle.kts @@ -6,24 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/permissions/build.gradle.kts b/permissions/build.gradle.kts index d8be8917b..286ea1998 100644 --- a/permissions/build.gradle.kts +++ b/permissions/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/resources-compose/build.gradle.kts b/resources-compose/build.gradle.kts index 507a00414..ecc878677 100644 --- a/resources-compose/build.gradle.kts +++ b/resources-compose/build.gradle.kts @@ -23,27 +23,14 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra -ext["component_type"] = ext["component_type_compose"] - -// if the include is made from the example project shared module we need to go up one more directory -val path_prefix = if (file("../gradle/componentskt.gradle.kts").exists()) - ".." else "../.." - -apply(from = "$path_prefix/gradle/android_compose.gradle") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -ext["component_type"] = ext["component_type_default"] +composeAndroidComponent() dependencies { implementation(project(":base")) api(project(":resources")) - val ext = (gradle as ExtensionAware).extra - implementation("androidx.compose.foundation:foundation:" + ext["androidx_compose_version"]) - implementation("androidx.compose.material:material:" + ext["androidx_compose_version"]) - implementation("androidx.compose.ui:ui:" + ext["androidx_compose_version"]) - implementation("androidx.compose.ui:ui-tooling:" + ext["androidx_compose_version"]) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${ext["kotlinx_coroutines_version"]}!!") + implement(Dependencies.AndroidX.Compose.Foundation) + implement(Dependencies.AndroidX.Compose.Material) + implement(Dependencies.AndroidX.Compose.UI) + implement(Dependencies.AndroidX.Compose.UITooling) + implement(Dependencies.KotlinX.Coroutines.Core) } diff --git a/resources/build.gradle.kts b/resources/build.gradle.kts index 5e705248b..309e4eb81 100644 --- a/resources/build.gradle.kts +++ b/resources/build.gradle.kts @@ -7,26 +7,16 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies {} +publishableComponent() kotlin { sourceSets { - val ext = (gradle as ExtensionAware).extra - val serialization_version: String by ext - getByName("commonMain") { dependencies { implementation(project(":base", "")) implementation(project(":logging", "")) - api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version") + expose(Dependencies.KotlinX.Serialization.Core) + expose(Dependencies.KotlinX.Serialization.Json) } } diff --git a/review/build.gradle.kts b/review/build.gradle.kts index 69842b7c7..5c3b98ab1 100644 --- a/review/build.gradle.kts +++ b/review/build.gradle.kts @@ -6,20 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! +publishableComponent() dependencies { - val ext = (gradle as ExtensionAware).extra - val play_core_version: String by ext - val play_core_ktx_version: String by ext - - implementation("com.google.android.play:core:$play_core_version") - implementation("com.google.android.play:core-ktx:$play_core_ktx_version") + implement(Dependencies.Android.Play.Core) + implement(Dependencies.Android.Play.CoreKtx) } kotlin { diff --git a/scientific/build.gradle.kts b/scientific/build.gradle.kts index 68e1d7415..2cd1a9c5c 100644 --- a/scientific/build.gradle.kts +++ b/scientific/build.gradle.kts @@ -7,26 +7,15 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { - - val ext = (gradle as ExtensionAware).extra - val serialization_version: String by ext - commonMain { dependencies { implementation(project(":base")) - api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version") + expose(Dependencies.KotlinX.Serialization.Core) + expose(Dependencies.KotlinX.Serialization.Json) } } commonTest { diff --git a/settings.gradle.kts b/settings.gradle.kts index 040d0154b..ffe9c4d52 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,7 +44,6 @@ pluginManagement { } } -apply("gradle/ext.gradle.kts") includeBuild("convention-plugins") rootProject.name = "Kaluga" diff --git a/storage-permissions/build.gradle.kts b/storage-permissions/build.gradle.kts index 7135eda09..e149787d0 100644 --- a/storage-permissions/build.gradle.kts +++ b/storage-permissions/build.gradle.kts @@ -6,24 +6,11 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -val kotlinx_coroutines_version = ext["kotlinx_coroutines_version"]!! - -dependencies { - val play_services_version = (gradle as ExtensionAware).extra["play_services_version"] - implementation("com.google.android.gms:play-services-location:$play_services_version") -} +publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/system/build.gradle.kts b/system/build.gradle.kts index 1d02e0dda..5c23021ea 100644 --- a/system/build.gradle.kts +++ b/system/build.gradle.kts @@ -6,20 +6,9 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! -dependencies { - implementation(project(mapOf("path" to ":base"))) -} +publishableComponent() kotlin { - - val ext = (gradle as ExtensionAware).extra - sourceSets { commonMain { dependencies { diff --git a/test-utils-alerts/build.gradle.kts b/test-utils-alerts/build.gradle.kts index 758be76a9..d7be158f8 100644 --- a/test-utils-alerts/build.gradle.kts +++ b/test-utils-alerts/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils-architecture/build.gradle.kts b/test-utils-architecture/build.gradle.kts index 869b0f540..ea94dc000 100644 --- a/test-utils-architecture/build.gradle.kts +++ b/test-utils-architecture/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils-base/build.gradle.kts b/test-utils-base/build.gradle.kts index bea107930..4e93e3911 100644 --- a/test-utils-base/build.gradle.kts +++ b/test-utils-base/build.gradle.kts @@ -6,17 +6,10 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -val androidx_arch_core_testing_version = ext["androidx_arch_core_testing_version"]!! +publishableComponent() dependencies { - api("androidx.arch.core:core-testing:$androidx_arch_core_testing_version") + expose(Dependencies.AndroidX.ArchCore) } kotlin { @@ -44,11 +37,9 @@ kotlin { } getByName("jvmMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-test:" + ext["kotlinx_coroutines_version"]) - api("org.jetbrains.kotlinx:kotlinx-coroutines-debug:" + ext["kotlinx_coroutines_version"]) + expose(Dependencies.KotlinX.Coroutines.Test) + expose(Dependencies.KotlinX.Coroutines.Debug) } } } @@ -56,8 +47,7 @@ kotlin { android { dependencies { - val ext = (gradle as ExtensionAware).extra - api("org.jetbrains.kotlinx:kotlinx-coroutines-test:" + ext["kotlinx_coroutines_version"]) - api("org.jetbrains.kotlinx:kotlinx-coroutines-debug:" + ext["kotlinx_coroutines_version"]) + expose(Dependencies.KotlinX.Coroutines.Test) + expose(Dependencies.KotlinX.Coroutines.Debug) } } diff --git a/test-utils-bluetooth/build.gradle.kts b/test-utils-bluetooth/build.gradle.kts index 514bee8f4..1957a9301 100644 --- a/test-utils-bluetooth/build.gradle.kts +++ b/test-utils-bluetooth/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils-hud/build.gradle.kts b/test-utils-hud/build.gradle.kts index 15a0d34a8..869b33505 100644 --- a/test-utils-hud/build.gradle.kts +++ b/test-utils-hud/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils-keyboard/build.gradle.kts b/test-utils-keyboard/build.gradle.kts index 2f6d6ce4d..47a59bf69 100644 --- a/test-utils-keyboard/build.gradle.kts +++ b/test-utils-keyboard/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils-koin/build.gradle.kts b/test-utils-koin/build.gradle.kts index 226c3a650..90c147628 100644 --- a/test-utils-koin/build.gradle.kts +++ b/test-utils-koin/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { @@ -22,7 +15,7 @@ kotlin { dependencies { api(project(":test-utils-base")) api(project(":test-utils-architecture")) - api("io.insert-koin:koin-core:" + ext["koin_version"]) + expose(Dependencies.Koin.Core) } } commonTest { diff --git a/test-utils-location/build.gradle.kts b/test-utils-location/build.gradle.kts index 3e32bc3fb..bdca0d829 100644 --- a/test-utils-location/build.gradle.kts +++ b/test-utils-location/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils-permissions/build.gradle.kts b/test-utils-permissions/build.gradle.kts index 2f087c8af..245297644 100644 --- a/test-utils-permissions/build.gradle.kts +++ b/test-utils-permissions/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils-resources/build.gradle.kts b/test-utils-resources/build.gradle.kts index c31e0e675..210f27579 100644 --- a/test-utils-resources/build.gradle.kts +++ b/test-utils-resources/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index fe43c23dc..c2ec54e44 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -6,14 +6,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra - -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { } +publishableComponent() kotlin { sourceSets { From d0defdfbc3250b1e5fd521271e9dc0fa6527eb54 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 4 Nov 2022 10:50:29 +0100 Subject: [PATCH 004/227] Fixes to get compilation working --- adding-a-new-module/template/build.gradle.kts | 17 +++++------------ .../iosTest/kotlin/observable/ThreadingTest.kt | 2 -- build.gradle.kts | 8 ++++---- buildSrc/src/main/kotlin/Component.kt | 4 ++-- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/adding-a-new-module/template/build.gradle.kts b/adding-a-new-module/template/build.gradle.kts index 151b61933..a9a3c2b9b 100644 --- a/adding-a-new-module/template/build.gradle.kts +++ b/adding-a-new-module/template/build.gradle.kts @@ -6,19 +6,12 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra +/* Multiplatform component */ +publishableComponent() +/* Compose component */ +composeAndroidComponent() -apply(from = "../gradle/publishable_component.gradle.kts") - -group = "com.splendo.kaluga" -version = ext["library_version"]!! - -dependencies { - /* Uncomment these lines if you are using fragments - val ext = (gradle as ExtensionAware).extra - androidTestImplementation("androidx.fragment:fragment-ktx:${ext["androidx_fragment_version"]}") - */ -} +dependencies { } kotlin { sourceSets { diff --git a/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt b/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt index 1d4ad918e..b6c4b6212 100644 --- a/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt +++ b/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt @@ -85,7 +85,6 @@ class ThreadingTest { val observer: (Value?) -> Unit = { } val disposable = s.observe(observer) assertNotFrozen(value) - assertNotFrozen(observer) withContext(Dispatchers.Default) { // due to the context switch the subject itself is frozen @@ -98,7 +97,6 @@ class ThreadingTest { disposable2.dispose() } - assertNotFrozen(observer) disposable.dispose() } } diff --git a/build.gradle.kts b/build.gradle.kts index 471734431..04ad7a695 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,8 +44,8 @@ allprojects { testLogging.exceptionFormat = TestExceptionFormat.FULL } - tasks.withType().configureEach { - // it.workerMaxHeapSize.set("512m") + tasks.withType().configureEach { + workerMaxHeapSize.set("512m") } } @@ -54,7 +54,7 @@ apiValidation { subprojects.forEach { val name = it.name - ignoredClasses.add("com.splendo.kaluga.${name}.BuildConfig".toString()) + ignoredClasses.add("com.splendo.kaluga.$name.BuildConfig".toString()) ignoredClasses.add("com.splendo.kaluga.${name.replace("-", ".")}.BuildConfig".toString()) ignoredClasses.add("com.splendo.kaluga.${name.replace("-", "")}.BuildConfig".toString()) ignoredClasses.add("com.splendo.kaluga.permissions.${name.replace("-permissions", "")}.BuildConfig".toString()) @@ -68,5 +68,5 @@ apiValidation { apply(from = "gradle/newModule.gradle.kts") apply(from = "gradle/copyReports.gradle.kts") -group = "com.splendo.kaluga" +group = Library.group version = Library.version diff --git a/buildSrc/src/main/kotlin/Component.kt b/buildSrc/src/main/kotlin/Component.kt index e2c5c0813..f0899920e 100644 --- a/buildSrc/src/main/kotlin/Component.kt +++ b/buildSrc/src/main/kotlin/Component.kt @@ -56,8 +56,8 @@ fun Project.commonComponent() { if (tasks.names.contains("linkDebugTest${targetName.capitalize() }")) { // creating copy task for the target val copyTask = tasks.create("copy${targetName.capitalize() }TestResources", Copy::class.java) { - from(File("src/iosTest/resources/.")) - into(File("$buildDir/bin/$targetName/debugTest")) + from("src/iosTest/resources/.") + into("$buildDir/bin/$targetName/debugTest") } // apply copy task to the target diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index c3c6c3dd6..506a7dd18 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -56,7 +56,7 @@ object Dependencies { val Android = Dependency(group, "kotlinx-coroutines-android", version) val Core = Dependency(group, "kotlinx-coroutines-core", version) val Swing = Dependency(group, "kotlinx-coroutines-swing", version) - val Js = Dependency(group, "kotlinx-coroutines-js", version) + val Js = Dependency(group, "kotlinx-coroutines-core-js", version) val PlayServices = Dependency(group, "kotlinx-coroutines-play-services", version) val Test = Dependency(group, "kotlinx-coroutines-test", version) val Debug = Dependency(group, "kotlinx-coroutines-debug", version) From 7790d7d1c07315026c92758186a74e208129f5e3 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 10 Oct 2022 15:15:36 +0200 Subject: [PATCH 005/227] Move to composite builds --- .github/workflows/emulator.yaml | 2 +- build.gradle.kts | 1 + .../build.gradle.kts | 7 ++++++ .../settings/gradle.kts | 0 .../src/main/kotlin/Accessors.kt | 0 .../src/main/kotlin/AndroidCommon.kt | 0 .../src/main/kotlin/AndroidCompose.kt | 0 .../src/main/kotlin/Component.kt | 0 .../src/main/kotlin/Dependencies.kt | 8 +++--- .../src/main/kotlin/GitBranch.kt | 0 .../src/main/kotlin/Library.kt | 0 .../main/kotlin/LibraryComponentsPlugin.kt | 25 +++++++++++++++++++ .../src/main/kotlin/Publish.kt | 0 .../src/main/kotlin/PublishableComponent.kt | 0 settings.gradle.kts | 1 + 15 files changed, 39 insertions(+), 5 deletions(-) rename {buildSrc => kaluga-library-components}/build.gradle.kts (86%) rename {buildSrc => kaluga-library-components}/settings/gradle.kts (100%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/Accessors.kt (100%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/AndroidCommon.kt (100%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/AndroidCompose.kt (100%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/Component.kt (100%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/Dependencies.kt (97%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/GitBranch.kt (100%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/Library.kt (100%) create mode 100644 kaluga-library-components/src/main/kotlin/LibraryComponentsPlugin.kt rename {buildSrc => kaluga-library-components}/src/main/kotlin/Publish.kt (100%) rename {buildSrc => kaluga-library-components}/src/main/kotlin/PublishableComponent.kt (100%) diff --git a/.github/workflows/emulator.yaml b/.github/workflows/emulator.yaml index bf2c94cb4..66f5a8305 100644 --- a/.github/workflows/emulator.yaml +++ b/.github/workflows/emulator.yaml @@ -1,4 +1,4 @@ -#### for keeping up to date with settings.build.gradle.kts see instructions in `test:` job +#### for keeping up to date with settings.gradle.kts see instructions in `test:` job name: Android Emulator tests on: pull_request env: diff --git a/build.gradle.kts b/build.gradle.kts index 04ad7a695..8c7ea7583 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,7 @@ buildscript { } plugins { + id("kaluga-library-components") id("convention.publication") id("org.jlleitschuh.gradle.ktlint") id("org.jlleitschuh.gradle.ktlint-idea") diff --git a/buildSrc/build.gradle.kts b/kaluga-library-components/build.gradle.kts similarity index 86% rename from buildSrc/build.gradle.kts rename to kaluga-library-components/build.gradle.kts index d22264d20..2f2e91698 100644 --- a/buildSrc/build.gradle.kts +++ b/kaluga-library-components/build.gradle.kts @@ -14,6 +14,13 @@ repositories { mavenCentral() } +gradlePlugin { + plugins.register("kaluga-library-components") { + id = "kaluga-library-components" + implementationClass = "LibraryComponentsPlugin" + } +} + dependencies { val properties = File("${rootDir.absolutePath}/../gradle.properties").loadProperties() diff --git a/buildSrc/settings/gradle.kts b/kaluga-library-components/settings/gradle.kts similarity index 100% rename from buildSrc/settings/gradle.kts rename to kaluga-library-components/settings/gradle.kts diff --git a/buildSrc/src/main/kotlin/Accessors.kt b/kaluga-library-components/src/main/kotlin/Accessors.kt similarity index 100% rename from buildSrc/src/main/kotlin/Accessors.kt rename to kaluga-library-components/src/main/kotlin/Accessors.kt diff --git a/buildSrc/src/main/kotlin/AndroidCommon.kt b/kaluga-library-components/src/main/kotlin/AndroidCommon.kt similarity index 100% rename from buildSrc/src/main/kotlin/AndroidCommon.kt rename to kaluga-library-components/src/main/kotlin/AndroidCommon.kt diff --git a/buildSrc/src/main/kotlin/AndroidCompose.kt b/kaluga-library-components/src/main/kotlin/AndroidCompose.kt similarity index 100% rename from buildSrc/src/main/kotlin/AndroidCompose.kt rename to kaluga-library-components/src/main/kotlin/AndroidCompose.kt diff --git a/buildSrc/src/main/kotlin/Component.kt b/kaluga-library-components/src/main/kotlin/Component.kt similarity index 100% rename from buildSrc/src/main/kotlin/Component.kt rename to kaluga-library-components/src/main/kotlin/Component.kt diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/kaluga-library-components/src/main/kotlin/Dependencies.kt similarity index 97% rename from buildSrc/src/main/kotlin/Dependencies.kt rename to kaluga-library-components/src/main/kotlin/Dependencies.kt index 506a7dd18..9548006d6 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/kaluga-library-components/src/main/kotlin/Dependencies.kt @@ -141,7 +141,7 @@ object Dependencies { val Core = Dependency(group, "core", version) val CoreKtx = Dependency(group, "core-ktx", version) - val Espresso = Dependency("$group.espresso", "espresso-core", version) + val Espresso = Dependency("$group.espresso", "espresso-core", espressoVersion) val JUnit = Dependency("$group.ext", "junit", junitVersion) val Rules = Dependency(group, "rules", version) val Runner = Dependency(group, "runner", version) @@ -158,7 +158,7 @@ object Dependencies { val Core = Dependency(group, "koin-core", version) } - val Napier = Dependency("io.github.aakira", "napier", "2.4.0") + val Napier = Dependency("io.github.aakira", "napier", "2.6.1") object Stately { private const val group = "co.touchlab" @@ -174,7 +174,7 @@ object Dependencies { object Mockito { private const val group = "org.mockito" - private const val version = "3.11.2" + private const val version = "4.8.1" val Android = Dependency(group, "mockito-android", version) val Core = Dependency(group, "mockito-core", version) @@ -182,7 +182,7 @@ object Dependencies { object ByteBuddy { private const val group = "net.bytebuddy" - private const val version = "1.11.3" + private const val version = "1.12.16" val Android = Dependency(group, "byte-buddy-android", version) val Agent = Dependency(group, "byte-buddy-agent", version) diff --git a/buildSrc/src/main/kotlin/GitBranch.kt b/kaluga-library-components/src/main/kotlin/GitBranch.kt similarity index 100% rename from buildSrc/src/main/kotlin/GitBranch.kt rename to kaluga-library-components/src/main/kotlin/GitBranch.kt diff --git a/buildSrc/src/main/kotlin/Library.kt b/kaluga-library-components/src/main/kotlin/Library.kt similarity index 100% rename from buildSrc/src/main/kotlin/Library.kt rename to kaluga-library-components/src/main/kotlin/Library.kt diff --git a/kaluga-library-components/src/main/kotlin/LibraryComponentsPlugin.kt b/kaluga-library-components/src/main/kotlin/LibraryComponentsPlugin.kt new file mode 100644 index 000000000..dc23d9d2f --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/LibraryComponentsPlugin.kt @@ -0,0 +1,25 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class LibraryComponentsPlugin: Plugin { + override fun apply(target: Project) { + // no-op + } +} diff --git a/buildSrc/src/main/kotlin/Publish.kt b/kaluga-library-components/src/main/kotlin/Publish.kt similarity index 100% rename from buildSrc/src/main/kotlin/Publish.kt rename to kaluga-library-components/src/main/kotlin/Publish.kt diff --git a/buildSrc/src/main/kotlin/PublishableComponent.kt b/kaluga-library-components/src/main/kotlin/PublishableComponent.kt similarity index 100% rename from buildSrc/src/main/kotlin/PublishableComponent.kt rename to kaluga-library-components/src/main/kotlin/PublishableComponent.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index ffe9c4d52..cf929ef1d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,6 +44,7 @@ pluginManagement { } } +includeBuild("kaluga-library-components") includeBuild("convention-plugins") rootProject.name = "Kaluga" From 07da66b3ba949488b8f20d9894e17c4751f92a1c Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 4 Nov 2022 11:00:28 +0100 Subject: [PATCH 006/227] Moving to new memory model --- alerts/src/commonMain/kotlin/Alerts.kt | 12 +-- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../kotlin/observable/Disposable.kt | 67 +++++++------- .../kotlin/observable/Observable.kt | 24 ++--- .../kotlin/viewmodel/LifecycleViewModel.kt | 10 +-- .../kotlin/observable/ObservableBaseTest.kt | 26 +++--- .../kotlin/observable/ReadOnlyPropertyTest.kt | 7 +- .../kotlin/observable/SimpleDisposable.kt | 3 - .../kotlin/viewmodel/LifecycleViewModel.kt | 8 +- .../viewmodel/ViewModelLifecycleManager.kt | 10 ++- .../kotlin/observable/ThreadingTest.kt | 5 -- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../src/commonMain/kotlin/Permission.kt | 87 ++++++++++++++----- .../iosMain/kotlin/IOSPermissionsHelper.kt | 8 +- .../kotlin/PermissionRefreshScheduler.kt | 34 +++----- .../iosMain/kotlin/av/AVPermissionHelper.kt | 11 +-- .../kotlin/PermissionRefreshSchedulerTest.kt | 17 ++-- base/build.gradle.kts | 4 - .../kotlin/AtomicReferenceDelegate.kt | 37 -------- .../commonMain/kotlin/flow/BaseFlowable.kt | 13 ++- .../commonMain/kotlin/flow/ColdFlowable.kt | 11 ++- .../commonMain/kotlin/state/KalugaState.kt | 42 +++++---- base/src/commonMain/kotlin/utils/TimeZone.kt | 2 - .../commonTest/kotlin/AtomicReferenceTest.kt | 1 - base/src/iosMain/kotlin/GCScheduler.kt | 12 ++- beacons/src/commonMain/kotlin/Beacons.kt | 6 +- .../src/commonMain/kotlin/Permission.kt | 28 +++++- bluetooth/build.gradle.kts | 1 - bluetooth/src/commonMain/kotlin/Bluetooth.kt | 5 +- .../src/commonMain/kotlin/Characteristic.kt | 6 +- .../device/DefaultDeviceConnectionManager.kt | 16 ++-- .../src/commonMain/kotlin/scanner/Scanner.kt | 12 +-- .../src/iosMain/kotlin/BluetoothBuilder.kt | 2 +- .../src/iosMain/kotlin/BluetoothMonitor.kt | 10 +-- .../device/DefaultDeviceConnectionManager.kt | 70 +++++++++------ .../src/iosMain/kotlin/scanner/Scanner.kt | 48 +++++----- .../src/commonMain/kotlin/Permission.kt | 23 ++++- .../kotlin/CalendarPermissionManager.kt | 11 +-- .../src/commonMain/kotlin/Permission.kt | 23 ++++- .../src/commonMain/kotlin/Permission.kt | 23 ++++- .../kotlin/ContactsPermissionManager.kt | 11 +-- .../src/commonMain/kotlin/DateTimePicker.kt | 10 +-- .../kotlin/bluetooth/KNBluetoothFramework.kt | 1 - gradle.properties | 2 - hud/src/commonMain/kotlin/HUD.kt | 8 +- hud/src/iosTest/kotlin/IOSHUDTests.kt | 9 -- .../src/main/kotlin/Dependencies.kt | 12 +-- .../src/commonMain/kotlin/Permission.kt | 23 ++++- .../kotlin/MainCLLocationManagerAccessor.kt | 8 +- .../kotlin/location/LocationManager.kt | 19 ++-- .../kotlin/location/LocationManager.kt | 11 +-- .../kotlin/location/LocationStateRepo.kt | 2 - .../kotlin/location/LocationManager.kt | 10 --- logging/build.gradle.kts | 3 - logging/src/commonMain/kotlin/NapierLogger.kt | 2 - logging/src/commonTest/kotlin/LoggerMock.kt | 41 ++++++--- logging/src/iosMain/kotlin/log.kt | 9 +- .../src/commonMain/kotlin/Permission.kt | 23 ++++- .../src/commonMain/kotlin/Permission.kt | 24 ++++- .../kotlin/NotificationsPermissionManager.kt | 10 +-- .../src/iosMain/kotlin/uikit/ButtonStyle.kt | 1 - .../iosMain/kotlin/uikit/UILinkTapGesture.kt | 1 - .../kotlin/uikit/UILinkTextViewDelegate.kt | 1 - .../kotlin/unit/AccelerationUnit.kt | 3 - .../kotlin/unit/AmountOfSubstanceUnit.kt | 2 - .../kotlin/unit/ElectricChargeUnit.kt | 2 - .../src/commonMain/kotlin/unit/SpeedUnit.kt | 3 - .../src/commonMain/kotlin/Permission.kt | 29 ++++++- .../kotlin/StoragePermissionManager.kt | 6 +- .../system/network/state/NetworkStateRepo.kt | 14 ++- .../system/network/NetworkManager.kt | 8 +- .../commonMain/kotlin/MockAlertPresenter.kt | 18 +--- .../src/commonMain/kotlin/ViewModelTest.kt | 17 ++-- .../kotlin/UIThreadViewModelTestTest.kt | 9 +- .../src/commonMain/kotlin/FlowTest.kt | 30 ++----- .../src/commonMain/kotlin/assertions.kt | 38 -------- .../src/commonMain/kotlin/mock/MockMethod.kt | 15 ++-- .../kotlin/testBlockingAndCancelScope.kt | 9 +- .../commonMain/kotlin/ui_thread_testing.kt | 12 +-- .../kotlin/MockDeviceWrapper.kt | 3 +- .../device/MockDeviceConnectionManager.kt | 10 +-- .../commonMain/kotlin/scanner/MockScanner.kt | 3 +- .../kotlin/IOSMockCharacteristicWrapper.kt | 6 +- .../kotlin/IOSMockDescriptorWrapper.kt | 6 +- .../src/commonMain/kotlin/MockHUD.kt | 12 +-- .../src/commonMain/kotlin/MockFocusHandler.kt | 10 +-- .../commonMain/kotlin/MockKeyboardManager.kt | 11 +-- .../src/commonMain/kotlin/KoinUIThreadTest.kt | 4 +- .../kotlin/KoinUIThreadViewModelTest.kt | 8 +- .../commonMain/kotlin/MockLocationManager.kt | 3 +- .../kotlin/MockLocationStateRepoBuilder.kt | 3 +- .../kotlin/MockPermissionManager.kt | 3 +- .../kotlin/MockPermissionsBuilder.kt | 19 ++-- test-utils/src/commonMain/kotlin/legacy.kt | 12 +-- 95 files changed, 615 insertions(+), 695 deletions(-) delete mode 100644 base/src/commonMain/kotlin/AtomicReferenceDelegate.kt delete mode 100644 test-utils-base/src/commonMain/kotlin/assertions.kt diff --git a/alerts/src/commonMain/kotlin/Alerts.kt b/alerts/src/commonMain/kotlin/Alerts.kt index 60c1c0e9d..26dc6b8ab 100644 --- a/alerts/src/commonMain/kotlin/Alerts.kt +++ b/alerts/src/commonMain/kotlin/Alerts.kt @@ -18,11 +18,11 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.alerts -import co.touchlab.stately.concurrency.Lock -import co.touchlab.stately.concurrency.withLock import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlin.coroutines.resume typealias AlertActionHandler = () -> Unit @@ -137,7 +137,7 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { private var actions: MutableList = mutableListOf() private var textInputAction: Alert.TextInputAction? = null private var style: Alert.Style = Alert.Style.ALERT - internal val lock = Lock() + internal val lock = Mutex() /** * Sets the [title] displayed in the alert @@ -321,7 +321,7 @@ expect class AlertPresenter : BaseAlertPresenter { * @param initialize The block to construct an Alert * @return The built alert interface object */ -fun BaseAlertPresenter.Builder.buildAlert( +suspend fun BaseAlertPresenter.Builder.buildAlert( coroutineScope: CoroutineScope, initialize: BaseAlertPresenter.Builder.() -> Unit ): BaseAlertPresenter = lock.withLock { @@ -338,7 +338,7 @@ fun BaseAlertPresenter.Builder.buildAlert( * @param initialize The block to construct an Alert * @return The built alert interface object */ -fun BaseAlertPresenter.Builder.buildActionSheet( +suspend fun BaseAlertPresenter.Builder.buildActionSheet( coroutineScope: CoroutineScope, initialize: BaseAlertPresenter.Builder.() -> Unit ): BaseAlertPresenter = lock.withLock { @@ -355,7 +355,7 @@ fun BaseAlertPresenter.Builder.buildActionSheet( * @param initialize The block to construct an Alert * @return The built alert interface object */ -fun BaseAlertPresenter.Builder.buildAlertWithInput( +suspend fun BaseAlertPresenter.Builder.buildAlertWithInput( coroutineScope: CoroutineScope, initialize: BaseAlertPresenter.Builder.() -> Unit ): BaseAlertPresenter = lock.withLock { diff --git a/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt b/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt index 239124465..70f8532dd 100644 --- a/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt +++ b/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt @@ -21,7 +21,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.viewModelScope -actual open class LifecycleViewModel internal actual constructor(allowFreezing: Boolean) : androidx.lifecycle.ViewModel() { +actual open class LifecycleViewModel internal actual constructor() : androidx.lifecycle.ViewModel() { actual val coroutineScope = viewModelScope diff --git a/architecture/src/commonMain/kotlin/observable/Disposable.kt b/architecture/src/commonMain/kotlin/observable/Disposable.kt index 66f8fb567..3665c9b0b 100644 --- a/architecture/src/commonMain/kotlin/observable/Disposable.kt +++ b/architecture/src/commonMain/kotlin/observable/Disposable.kt @@ -18,10 +18,8 @@ @file:JvmName("DisposableCommonKt") package com.splendo.kaluga.architecture.observable -import co.touchlab.stately.collections.sharedMutableListOf -import co.touchlab.stately.concurrency.AtomicBoolean -import co.touchlab.stately.ensureNeverFrozen -import co.touchlab.stately.isFrozen +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit import kotlin.jvm.JvmName typealias DisposeHandler = () -> Unit @@ -39,12 +37,12 @@ interface Disposable { /** * Disposes the associated object */ - fun dispose() + suspend fun dispose() /** * Adds this disposable to a [DisposeBag] */ - fun addTo(disposeBag: DisposeBag) + suspend fun addTo(disposeBag: DisposeBag) } expect class SimpleDisposable(onDispose: DisposeHandler) : BaseSimpleDisposable @@ -55,20 +53,19 @@ expect class SimpleDisposable(onDispose: DisposeHandler) : BaseSimpleDisposable */ abstract class BaseSimpleDisposable(onDispose: DisposeHandler) : Disposable { - private val _isDisposed = AtomicBoolean(false) - val isDisposed - get() = _isDisposed.value - + private val disposal = Semaphore(1) private var disposeHandler: DisposeHandler? = onDispose /** * Disposes the associated object */ - override fun dispose() { - if (_isDisposed.compareAndSet(expected = false, new = true)) { - disposeHandler?.invoke() - if (!disposeHandler.isFrozen) disposeHandler = null - afterDispose() + override suspend fun dispose() { + disposal.withPermit { + disposeHandler?.let { + it.invoke() + disposeHandler = null + afterDispose() + } } } @@ -77,7 +74,7 @@ abstract class BaseSimpleDisposable(onDispose: DisposeHandler) : Disposable { /** * Adds this disposable to a [DisposeBag] */ - override fun addTo(disposeBag: DisposeBag) { + override suspend fun addTo(disposeBag: DisposeBag) { disposeBag.add(this) } } @@ -85,33 +82,31 @@ abstract class BaseSimpleDisposable(onDispose: DisposeHandler) : Disposable { /** * Container for multiple [Disposable]. Allows nested [DisposeBag]. */ -class DisposeBag(allowFreezing: Boolean = false) : Disposable { - - constructor() : this(false) +class DisposeBag : Disposable { - private val disposables: MutableList = if (allowFreezing) sharedMutableListOf() else mutableListOf() - private val nestedBags: MutableList = if (allowFreezing) sharedMutableListOf() else mutableListOf() - - init { - if (!allowFreezing) - ensureNeverFrozen() // if our DisposeBag gets frozen we cannot dispose properly so this is generally unwanted - } + private val addingOperation = Semaphore(1) + private val disposables: MutableList = mutableListOf() + private val nestedBags: MutableList = mutableListOf() /** * Adds a nested [DisposeBag] */ - fun add(disposeBag: DisposeBag) { - nestedBags.add(disposeBag) + suspend fun add(disposeBag: DisposeBag) { + addingOperation.withPermit { + nestedBags.add(disposeBag) + } } /** * Adds a [Disposable] to this [DisposeBag] */ - fun add(disposable: Disposable) { - disposables.add(disposable) + suspend fun add(disposable: Disposable) { + addingOperation.withPermit { + disposables.add(disposable) + } } - override fun addTo(disposeBag: DisposeBag) { + override suspend fun addTo(disposeBag: DisposeBag) { disposeBag.add(this) } @@ -119,10 +114,14 @@ class DisposeBag(allowFreezing: Boolean = false) : Disposable { * Disposes all [Disposable]s and nested [DisposeBag]s added to this [DisposeBag]. * Added elements can only be disposed once */ - override fun dispose() { + override suspend fun dispose() { disposables.forEach { it.dispose() } - disposables.clear() + addingOperation.withPermit { + disposables.clear() + } nestedBags.forEach { it.dispose() } - nestedBags.clear() + addingOperation.withPermit { + nestedBags.clear() + } } } diff --git a/architecture/src/commonMain/kotlin/observable/Observable.kt b/architecture/src/commonMain/kotlin/observable/Observable.kt index cbad22e80..419ba060e 100644 --- a/architecture/src/commonMain/kotlin/observable/Observable.kt +++ b/architecture/src/commonMain/kotlin/observable/Observable.kt @@ -17,9 +17,6 @@ package com.splendo.kaluga.architecture.observable -import co.touchlab.stately.concurrency.AtomicBoolean -import co.touchlab.stately.concurrency.AtomicReference -import co.touchlab.stately.isFrozen import com.splendo.kaluga.architecture.observable.ObservableOptional.Nothing import com.splendo.kaluga.architecture.observable.ObservableOptional.Value import com.splendo.kaluga.base.KalugaThread @@ -192,14 +189,14 @@ open class Observation>( var onFirstObservation: (() -> Unit)? = null set(value) { // if an observation took place before this field was set, invoke immediately - if (firstObservation.value) { + if (firstObservation) { value?.invoke() } else { field = value } } - private val firstObservation = AtomicBoolean(false) + private var firstObservation = false var beforeObservedValueGet: ((OO) -> ObservableOptional)? = null @@ -221,8 +218,6 @@ open class Observation>( field = value } - private val backingAtomicReference = AtomicReference?>(null) - /** * set the value of this Observable from a suspended context. */ @@ -242,16 +237,10 @@ open class Observation>( */ private fun setValueUnconfined(value: ObservableOptional): ObservableOptional { val v = value.asResult(defaultValue) - val before = backingAtomicReference.get() ?: backingInternalValue + val before = backingInternalValue if (before != v) { - // TODO: since our context is supposed to be single thread, we could use a @ThreadLocal similar to tracking observers - // This would only need to freeze the current result, or if possible a copy of it - - if (!this@Observation.isFrozen) // if the parent is frozen we can no longer update this reference - backingInternalValue = v - else // if it's frozen use an atomic value - backingAtomicReference.set(v) + backingInternalValue = v val result = (v as? Value<*>)?.value as R @@ -264,12 +253,13 @@ open class Observation>( } private fun getValue(): OO { - if (firstObservation.compareAndSet(expected = false, new = true)) { + if (!firstObservation) { + firstObservation = true onFirstObservation?.invoke() } return handleOnMain { - backingAtomicReference.get() ?: backingInternalValue ?: error("unexpected null") + backingInternalValue ?: error("unexpected null") } as OO } diff --git a/architecture/src/commonMain/kotlin/viewmodel/LifecycleViewModel.kt b/architecture/src/commonMain/kotlin/viewmodel/LifecycleViewModel.kt index 5525f5b31..8fea273d1 100644 --- a/architecture/src/commonMain/kotlin/viewmodel/LifecycleViewModel.kt +++ b/architecture/src/commonMain/kotlin/viewmodel/LifecycleViewModel.kt @@ -27,18 +27,18 @@ import kotlinx.coroutines.cancelChildren message = "ViewModel was renamed as the name didn't match it's function.", replaceWith = ReplaceWith("LifecycleViewModel") ) -open class ViewModel(allowFreezing: Boolean = false) : LifecycleViewModel(allowFreezing) +open class ViewModel : LifecycleViewModel() @Deprecated( message = "BaseViewModel was renamed as the name didn't match it's function.", replaceWith = ReplaceWith("BaseLifecycleViewModel") ) -open class BaseViewModel(allowFreezing: Boolean = false) : BaseLifecycleViewModel(allowFreezing) +open class BaseViewModel : BaseLifecycleViewModel() /** * Simple ViewModel class */ -expect open class LifecycleViewModel internal constructor(allowFreezing: Boolean = false) { +expect open class LifecycleViewModel internal constructor() { /** * [CoroutineScope] of the ViewModel */ @@ -53,7 +53,7 @@ expect open class LifecycleViewModel internal constructor(allowFreezing: Boolean /** * Default [LifecycleViewModel] implementation respecting the Lifecycle of the presenting view */ -open class BaseLifecycleViewModel(allowFreezing: Boolean = false) : LifecycleViewModel(allowFreezing) { +open class BaseLifecycleViewModel : LifecycleViewModel() { private val resumedJobs = SupervisorJob() @@ -88,4 +88,4 @@ open class BaseLifecycleViewModel(allowFreezing: Boolean = false) : LifecycleVie * Default [LifecycleViewModel] allowing navigation * @param navigator The [Navigator] handling navigation */ -open class NavigatingViewModel>(val navigator: Navigator, allowFreezing: Boolean = false) : BaseViewModel(allowFreezing) +open class NavigatingViewModel>(val navigator: Navigator) : BaseLifecycleViewModel() diff --git a/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt b/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt index 7e5425bec..9819c9e54 100644 --- a/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt +++ b/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.architecture.observable -import co.touchlab.stately.concurrency.AtomicReference -import co.touchlab.stately.freeze import com.splendo.kaluga.architecture.observable.ObservableOptional.Nothing import com.splendo.kaluga.architecture.observable.ObservableOptional.Value import com.splendo.kaluga.base.runBlocking @@ -201,24 +199,24 @@ abstract class ObservableBaseTest : BaseTest() { assertEquals(initialExpected.value, property) } - val observedValue = AtomicReference(unusedValue) - val disposable = observable.observe { observedValue.set(it) } + var observedValue: R? = unusedValue + val disposable = observable.observe { observedValue = it } - var observedInitializedValue: AtomicReference? = null + var observedInitializedValue: R? = null var disposableInitialized: Disposable? = null if (observable is Initialized<*, *>) { - observedInitializedValue = AtomicReference(unusedValue) + observedInitializedValue = unusedValue disposableInitialized = (observable as Initialized).observeInitialized { observedInitializedValue.set(it) } } when (initialExpected) { - is Nothing<*> -> assertEquals(null, observedValue.get()) - is Value<*> -> assertEquals(initialExpected.value, observedValue.get()) + is Nothing<*> -> assertEquals(null, observedValue) + is Value<*> -> assertEquals(initialExpected.value, observedValue) } if (observedInitializedValue != null) { assertTrue(initialExpected is Value<*>) - assertEquals(initialExpected.value as R, observedInitializedValue.get()) + assertEquals(initialExpected.value as R, observedInitializedValue) } semaphore.release() @@ -227,7 +225,7 @@ abstract class ObservableBaseTest : BaseTest() { val lastUpdate = count == updates.size - 1 - val observedBefore = observedValue.get() + val observedBefore = observedValue if (lastUpdate) { disposable.dispose() @@ -247,15 +245,15 @@ abstract class ObservableBaseTest : BaseTest() { } if (lastUpdate || expected !is Value<*>) // TODO <-- sus - assertEquals(observedBefore, observedValue.get()) + assertEquals(observedBefore, observedValue) else - assertEquals(expected.value, observedValue.get()) + assertEquals(expected.value, observedValue) if (!lastUpdate && observedInitializedValue != null) { assertTrue(expected is Value<*>) - assertEquals(expected.value, observedInitializedValue.get()) + assertEquals(expected.value, observedInitializedValue) } else if (observedInitializedValue != null) { - assertEquals(observedBefore, observedInitializedValue.get()) + assertEquals(observedBefore, observedInitializedValue) } semaphore.release() diff --git a/architecture/src/commonTest/kotlin/observable/ReadOnlyPropertyTest.kt b/architecture/src/commonTest/kotlin/observable/ReadOnlyPropertyTest.kt index 5c7cc7d61..819542012 100644 --- a/architecture/src/commonTest/kotlin/observable/ReadOnlyPropertyTest.kt +++ b/architecture/src/commonTest/kotlin/observable/ReadOnlyPropertyTest.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.architecture.observable -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.runBlocking import kotlinx.coroutines.flow.MutableStateFlow import kotlin.properties.ReadOnlyProperty @@ -35,8 +34,8 @@ class ReadOnlyPropertyTest : ObservableBaseTest() { val observable = ro.toDefaultObservable("default") - val observed = AtomicReference("nothing yet") - observable.observeInitialized { observed.set(it) } + var observed = "nothing yet" + observable.observeInitialized { observed = it } testStringDefaultObservable( observable = observable, @@ -44,7 +43,7 @@ class ReadOnlyPropertyTest : ObservableBaseTest() { shortDelayAfterUpdate = false, { nullableString.value = "new" - assertEquals("default", observed.get(), "the property will only report the new value upon read") + assertEquals("default", observed, "the property will only report the new value upon read") "new" }, // when we actually read the property to test this, the new value will propagate { diff --git a/architecture/src/iosMain/kotlin/observable/SimpleDisposable.kt b/architecture/src/iosMain/kotlin/observable/SimpleDisposable.kt index d575e276e..2161caa35 100644 --- a/architecture/src/iosMain/kotlin/observable/SimpleDisposable.kt +++ b/architecture/src/iosMain/kotlin/observable/SimpleDisposable.kt @@ -42,7 +42,4 @@ actual fun > observers(observation: Observa return observersForObservation[observation] as? List<(R) -> Unit> ?: emptyList() } -// Use this to have a thread local reference on iOS that does not get frozen -// TODO: this can be further improved by using a WeakRef -@ThreadLocal private val observersForObservation = mutableMapOf, MutableList<(Any?)->Unit>>() diff --git a/architecture/src/iosMain/kotlin/viewmodel/LifecycleViewModel.kt b/architecture/src/iosMain/kotlin/viewmodel/LifecycleViewModel.kt index ad4da39e4..e3ed57336 100644 --- a/architecture/src/iosMain/kotlin/viewmodel/LifecycleViewModel.kt +++ b/architecture/src/iosMain/kotlin/viewmodel/LifecycleViewModel.kt @@ -21,14 +21,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren -import kotlin.native.concurrent.ensureNeverFrozen -actual open class LifecycleViewModel internal actual constructor(val allowFreezing: Boolean) { - - init { - if (!allowFreezing) - ensureNeverFrozen() - } +actual open class LifecycleViewModel internal actual constructor() { private val lifecycleJob = SupervisorJob() diff --git a/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt b/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt index c5ccead87..657fb55d9 100644 --- a/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt +++ b/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt @@ -33,9 +33,9 @@ import platform.UIKit.willMoveToParentViewController * This convenience class is the result of [BaseLifecycleViewModel.addLifecycleManager]. * Invoke [unbind] to unbind the Lifecycle from its bound [UIViewController] */ -class LifecycleManager internal constructor(allowFreezing: Boolean, clearViewModel: () -> Unit) { +class LifecycleManager internal constructor(clearViewModel: () -> Unit) { - internal val disposeBag: DisposeBag = DisposeBag(allowFreezing) + internal val disposeBag: DisposeBag = DisposeBag() private var onUnbind: (() -> Unit)? = clearViewModel /** @@ -55,7 +55,7 @@ typealias onLifeCycleChanged = () -> List internal class ViewModelLifecycleManager(private val viewModel: VM, private val onLifecycle: onLifeCycleChanged) : UIViewController(null, null) { - internal val lifecycleManager = LifecycleManager(viewModel.allowFreezing) { + internal val lifecycleManager = LifecycleManager { viewModel.clear() willMoveToParentViewController(null) view.removeFromSuperview() @@ -66,7 +66,9 @@ internal class ViewModelLifecycleManager(private va super.viewDidAppear(animated) viewModel.didResume() - onLifecycle().forEach { lifecycleManager.disposeBag.add(it) } + onLifecycle().forEach { + lifecycleManager.disposeBag.add(it) + } } override fun viewDidDisappear(animated: Boolean) { diff --git a/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt b/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt index b6c4b6212..c23e2c733 100644 --- a/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt +++ b/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.architecture.observable -import co.touchlab.stately.ensureNeverFrozen import com.splendo.kaluga.base.runBlocking import com.splendo.kaluga.test.base.assertFrozen import com.splendo.kaluga.test.base.assertNotFrozen @@ -36,10 +35,6 @@ class ThreadingTest { assertNotFrozen(s) val value = Value() - // provide early failure for debugging test - value.ensureNeverFrozen() - s.ensureNeverFrozen() - assertNotFrozen(value) assertNotFrozen(s) s.set(value) diff --git a/architecture/src/jsMain/kotlin/viewmodel/LifecycleViewModel.kt b/architecture/src/jsMain/kotlin/viewmodel/LifecycleViewModel.kt index b7434570d..67858ad66 100644 --- a/architecture/src/jsMain/kotlin/viewmodel/LifecycleViewModel.kt +++ b/architecture/src/jsMain/kotlin/viewmodel/LifecycleViewModel.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren -actual open class LifecycleViewModel internal actual constructor(allowFreezing: Boolean) { +actual open class LifecycleViewModel internal actual constructor() { private val lifecycleJob = SupervisorJob() diff --git a/architecture/src/jvmMain/kotlin/viewmodel/LifecycleViewModel.kt b/architecture/src/jvmMain/kotlin/viewmodel/LifecycleViewModel.kt index b7434570d..67858ad66 100644 --- a/architecture/src/jvmMain/kotlin/viewmodel/LifecycleViewModel.kt +++ b/architecture/src/jvmMain/kotlin/viewmodel/LifecycleViewModel.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren -actual open class LifecycleViewModel internal actual constructor(allowFreezing: Boolean) { +actual open class LifecycleViewModel internal actual constructor() { private val lifecycleJob = SupervisorJob() diff --git a/base-permissions/src/commonMain/kotlin/Permission.kt b/base-permissions/src/commonMain/kotlin/Permission.kt index f50c909f5..e5d009917 100644 --- a/base-permissions/src/commonMain/kotlin/Permission.kt +++ b/base-permissions/src/commonMain/kotlin/Permission.kt @@ -17,16 +17,16 @@ package com.splendo.kaluga.permissions.base -import co.touchlab.stately.collections.IsoMutableMap import com.splendo.kaluga.base.singleThreadDispatcher import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext -import kotlin.native.concurrent.SharedImmutable import kotlin.reflect.KClass import kotlin.reflect.KClassifier @@ -48,50 +48,88 @@ interface BasePermissionsBuilder

* Closure that takes permission and coroutine contexts and creates [PermissionStateRepo]. * Each platform registers [PermissionStateRepoBuilder] in the register permission helper method. */ -private interface PermissionStateRepoBuilder

{ +interface PermissionStateRepoBuilder

{ fun create(permission: P, coroutineContext: CoroutineContext): BasePermissionStateRepo

} expect class PermissionContext expect val defaultPermissionContext: PermissionContext +class PermissionsBuilderError(message: String) : Error(message) + /** * Builder for providing the proper [PermissionManager] for each [Permission] * @param context [PermissionContext] an additional parameter platform can pass to the [PermissionsBuilder]. [NSBundle] on iOS and [Contect] on Andoid. */ open class PermissionsBuilder(val context: PermissionContext = defaultPermissionContext) { - private val builders = IsoMutableMap>() - private val repoBuilders = IsoMutableMap>() + private val buildersLock = Mutex() + private val builders = mutableMapOf>() + private val repoBuildersLock = Mutex() + private val repoBuilders = mutableMapOf>() + + suspend inline fun > register(builder: B): B = register(P::class, builder) + suspend fun

> register(permission: KClass

, builder: B): B = buildersLock.withLock { + if (builders[permission] == null) { + builders[permission] = builder + builder + } else { + throw PermissionsBuilderError("Builder for $permission was already registered") + } + } - inline fun > register(builder: B): B = register(P::class, builder) - fun

> register(permission: KClass

, builder: B): B { - builders[permission] = builder - return builder + suspend inline fun > registerOrGet(builder: B): BasePermissionsBuilder

= registerOrGet(P::class, builder) + suspend fun

> registerOrGet(permission: KClass

, builder: B): BasePermissionsBuilder

= buildersLock.withLock { + builders.getOrPut(permission::class) { builder } as BasePermissionsBuilder

+ } + + suspend fun

unregister(permission: P) { + buildersLock.withLock { + builders.remove(permission::class) + } } operator fun

get(permission: P): BasePermissionsBuilder

= - builders[permission::class] as? BasePermissionsBuilder

?: throw Error("The Builder for $permission was not registered") - - inline fun registerPermissionStateRepoBuilder(noinline permissionStateRepoBuilder: (P, CoroutineContext) -> BasePermissionStateRepo

) = registerPermissionStateRepoBuilder(P::class, permissionStateRepoBuilder) - fun

registerPermissionStateRepoBuilder(permission: KClass

, permissionStateRepoBuilder: (P, CoroutineContext) -> BasePermissionStateRepo

) { - repoBuilders[permission] = object : PermissionStateRepoBuilder

{ - override fun create( - permission: P, - coroutineContext: CoroutineContext - ): BasePermissionStateRepo

{ - return permissionStateRepoBuilder(permission, coroutineContext) + builders[permission::class] as? BasePermissionsBuilder

?: throw PermissionsBuilderError("The Builder for $permission was not registered") + + suspend inline fun registerPermissionStateRepoBuilder(noinline permissionStateRepoBuilder: (P, CoroutineContext) -> BasePermissionStateRepo

) = registerPermissionStateRepoBuilder(P::class, permissionStateRepoBuilder) + suspend fun

registerPermissionStateRepoBuilder(permission: KClass

, permissionStateRepoBuilder: (P, CoroutineContext) -> BasePermissionStateRepo

) { + repoBuildersLock.withLock { + if (repoBuilders[permission] == null) { + createPermissionStateRepoBuilder(permissionStateRepoBuilder).also { repoBuilders[permission] = it } + } else { + throw PermissionsBuilderError("Builder for $permission PermissionStateRepo was already registered") } } } + suspend inline fun registerOrGetPermissionStateRepoBuilder(noinline permissionStateRepoBuilder: (P, CoroutineContext) -> BasePermissionStateRepo

) = registerOrGetPermissionStateRepoBuilder(P::class, permissionStateRepoBuilder) + suspend fun

registerOrGetPermissionStateRepoBuilder(permission: KClass

, permissionStateRepoBuilder: (P, CoroutineContext) -> BasePermissionStateRepo

) = repoBuildersLock.withLock { + repoBuilders.getOrPut(permission) { createPermissionStateRepoBuilder(permissionStateRepoBuilder) } as PermissionStateRepoBuilder

+ } + + + suspend fun

unregisterPermissionStateRepoBuilder(permission: P) { + repoBuildersLock.withLock { + repoBuilders.remove(permission::class) + } + } + fun

createPermissionStateRepo(permission: P, coroutineContext: CoroutineContext): BasePermissionStateRepo<*> = (repoBuilders[permission::class] as? PermissionStateRepoBuilder

)?.let { it.create(permission, coroutineContext) - } ?: throw Error("Permission state repo factory was not registered for $permission") + } ?: throw PermissionsBuilderError("Permission state repo factory was not registered for $permission") + + private inline fun

createPermissionStateRepoBuilder(crossinline permissionStateRepoBuilder: (P, CoroutineContext) -> BasePermissionStateRepo

): PermissionStateRepoBuilder

= object : PermissionStateRepoBuilder

{ + override fun create( + permission: P, + coroutineContext: CoroutineContext + ): BasePermissionStateRepo

{ + return permissionStateRepoBuilder(permission, coroutineContext) + } + } } -@SharedImmutable // NOTE: replace with a limited parallelism dispatcher view when available private val defaultPermissionDispatcher by lazy { singleThreadDispatcher("Permissions") } @@ -106,10 +144,11 @@ class Permissions( private val coroutineContext: CoroutineContext = defaultPermissionDispatcher, ) { - private val permissionStateRepos: IsoMutableMap> = IsoMutableMap() + private val permissionStateRepos = mutableMapOf>() - private fun

permissionStateRepo(permission: P) = - (permissionStateRepos[permission] ?: builder.createPermissionStateRepo(permission, coroutineContext + CoroutineName(permission.name)).also { permissionStateRepos[permission] = it }) as BasePermissionStateRepo

+ private fun

permissionStateRepo(permission: P): BasePermissionStateRepo

= permissionStateRepos.getOrPut(permission) { + builder.createPermissionStateRepo(permission, coroutineContext + CoroutineName(permission.name)) + } as BasePermissionStateRepo

/** * Gets a [Flow] of [PermissionState] for a given [Permission] diff --git a/base-permissions/src/iosMain/kotlin/IOSPermissionsHelper.kt b/base-permissions/src/iosMain/kotlin/IOSPermissionsHelper.kt index 7d1649c7b..731ee75a8 100644 --- a/base-permissions/src/iosMain/kotlin/IOSPermissionsHelper.kt +++ b/base-permissions/src/iosMain/kotlin/IOSPermissionsHelper.kt @@ -26,6 +26,7 @@ import com.splendo.kaluga.permissions.base.IOSPermissionsHelper.AuthorizationSta import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.withLock import platform.Foundation.NSBundle /** @@ -75,10 +76,9 @@ class IOSPermissionsHelper { fun AuthorizationStatusHandler.requestAuthorizationStatus(timerHelper: PermissionRefreshScheduler? = null, coroutineScope: CoroutineScope, request: suspend () -> AuthorizationStatus) { coroutineScope.launch { - timerHelper?.isWaiting?.value = true - val newStatus = request() - timerHelper?.isWaiting?.value = false - status(newStatus) + timerHelper?.waitingLock?.withLock { + request() + }?.let { status(it) } } } diff --git a/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt b/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt index 2326b2844..5fd2695a5 100644 --- a/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt +++ b/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.permissions.base -import co.touchlab.stately.concurrency.AtomicBoolean -import co.touchlab.stately.concurrency.AtomicReference import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -60,10 +58,10 @@ class PermissionRefreshScheduler( } } - private val lastPermission: AtomicReference = AtomicReference(null) - val isWaiting = AtomicBoolean(false) - private val timerState: AtomicReference = AtomicReference(TimerJobState.TimerNotRunning) - private val timerLock = Mutex() + private var lastPermission: IOSPermissionsHelper.AuthorizationStatus? = null + val waitingLock = Mutex() + private var timerState: TimerJobState = TimerJobState.TimerNotRunning + private val lock = Mutex() /** * Starts monitoring for changes to the permission @@ -76,18 +74,16 @@ class PermissionRefreshScheduler( } private suspend fun launchTimerJob(interval: Duration) { - timerLock.withLock { - val timerJobState = timerState.get() + lock.withLock { + val timerJobState = timerState if (timerJobState is TimerJobState.TimerNotRunning) { - this.timerState.set( - timerJobState.startTimer(interval, this) { + this.timerState = timerJobState.startTimer(interval, this) { val status = currentAuthorizationStatusProvider.provide() - if (!isWaiting.value && lastPermission.get() != status) { - updateLastPermission() + if (!waitingLock.isLocked && lastPermission != status) { + lastPermission = currentAuthorizationStatusProvider.provide() onPermissionChangedFlow.status(status) } } - ) } } } @@ -97,17 +93,13 @@ class PermissionRefreshScheduler( */ fun stopMonitoring() { launch { - timerLock.withLock { - val timerJobState = timerState.get() + lock.withLock { + val timerJobState = timerState if (timerJobState is TimerJobState.TimerRunning) { - this@PermissionRefreshScheduler.timerState.set(timerJobState.stopTimer()) + this@PermissionRefreshScheduler.timerState = timerJobState.stopTimer() } + lastPermission = null } } - lastPermission.set(null) - } - - private suspend fun updateLastPermission() { - lastPermission.set(currentAuthorizationStatusProvider.provide()) } } diff --git a/base-permissions/src/iosMain/kotlin/av/AVPermissionHelper.kt b/base-permissions/src/iosMain/kotlin/av/AVPermissionHelper.kt index 2bda0137f..106f86e08 100644 --- a/base-permissions/src/iosMain/kotlin/av/AVPermissionHelper.kt +++ b/base-permissions/src/iosMain/kotlin/av/AVPermissionHelper.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.permissions.base.av -import co.touchlab.stately.freeze import com.splendo.kaluga.logging.error import com.splendo.kaluga.permissions.base.AuthorizationStatusHandler import com.splendo.kaluga.permissions.base.CurrentAuthorizationStatusProvider @@ -63,14 +62,12 @@ class AVPermissionHelper( val mediaType = type.avMediaType onPermissionChangedFlow.requestAuthorizationStatus(timerHelper, coroutineScope) { val deferred = CompletableDeferred() - val callback = { allowed: Boolean -> + AVCaptureDevice.requestAccessForMediaType( + mediaType + ) { allowed -> deferred.complete(allowed) Unit - }.freeze() - AVCaptureDevice.requestAccessForMediaType( - mediaType, - callback - ) + } if (deferred.await()) IOSPermissionsHelper.AuthorizationStatus.Authorized else IOSPermissionsHelper.AuthorizationStatus.Denied } } else { diff --git a/base-permissions/src/iosTest/kotlin/PermissionRefreshSchedulerTest.kt b/base-permissions/src/iosTest/kotlin/PermissionRefreshSchedulerTest.kt index f24128222..ecba50ade 100644 --- a/base-permissions/src/iosTest/kotlin/PermissionRefreshSchedulerTest.kt +++ b/base-permissions/src/iosTest/kotlin/PermissionRefreshSchedulerTest.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.permissions.base -import co.touchlab.stately.concurrency.AtomicReference -import co.touchlab.stately.concurrency.value import com.splendo.kaluga.base.runBlocking import com.splendo.kaluga.test.base.BaseTest import kotlinx.coroutines.delay @@ -32,11 +30,10 @@ class PermissionRefreshSchedulerTest : BaseTest() { @Test fun testStartMonitoring() = runBlocking { - val authorization: AtomicReference = - AtomicReference(IOSPermissionsHelper.AuthorizationStatus.NotDetermined) + var authorization = IOSPermissionsHelper.AuthorizationStatus.NotDetermined val authorizationProvider = object : CurrentAuthorizationStatusProvider { override suspend fun provide(): IOSPermissionsHelper.AuthorizationStatus { - return authorization.value + return authorization } } @@ -55,24 +52,24 @@ class PermissionRefreshSchedulerTest : BaseTest() { delay(50) assertNull(onPermissionChangedFlow.value) - authorization.set(IOSPermissionsHelper.AuthorizationStatus.Authorized) + authorization = IOSPermissionsHelper.AuthorizationStatus.Authorized delay(60) assertEquals( IOSPermissionsHelper.AuthorizationStatus.Authorized, onPermissionChangedFlow.value ) - timerHelper.isWaiting.value = true - authorization.set(IOSPermissionsHelper.AuthorizationStatus.Denied) + timerHelper.waitingLock.lock() + authorization = IOSPermissionsHelper.AuthorizationStatus.Denied onPermissionChangedFlow.value = null delay(60) assertNull(onPermissionChangedFlow.value) - timerHelper.isWaiting.value = false + timerHelper.waitingLock.unlock() delay(50) assertEquals(IOSPermissionsHelper.AuthorizationStatus.Denied, onPermissionChangedFlow.value) - authorization.set(IOSPermissionsHelper.AuthorizationStatus.Authorized) + authorization = IOSPermissionsHelper.AuthorizationStatus.Authorized onPermissionChangedFlow.value = null timerHelper.stopMonitoring() delay(50) diff --git a/base/build.gradle.kts b/base/build.gradle.kts index f965cef72..491c72910 100644 --- a/base/build.gradle.kts +++ b/base/build.gradle.kts @@ -16,10 +16,6 @@ kotlin { getByName("commonMain") { dependencies { implementation(project(":logging", "")) - expose(Dependencies.Stately.Common) - expose(Dependencies.Stately.Isolate) - expose(Dependencies.Stately.IsoCollections) - expose(Dependencies.Stately.Concurrency) } } getByName("jsMain") { diff --git a/base/src/commonMain/kotlin/AtomicReferenceDelegate.kt b/base/src/commonMain/kotlin/AtomicReferenceDelegate.kt deleted file mode 100644 index bb3e14964..000000000 --- a/base/src/commonMain/kotlin/AtomicReferenceDelegate.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright 2021 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.base - -import co.touchlab.stately.concurrency.AtomicReference -import kotlin.reflect.KProperty - -/** - * Multiplatform [AtomicReference] property wrapper. - * - * Example: - * ``` - * val atomicJobRef: Job? by AtomicReferenceDelegate() - * atomicJobRef = Job() - * atomicJobRef = null - * ``` - */ -class AtomicReferenceDelegate(initialValue: T? = null) { - private val reference = AtomicReference(initialValue) - operator fun getValue(thisRef: Any?, property: KProperty<*>) = reference.get() - operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) = reference.set(value) -} diff --git a/base/src/commonMain/kotlin/flow/BaseFlowable.kt b/base/src/commonMain/kotlin/flow/BaseFlowable.kt index f4b4d7564..058f9a482 100644 --- a/base/src/commonMain/kotlin/flow/BaseFlowable.kt +++ b/base/src/commonMain/kotlin/flow/BaseFlowable.kt @@ -18,7 +18,6 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.flow -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.logging.warn import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.channels.ConflatedBroadcastChannel @@ -35,9 +34,9 @@ import kotlinx.coroutines.flow.first @Deprecated("This Channel based implementation is deprecated in favor of SharedFlow or StateFlow") abstract class BaseFlowable(private val channelFactory: () -> BroadcastChannel = { ConflatedBroadcastChannel() }) : Flowable { - private val channel: AtomicReference?> = AtomicReference(null) + private var channel: BroadcastChannel? = null protected fun ensureChannel(): BroadcastChannel { - return channel.get() ?: channelFactory().also { channel.set(it) } + return channel ?: channelFactory().also { channel = it } } override fun flow(flowConfig: FlowConfig): Flow { @@ -48,17 +47,17 @@ abstract class BaseFlowable(private val channelFactory: () -> BroadcastChanne } override suspend fun set(value: T) { - channel.get()?.send(value) ?: warn("'$value' offered to Flowable but there is no channel active") + channel?.send(value) ?: warn("'$value' offered to Flowable but there is no channel active") } override fun cancelFlows() { - channel.get()?.let { + channel?.let { it.close() - channel.compareAndSet(it, null) + channel = null } } // Note: if the channel is buffered, this could be wrong. // Since Flowable is deprecated this will not be fixed - protected suspend fun currentValue(): T? = channel.get()?.asFlow()?.first() + protected suspend fun currentValue(): T? = channel?.asFlow()?.first() } diff --git a/base/src/commonMain/kotlin/flow/ColdFlowable.kt b/base/src/commonMain/kotlin/flow/ColdFlowable.kt index a5c86bec8..624e68dba 100644 --- a/base/src/commonMain/kotlin/flow/ColdFlowable.kt +++ b/base/src/commonMain/kotlin/flow/ColdFlowable.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.base.flow -import co.touchlab.stately.concurrency.AtomicInt import com.splendo.kaluga.flow.BaseFlowable import com.splendo.kaluga.flow.FlowConfig import kotlinx.coroutines.channels.BroadcastChannel @@ -41,22 +40,22 @@ import kotlinx.coroutines.sync.withLock class ColdFlowable(private val initialize: suspend () -> T, private val deinitialize: suspend (T) -> Unit, channelFactory: () -> BroadcastChannel = { ConflatedBroadcastChannel() }) : BaseFlowable(channelFactory) { private val counterMutex = Mutex() - private var flowingCounter = AtomicInt(0) + private var flowingCounter = 0 override fun flow(flowConfig: FlowConfig): Flow { return super.flow(flowConfig) .onStart { counterMutex.withLock { - val count = flowingCounter.incrementAndGet() - if (count == 1) { + flowingCounter++ + if (flowingCounter == 1) { set(initialize()) } } } .onCompletion { counterMutex.withLock { - val count = flowingCounter.decrementAndGet() - if (count == 0) { + flowingCounter-- + if (flowingCounter == 0) { val finalValue = currentValue() cancelFlows() finalValue?.let { diff --git a/base/src/commonMain/kotlin/state/KalugaState.kt b/base/src/commonMain/kotlin/state/KalugaState.kt index 72f79a1e7..450514936 100644 --- a/base/src/commonMain/kotlin/state/KalugaState.kt +++ b/base/src/commonMain/kotlin/state/KalugaState.kt @@ -17,7 +17,6 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands */ package com.splendo.kaluga.state -import co.touchlab.stately.concurrency.AtomicBoolean import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.FirstCollection import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.LaterCollections import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.NoMoreCollections @@ -40,13 +39,13 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withPermit import kotlin.coroutines.CoroutineContext -import kotlin.native.concurrent.SharedImmutable import kotlin.reflect.KClass -@SharedImmutable private val remain: suspend() -> KalugaState = { error("This should never be called. It's only used to indicate the state should remain the same") } /** @@ -152,10 +151,12 @@ abstract class StateRepo>(coroutineCon @Deprecated(message = "StateRepo itself is now a Flow", replaceWith = ReplaceWith("StateRepo")) fun flow(): Flow = mutableFlow.asSharedFlow() - private val initialized = AtomicBoolean(false) + private val initializedMutex = Mutex() + private var initialized = false - internal open suspend fun initialize(initialValue: S? = null): S = - if (initialized.compareAndSet(false, true)) + internal open suspend fun initialize(initialValue: S? = null): S = initializedMutex.withLock { + if (!initialized) { + initialized = true (initialValue ?: initialValue()).also { value -> mutableFlow.emit(value) stateMutex.release() // release the initial permit held @@ -164,8 +165,10 @@ abstract class StateRepo>(coroutineCon // State machines that need initialization should rely on having an initialization state rather than using this method. value.initialState() } - else + } else { state() + } + } /** * Gets the initial value of the repo @@ -348,17 +351,20 @@ abstract class BaseHotStateRepo>( abstract val lazyMutableSharedFlow: Lazy - // guards once only initialization across threads - private val initialized = AtomicBoolean(false) + private val lock = Mutex() + private var initialized = false override val mutableFlow: F get() { val isInitialized = lazyMutableSharedFlow.isInitialized() val flow = lazyMutableSharedFlow.value - if (!isInitialized && initialized.compareAndSet(expected = false, new = true)) - launch(coroutineContext) { + launch(coroutineContext) { + if (lock.withLock { + (!isInitialized && !initialized).also { if (it) initialized = true } + }) { initialize() } + } return flow } } @@ -377,8 +383,8 @@ abstract class BaseColdStateRepo>( context: CoroutineContext = Dispatchers.Main.immediate ) : StateRepo(context) { - // guards once only initialization across threads - private val initialized = AtomicBoolean(false) + private val lock = Mutex() + private var initialized = false abstract val lazyMutableFlow: Lazy @@ -386,8 +392,14 @@ abstract class BaseColdStateRepo>( get() { val isInitialized = lazyMutableFlow.isInitialized() val flow = lazyMutableFlow.value - if (!isInitialized && initialized.compareAndSet(expected = false, new = true)) { - launch(coroutineContext) { + launch(coroutineContext) { + if (lock.withLock { + (!isInitialized && !initialized).also { + if (it) initialized = true + } + } + ) + { flow.onCollectionEvent { event -> when (event) { NoMoreCollections -> noMoreCollections().also { it.finalState() } diff --git a/base/src/commonMain/kotlin/utils/TimeZone.kt b/base/src/commonMain/kotlin/utils/TimeZone.kt index a92866afb..51b972cf5 100644 --- a/base/src/commonMain/kotlin/utils/TimeZone.kt +++ b/base/src/commonMain/kotlin/utils/TimeZone.kt @@ -19,7 +19,6 @@ package com.splendo.kaluga.base.utils import com.splendo.kaluga.base.utils.DefaultKalugaDate.Companion.now import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale -import kotlin.native.concurrent.ThreadLocal /** * Style for writing the name of a [TimeZone] @@ -30,7 +29,6 @@ enum class TimeZoneNameStyle { Long } -@ThreadLocal // one instance per thread is preferred over a non-lazy initialization, since timezones are heavy to instantiate val TimeZone.Companion.utc: TimeZone by lazy { TimeZone.get("UTC")!! } expect class TimeZone { diff --git a/base/src/commonTest/kotlin/AtomicReferenceTest.kt b/base/src/commonTest/kotlin/AtomicReferenceTest.kt index ea478ab65..30b4a9b22 100644 --- a/base/src/commonTest/kotlin/AtomicReferenceTest.kt +++ b/base/src/commonTest/kotlin/AtomicReferenceTest.kt @@ -15,7 +15,6 @@ */ -import com.splendo.kaluga.base.AtomicReferenceDelegate import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull diff --git a/base/src/iosMain/kotlin/GCScheduler.kt b/base/src/iosMain/kotlin/GCScheduler.kt index 53c89af29..9c00ee80f 100644 --- a/base/src/iosMain/kotlin/GCScheduler.kt +++ b/base/src/iosMain/kotlin/GCScheduler.kt @@ -17,18 +17,16 @@ package com.splendo.kaluga.base -import co.touchlab.stately.concurrency.AtomicBoolean import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch -import kotlin.native.concurrent.ThreadLocal +import kotlinx.coroutines.sync.Mutex import kotlin.native.internal.GC -@SharedImmutable -private val isCollecting: AtomicBoolean = AtomicBoolean(false) -@ThreadLocal object GCScheduler { + private val collectingMutex = Mutex() + /** * Schedules the Garbage Collector only if no other garbage collection is active. * This is useful in case calling the Garbage collector could trigger another call to [GC.collect]. @@ -42,9 +40,9 @@ object GCScheduler { * This is useful in case calling the Garbage collector could trigger another call to [GC.collect]. */ fun collectIfNotCollecting() { - if (isCollecting.compareAndSet(expected = false, new = true)) { + if (collectingMutex.tryLock()) { GC.collect() - isCollecting.value = false + collectingMutex.unlock() } } } diff --git a/beacons/src/commonMain/kotlin/Beacons.kt b/beacons/src/commonMain/kotlin/Beacons.kt index 14fe7fbd6..cf72c9fd3 100644 --- a/beacons/src/commonMain/kotlin/Beacons.kt +++ b/beacons/src/commonMain/kotlin/Beacons.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.bluetooth.beacons -import co.touchlab.stately.collections.IsoMutableMap -import com.splendo.kaluga.base.AtomicReferenceDelegate import com.splendo.kaluga.base.utils.DefaultKalugaDate import com.splendo.kaluga.bluetooth.BluetoothService import com.splendo.kaluga.bluetooth.device.Device @@ -38,7 +36,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch private typealias BeaconJob = Pair -private typealias BeaconsMap = IsoMutableMap +private typealias BeaconsMap = MutableMap class Beacons( private val bluetooth: BluetoothService, @@ -47,7 +45,7 @@ class Beacons( private companion object { const val TAG = "Beacons" } - private val cache = BeaconsMap() + private val cache = mutableMapOf() private val cacheJob = Job() private val coroutineScope = CoroutineScope(Dispatchers.Default + cacheJob) private var monitoringJob: Job? by AtomicReferenceDelegate() diff --git a/bluetooth-permissions/src/commonMain/kotlin/Permission.kt b/bluetooth-permissions/src/commonMain/kotlin/Permission.kt index f3ae4579c..6c65f6654 100644 --- a/bluetooth-permissions/src/commonMain/kotlin/Permission.kt +++ b/bluetooth-permissions/src/commonMain/kotlin/Permission.kt @@ -32,7 +32,7 @@ object BluetoothPermission : Permission() { override val name: String = "Bluetooth" } -fun PermissionsBuilder.registerBluetoothPermission( +suspend fun PermissionsBuilder.registerBluetoothPermission( bluetoothPermissionManagerBuilderBuilder: (PermissionContext) -> BaseBluetoothPermissionManagerBuilder = ::BluetoothPermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -46,7 +46,7 @@ fun PermissionsBuilder.registerBluetoothPermission( ) } -fun PermissionsBuilder.registerBluetoothPermission( +suspend fun PermissionsBuilder.registerBluetoothPermission( bluetoothPermissionManagerBuilderBuilder: (PermissionContext) -> BaseBluetoothPermissionManagerBuilder = ::BluetoothPermissionManagerBuilder, stateRepoBuilder: (BaseBluetoothPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = bluetoothPermissionManagerBuilderBuilder(context).also { @@ -55,3 +55,27 @@ fun PermissionsBuilder.registerBluetoothPermission( stateRepoBuilder(it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerBluetoothPermissionIfNotRegistered( + bluetoothPermissionManagerBuilderBuilder: (PermissionContext) -> BaseBluetoothPermissionManagerBuilder = ::BluetoothPermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerBluetoothPermissionIfNotRegistered(bluetoothPermissionManagerBuilderBuilder) { baseBluetoothPermissionManagerBuilder, coroutineContext -> + BluetoothPermissionStateRepo( + baseBluetoothPermissionManagerBuilder, + monitoringInterval, + settings, + coroutineContext + ) + } + +suspend fun PermissionsBuilder.registerBluetoothPermissionIfNotRegistered( + bluetoothPermissionManagerBuilderBuilder: (PermissionContext) -> BaseBluetoothPermissionManagerBuilder = ::BluetoothPermissionManagerBuilder, + stateRepoBuilder: (BaseBluetoothPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = bluetoothPermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { _, coroutineContext -> + stateRepoBuilder(it, coroutineContext) + } +} diff --git a/bluetooth/build.gradle.kts b/bluetooth/build.gradle.kts index 837d47951..66d0a479a 100644 --- a/bluetooth/build.gradle.kts +++ b/bluetooth/build.gradle.kts @@ -18,7 +18,6 @@ kotlin { commonMain { dependencies { api(project(":bluetooth-permissions", "")) - implement(Dependencies.Stately.Concurrency) } } commonTest { diff --git a/bluetooth/src/commonMain/kotlin/Bluetooth.kt b/bluetooth/src/commonMain/kotlin/Bluetooth.kt index 04b7bf0a7..7b415715c 100644 --- a/bluetooth/src/commonMain/kotlin/Bluetooth.kt +++ b/bluetooth/src/commonMain/kotlin/Bluetooth.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.bluetooth -import co.touchlab.stately.collections.sharedMutableListOf import com.splendo.kaluga.base.flow.filterOnlyImportant import com.splendo.kaluga.base.singleThreadDispatcher import com.splendo.kaluga.bluetooth.device.BaseAdvertisementData @@ -50,9 +49,7 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.transformLatest import kotlin.coroutines.CoroutineContext import kotlin.jvm.JvmName -import kotlin.native.concurrent.SharedImmutable -@SharedImmutable // NOTE: replace with a limited parallelism dispatcher view when available private val defaultBluetoothDispatcher by lazy { singleThreadDispatcher("Bluetooth") } @@ -240,7 +237,7 @@ fun Flow.mtu() = state().map { state -> }.distinctUntilChanged() fun Flow.distance(environmentalFactor: Double = 2.0, averageOver: Int = 5): Flow { - val lastNResults = sharedMutableListOf() + val lastNResults = mutableListOf() return this.info().map { deviceInfo -> while (lastNResults.size >= averageOver) { lastNResults.removeAt(0) diff --git a/bluetooth/src/commonMain/kotlin/Characteristic.kt b/bluetooth/src/commonMain/kotlin/Characteristic.kt index 61cded0c8..d431fc716 100644 --- a/bluetooth/src/commonMain/kotlin/Characteristic.kt +++ b/bluetooth/src/commonMain/kotlin/Characteristic.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.bluetooth -import co.touchlab.stately.concurrency.AtomicBoolean import com.splendo.kaluga.bluetooth.device.DeviceAction import com.splendo.kaluga.bluetooth.device.DeviceConnectionManager import com.splendo.kaluga.logging.Logger @@ -38,10 +37,7 @@ open class Characteristic( ) { private val isBusy = MutableStateFlow(false) - private val _isNotifying = AtomicBoolean(false) - var isNotifying: Boolean - get() = _isNotifying.value - set(value) { _isNotifying.value = value } + var isNotifying: Boolean = false /** * Enables notification or indication for this [Characteristic]. diff --git a/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt b/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt index ccbe21dd7..bcea97016 100644 --- a/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt +++ b/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt @@ -17,9 +17,6 @@ package com.splendo.kaluga.bluetooth.device -import co.touchlab.stately.collections.sharedMutableMapOf -import co.touchlab.stately.concurrency.AtomicReference -import co.touchlab.stately.concurrency.value import com.splendo.kaluga.bluetooth.Characteristic import com.splendo.kaluga.bluetooth.Descriptor import com.splendo.kaluga.bluetooth.Service @@ -98,11 +95,8 @@ abstract class BaseDeviceConnectionManager( private val logTag = "Bluetooth Device ${deviceWrapper.identifier.stringValue}" private val logger = settings.logger - private val _currentAction = AtomicReference(null) - protected var currentAction: DeviceAction? - get() = _currentAction.get() - set(value) { _currentAction.set(value) } - protected val notifyingCharacteristics = sharedMutableMapOf() + protected var currentAction: DeviceAction? = null + protected val notifyingCharacteristics = mutableMapOf() private val eventChannel = Channel(UNLIMITED) override val events: Flow = eventChannel.receiveAsFlow() @@ -155,10 +149,10 @@ abstract class BaseDeviceConnectionManager( fun createService(wrapper: ServiceWrapper): Service = Service(wrapper, ::emitEvent, logTag, logger) override fun handleDisconnect(onDisconnect: (suspend () -> Unit)?) { - val currentAction = _currentAction + val currentAction = this.currentAction val notifyingCharacteristics = this.notifyingCharacteristics val clean = suspend { - currentAction.value = null + this.currentAction = null notifyingCharacteristics.clear() onDisconnect?.invoke() Unit @@ -182,7 +176,7 @@ abstract class BaseDeviceConnectionManager( open fun handleCurrentActionCompleted(succeeded: Boolean) { val currentAction = this.currentAction - _currentAction.value = null + this.currentAction = null if (currentAction != null) { if (succeeded) logger.info(logTag) { "Completed $currentAction successfully" } diff --git a/bluetooth/src/commonMain/kotlin/scanner/Scanner.kt b/bluetooth/src/commonMain/kotlin/scanner/Scanner.kt index 3ba6d4caa..3810d3db8 100644 --- a/bluetooth/src/commonMain/kotlin/scanner/Scanner.kt +++ b/bluetooth/src/commonMain/kotlin/scanner/Scanner.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.bluetooth.scanner -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.flow.filterOnlyImportant import com.splendo.kaluga.bluetooth.BluetoothMonitor import com.splendo.kaluga.bluetooth.UUID @@ -118,15 +117,8 @@ abstract class BaseScanner constructor( protected open val permissionsFlow: Flow>> get() = bluetoothPermissionRepo.filterOnlyImportant().map { listOf(it) } protected open val enabledFlow: Flow> get() = (bluetoothEnabledMonitor?.isEnabled ?: flowOf(false)).map { listOf(it) } - private val _monitoringPermissionsJob = AtomicReference(null) - private var monitoringPermissionsJob: Job? - get() = _monitoringPermissionsJob.get() - set(value) { _monitoringPermissionsJob.set(value) } - - private val _monitoringBluetoothEnabledJob = AtomicReference(null) - private var monitoringBluetoothEnabledJob: Job? - get() = _monitoringBluetoothEnabledJob.get() - set(value) { _monitoringBluetoothEnabledJob.set(value) } + private var monitoringPermissionsJob: Job? = null + private var monitoringBluetoothEnabledJob: Job? = null override fun startMonitoringPermissions() { logger.debug(LOG_TAG) { "Start monitoring permissions" } diff --git a/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt b/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt index 35e112e25..c057f30b8 100644 --- a/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt +++ b/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt @@ -11,7 +11,7 @@ import kotlin.coroutines.CoroutineContext actual class BluetoothBuilder( private val bundle: NSBundle = NSBundle.mainBundle, - private val permissionsBuilder: (CoroutineContext) -> Permissions = { context -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( PermissionsBuilder(bundle).apply { registerBluetoothPermission() diff --git a/bluetooth/src/iosMain/kotlin/BluetoothMonitor.kt b/bluetooth/src/iosMain/kotlin/BluetoothMonitor.kt index b1b05e58c..6b893a1b9 100644 --- a/bluetooth/src/iosMain/kotlin/BluetoothMonitor.kt +++ b/bluetooth/src/iosMain/kotlin/BluetoothMonitor.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.bluetooth -import co.touchlab.stately.concurrency.AtomicReference -import co.touchlab.stately.concurrency.value import com.splendo.kaluga.base.monitor.DefaultServiceMonitor import com.splendo.kaluga.base.monitor.ServiceMonitor import platform.CoreBluetooth.CBCentralManager @@ -47,14 +45,14 @@ class DefaultBluetoothMonitor internal constructor( } } - private val centralManager = AtomicReference(null) + private var centralManager: CBCentralManager? = null private val centralManagerDelegate = CentralManagerDelegate(::updateState) override val isServiceEnabled: Boolean get() = initializeCentralManagerIfNotInitialized().state == CBManagerStatePoweredOn - private fun initializeCentralManagerIfNotInitialized(): CBCentralManager = centralManager.value ?: centralManagerBuilder().apply { - centralManager.set(this) + private fun initializeCentralManagerIfNotInitialized(): CBCentralManager = centralManager ?: centralManagerBuilder().apply { + centralManager = this } override fun monitoringDidStart() { @@ -63,6 +61,6 @@ class DefaultBluetoothMonitor internal constructor( } override fun monitoringDidStop() { - centralManager.value?.delegate = null + centralManager?.delegate = null } } diff --git a/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt b/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt index e6c65f0b7..c2db6d850 100644 --- a/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt +++ b/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.bluetooth.device -import co.touchlab.stately.collections.sharedMutableListOf import com.splendo.kaluga.base.toNSData import com.splendo.kaluga.base.typedList import com.splendo.kaluga.bluetooth.DefaultServiceWrapper @@ -25,6 +24,8 @@ import com.splendo.kaluga.bluetooth.uuidString import com.splendo.kaluga.logging.debug import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import platform.CoreBluetooth.CBCentralManager import platform.CoreBluetooth.CBCharacteristic import platform.CoreBluetooth.CBCharacteristicWriteWithResponse @@ -69,8 +70,9 @@ internal actual class DefaultDeviceConnectionManager( private const val TAG = "IOS Bluetooth DeviceConnectionManager" } - private val discoveringServices = sharedMutableListOf() - private val discoveringCharacteristics = sharedMutableListOf() + private val discoveringMutext = Mutex() + private val discoveringServices = mutableListOf() + private val discoveringCharacteristics = mutableListOf() @Suppress("CONFLICTING_OVERLOADS") private val peripheralDelegate = object : NSObject(), CBPeripheralDelegateProtocol { @@ -136,9 +138,11 @@ internal actual class DefaultDeviceConnectionManager( } override suspend fun discoverServices() { - discoveringServices.clear() - discoveringCharacteristics.clear() - peripheral.discoverServices(null) + discoveringMutext.withLock { + discoveringServices.clear() + discoveringCharacteristics.clear() + peripheral.discoverServices(null) + } } override suspend fun disconnect() { @@ -200,31 +204,43 @@ internal actual class DefaultDeviceConnectionManager( handleUpdatedDescriptor(descriptor.UUID, succeeded = error == null) } - private fun didDiscoverServices() { - discoveringServices.addAll( - peripheral.services?.typedList()?.map { - peripheral.discoverCharacteristics(emptyList(), it) - it.UUID - } ?: emptyList() - ) + private suspend fun didDiscoverServices() { + launch { + discoveringMutext.withLock { + discoveringServices.addAll( + peripheral.services?.typedList()?.map { + peripheral.discoverCharacteristics(emptyList(), it) + it.UUID + } ?: emptyList() + ) + } - checkScanComplete() + checkScanComplete() + } } private fun didDiscoverCharacteristic(forService: CBService) { - discoveringServices.remove(forService.UUID) - discoveringCharacteristics.addAll( - forService.characteristics?.typedList()?.map { - peripheral.discoverDescriptorsForCharacteristic(it) - it.UUID - } ?: emptyList() - ) - checkScanComplete() - } - - private fun didDiscoverDescriptors(forCharacteristic: CBCharacteristic) { - discoveringCharacteristics.remove(forCharacteristic.UUID) - checkScanComplete() + launch { + discoveringMutext.withLock { + discoveringServices.remove(forService.UUID) + discoveringCharacteristics.addAll( + forService.characteristics?.typedList()?.map { + peripheral.discoverDescriptorsForCharacteristic(it) + it.UUID + } ?: emptyList() + ) + } + checkScanComplete() + } + } + + private suspend fun didDiscoverDescriptors(forCharacteristic: CBCharacteristic) { + launch { + discoveringMutext.withLock { + discoveringCharacteristics.remove(forCharacteristic.UUID) + } + checkScanComplete() + } } private fun checkScanComplete() { diff --git a/bluetooth/src/iosMain/kotlin/scanner/Scanner.kt b/bluetooth/src/iosMain/kotlin/scanner/Scanner.kt index 7f44201f1..b1db0b2d3 100644 --- a/bluetooth/src/iosMain/kotlin/scanner/Scanner.kt +++ b/bluetooth/src/iosMain/kotlin/scanner/Scanner.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.bluetooth.scanner -import co.touchlab.stately.collections.sharedMutableListOf -import co.touchlab.stately.collections.sharedMutableSetOf import com.splendo.kaluga.base.typedMap import com.splendo.kaluga.base.utils.EmptyCompletableDeferred import com.splendo.kaluga.base.utils.complete @@ -29,6 +27,8 @@ import com.splendo.kaluga.bluetooth.device.DefaultCBPeripheralWrapper import com.splendo.kaluga.bluetooth.device.DefaultDeviceConnectionManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import platform.CoreBluetooth.CBCentralManager import platform.CoreBluetooth.CBCentralManagerDelegateProtocol import platform.CoreBluetooth.CBCentralManagerOptionShowPowerAlertKey @@ -91,23 +91,27 @@ actual class DefaultScanner internal constructor( private val scanQueue = dispatch_queue_create("ScannerScanning", null) private val pairedDevicesQueue = dispatch_queue_create("ScannerPairedDevices", null) override val isSupported: Boolean = true - private val centralManagers = sharedMutableListOf() - private val discoveringDelegates = sharedMutableListOf() - private val activeDelegates = sharedMutableSetOf() + private val centralManagersLock = Mutex() + private val centralManagers = mutableListOf() + private val discoveringDelegates = mutableListOf() + private val activeDelegates = mutableSetOf() override val bluetoothEnabledMonitor: BluetoothMonitor = BluetoothMonitor.Builder { CBCentralManager(null, enabledQueue, emptyMap()) }.create() private suspend fun scan(filter: UUID? = null) { - val centralManager = CBCentralManager(null, scanQueue) - centralManagers.add(centralManager) - val awaitPoweredOn = EmptyCompletableDeferred() - val delegate = PoweredOnCBCentralManagerDelegate(this, awaitPoweredOn) - discoveringDelegates.add(delegate) - centralManager.delegate = delegate - awaitPoweredOn.await() - centralManager.scanForPeripheralsWithServices( - filter?.let { listOf(filter) }, - scanSettings.parse() - ) + centralManagersLock.withLock { + val centralManager = CBCentralManager(null, scanQueue) + centralManagers.add(centralManager) + + val awaitPoweredOn = EmptyCompletableDeferred() + val delegate = PoweredOnCBCentralManagerDelegate(this, awaitPoweredOn) + discoveringDelegates.add(delegate) + centralManager.delegate = delegate + awaitPoweredOn.await() + centralManager.scanForPeripheralsWithServices( + filter?.let { listOf(filter) }, + scanSettings.parse() + ) + } } override suspend fun didStartScanning(filter: Set) { @@ -119,13 +123,15 @@ actual class DefaultScanner internal constructor( } override suspend fun didStopScanning() { - centralManagers.forEach { centralManager -> - if (centralManager.state == CBCentralManagerStatePoweredOn) { - centralManager.stopScan() + centralManagersLock.withLock { + centralManagers.forEach { centralManager -> + if (centralManager.state == CBCentralManagerStatePoweredOn) { + centralManager.stopScan() + } } + discoveringDelegates.clear() + centralManagers.clear() } - discoveringDelegates.clear() - centralManagers.clear() } override fun generateEnableSensorsActions(): List { diff --git a/calendar-permissions/src/commonMain/kotlin/Permission.kt b/calendar-permissions/src/commonMain/kotlin/Permission.kt index 8e4e38e75..533c053b4 100644 --- a/calendar-permissions/src/commonMain/kotlin/Permission.kt +++ b/calendar-permissions/src/commonMain/kotlin/Permission.kt @@ -33,7 +33,7 @@ data class CalendarPermission(val allowWrite: Boolean = false) : Permission() { override val name: String = "Calendar - ${if (allowWrite) "ReadWrite" else "ReadOnly"}" } -fun PermissionsBuilder.registerCalendarPermission( +suspend fun PermissionsBuilder.registerCalendarPermission( calendarPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCalendarPermissionManagerBuilder = ::CalendarPermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -42,7 +42,7 @@ fun PermissionsBuilder.registerCalendarPermission( CalendarPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) } -fun PermissionsBuilder.registerCalendarPermission( +suspend fun PermissionsBuilder.registerCalendarPermission( calendarPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCalendarPermissionManagerBuilder = ::CalendarPermissionManagerBuilder, calendarPermissionStateRepoBuilder: (CalendarPermission, BaseCalendarPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = calendarPermissionManagerBuilderBuilder(context).also { @@ -51,3 +51,22 @@ fun PermissionsBuilder.registerCalendarPermission( calendarPermissionStateRepoBuilder(permission, it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerCalendarPermissionIfNotRegistered( + calendarPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCalendarPermissionManagerBuilder = ::CalendarPermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerCalendarPermissionIfNotRegistered(calendarPermissionManagerBuilderBuilder) { permission, builder, coroutineContext -> + CalendarPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) + } + +suspend fun PermissionsBuilder.registerCalendarPermissionIfNotRegistered( + calendarPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCalendarPermissionManagerBuilder = ::CalendarPermissionManagerBuilder, + calendarPermissionStateRepoBuilder: (CalendarPermission, BaseCalendarPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = calendarPermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { permission, coroutineContext -> + calendarPermissionStateRepoBuilder(permission, it, coroutineContext) + } +} diff --git a/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt b/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt index f3cd86fa4..5c109a805 100644 --- a/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt +++ b/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.permissions.calendar -import co.touchlab.stately.freeze import com.splendo.kaluga.logging.error import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.BasePermissionManager @@ -62,14 +61,12 @@ actual class DefaultCalendarPermissionManager( if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSCalendarsUsageDescription).isEmpty()) { permissionHandler.requestAuthorizationStatus(timerHelper, CoroutineScope(coroutineContext)) { val deferred = CompletableDeferred() - val callback = { success: Boolean, error: NSError? -> + eventStore.requestAccessToEntityType( + EKEntityType.EKEntityTypeEvent + ) { success, error -> error?.let { deferred.completeExceptionally(Throwable(it.localizedDescription)) } ?: deferred.complete(success) Unit - }.freeze() - eventStore.requestAccessToEntityType( - EKEntityType.EKEntityTypeEvent, - callback - ) + } try { if (deferred.await()) diff --git a/camera-permissions/src/commonMain/kotlin/Permission.kt b/camera-permissions/src/commonMain/kotlin/Permission.kt index af1fd2f1b..0e246df2e 100644 --- a/camera-permissions/src/commonMain/kotlin/Permission.kt +++ b/camera-permissions/src/commonMain/kotlin/Permission.kt @@ -32,7 +32,7 @@ object CameraPermission : Permission() { override val name: String = "Camera" } -fun PermissionsBuilder.registerCameraPermission( +suspend fun PermissionsBuilder.registerCameraPermission( cameraPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCameraPermissionManagerBuilder = ::CameraPermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -41,7 +41,7 @@ fun PermissionsBuilder.registerCameraPermission( CameraPermissionStateRepo(builder, monitoringInterval, settings, coroutineContext) } -fun PermissionsBuilder.registerCameraPermission( +suspend fun PermissionsBuilder.registerCameraPermission( cameraPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCameraPermissionManagerBuilder = ::CameraPermissionManagerBuilder, cameraPermissionStateRepoBuilder: (BaseCameraPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = cameraPermissionManagerBuilderBuilder(context).also { @@ -50,3 +50,22 @@ fun PermissionsBuilder.registerCameraPermission( cameraPermissionStateRepoBuilder(it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerCameraPermissionIfNotRegistered( + cameraPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCameraPermissionManagerBuilder = ::CameraPermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerCameraPermissionIfNotRegistered(cameraPermissionManagerBuilderBuilder) { builder, coroutineContext -> + CameraPermissionStateRepo(builder, monitoringInterval, settings, coroutineContext) + } + +suspend fun PermissionsBuilder.registerCameraPermissionIfNotRegistered( + cameraPermissionManagerBuilderBuilder: (PermissionContext) -> BaseCameraPermissionManagerBuilder = ::CameraPermissionManagerBuilder, + cameraPermissionStateRepoBuilder: (BaseCameraPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = cameraPermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { _, coroutineContext -> + cameraPermissionStateRepoBuilder(it, coroutineContext) + } +} diff --git a/contacts-permissions/src/commonMain/kotlin/Permission.kt b/contacts-permissions/src/commonMain/kotlin/Permission.kt index b1d0f8f17..a03679546 100644 --- a/contacts-permissions/src/commonMain/kotlin/Permission.kt +++ b/contacts-permissions/src/commonMain/kotlin/Permission.kt @@ -33,7 +33,7 @@ data class ContactsPermission(val allowWrite: Boolean = false) : Permission() { override val name: String = "Contacts - ${if (allowWrite) "ReadWrite" else "ReadOnly"}" } -fun PermissionsBuilder.registerContactsPermission( +suspend fun PermissionsBuilder.registerContactsPermission( contactsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseContactsPermissionManagerBuilder = ::ContactsPermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -42,7 +42,7 @@ fun PermissionsBuilder.registerContactsPermission( ContactsPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) } -fun PermissionsBuilder.registerContactsPermission( +suspend fun PermissionsBuilder.registerContactsPermission( contactsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseContactsPermissionManagerBuilder = ::ContactsPermissionManagerBuilder, contactsPermissionStateRepoBuilder: (ContactsPermission, BaseContactsPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = contactsPermissionManagerBuilderBuilder(context).also { @@ -51,3 +51,22 @@ fun PermissionsBuilder.registerContactsPermission( contactsPermissionStateRepoBuilder(permission, it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerContactsPermissionIfNotRegistered( + contactsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseContactsPermissionManagerBuilder = ::ContactsPermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerContactsPermissionIfNotRegistered(contactsPermissionManagerBuilderBuilder) { permission, builder, coroutineContext -> + ContactsPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) + } + +suspend fun PermissionsBuilder.registerContactsPermissionIfNotRegistered( + contactsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseContactsPermissionManagerBuilder = ::ContactsPermissionManagerBuilder, + contactsPermissionStateRepoBuilder: (ContactsPermission, BaseContactsPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = contactsPermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { permission, coroutineContext -> + contactsPermissionStateRepoBuilder(permission, it, coroutineContext) + } +} diff --git a/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt b/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt index 90530a3b1..df9135897 100644 --- a/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt +++ b/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.permissions.contacts -import co.touchlab.stately.freeze import com.splendo.kaluga.logging.error import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.BasePermissionManager @@ -62,14 +61,12 @@ actual class DefaultContactsPermissionManager( if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSContactsUsageDescription).isEmpty()) { permissionHandler.requestAuthorizationStatus(timerHelper, CoroutineScope(coroutineContext)) { val deferred = CompletableDeferred() - val callback = { success: Boolean, error: NSError? -> + contactStore.requestAccessForEntityType( + CNEntityType.CNEntityTypeContacts + ) { success, error -> error?.let { deferred.completeExceptionally(Throwable(it.localizedDescription)) } ?: deferred.complete(success) Unit - }.freeze() - contactStore.requestAccessForEntityType( - CNEntityType.CNEntityTypeContacts, - callback - ) + } try { if (deferred.await()) diff --git a/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt b/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt index c240b716e..2f0b3cd35 100644 --- a/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt +++ b/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt @@ -18,8 +18,6 @@ Copyright 2020 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.datetimepicker -import co.touchlab.stately.concurrency.Lock -import co.touchlab.stately.concurrency.withLock import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker import com.splendo.kaluga.base.utils.DefaultKalugaDate import com.splendo.kaluga.base.utils.KalugaDate @@ -27,6 +25,8 @@ import com.splendo.kaluga.base.utils.Locale import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlin.coroutines.resume data class DateTimePicker( @@ -108,7 +108,7 @@ abstract class BaseDateTimePickerPresenter(private val dateTimePicker: DateTimeP private var locale: Locale = defaultLocale private var selectedDate: KalugaDate = DefaultKalugaDate.epoch() private var type: DateTimePicker.Type = DateTimePicker.Type.TimeType - internal val lock = Lock() + internal val lock = Mutex() /** * Sets the [message] displayed in the DateTimePicker @@ -221,7 +221,7 @@ expect class DateTimePickerPresenter : BaseDateTimePickerPresenter { * @param initialize The block to construct an Alert * @return The built alert interface object */ -fun BaseDateTimePickerPresenter.Builder.buildDatePicker( +suspend fun BaseDateTimePickerPresenter.Builder.buildDatePicker( coroutineScope: CoroutineScope, earliestDate: KalugaDate? = null, latestDate: KalugaDate? = null, @@ -240,7 +240,7 @@ fun BaseDateTimePickerPresenter.Builder.buildDatePicker( * @param initialize The block to construct an Alert * @return The built alert interface object */ -fun BaseDateTimePickerPresenter.Builder.buildTimePicker(coroutineScope: CoroutineScope, initialize: BaseDateTimePickerPresenter.Builder.() -> Unit): BaseDateTimePickerPresenter = lock.withLock { +suspend fun BaseDateTimePickerPresenter.Builder.buildTimePicker(coroutineScope: CoroutineScope, initialize: BaseDateTimePickerPresenter.Builder.() -> Unit): BaseDateTimePickerPresenter = lock.withLock { reset() setType(DateTimePicker.Type.TimeType) initialize() diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt index 7cfc52e20..791f0b0dc 100644 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt +++ b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt @@ -24,7 +24,6 @@ import com.splendo.kaluga.bluetooth.scanner.BaseScanner import kotlinx.coroutines.MainScope import permissions.KNPermissionsFramework -@kotlin.native.concurrent.ThreadLocal object KNBluetoothFramework { val mainScope = MainScope() val bluetooth = BluetoothBuilder().create( diff --git a/gradle.properties b/gradle.properties index e9c1f92c2..c02400535 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,6 @@ org.gradle.jvmargs=-Xmx3048M -Dkotlin.daemon.jvm.options\="-Xmx3048M" #MPP kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.native.enableDependencyPropagation=false -# TODO: Disable -kotlin.native.binary.memoryModel=strict kotlin.mpp.enableCInteropCommonization=true ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## diff --git a/hud/src/commonMain/kotlin/HUD.kt b/hud/src/commonMain/kotlin/HUD.kt index c8910659c..60c1e0bd0 100644 --- a/hud/src/commonMain/kotlin/HUD.kt +++ b/hud/src/commonMain/kotlin/HUD.kt @@ -18,13 +18,13 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.hud -import co.touchlab.stately.concurrency.Lock -import co.touchlab.stately.concurrency.withLock import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock /** * Style of the Loading Indicator @@ -46,7 +46,7 @@ abstract class BaseHUD(coroutineScope: CoroutineScope) : CoroutineScope by corou */ abstract class Builder : LifecycleSubscribableMarker { - internal val lock = Lock() + internal val lock = Mutex() /** The style of the loading indicator */ internal var style: HUDStyle = HUDStyle.SYSTEM @@ -142,7 +142,7 @@ suspend fun BaseHUD.presentDuring(animated: Boolean = true, block: suspend B * @param coroutineScope The [CoroutineScope] managing the HUD lifecycle. * @param initialize Method for initializing the [HUD.Builder] */ -fun BaseHUD.Builder.build(coroutineScope: CoroutineScope, initialize: BaseHUD.Builder.() -> Unit = { }): BaseHUD = lock.withLock { +suspend fun BaseHUD.Builder.build(coroutineScope: CoroutineScope, initialize: BaseHUD.Builder.() -> Unit = { }): BaseHUD = lock.withLock { clear() initialize() return create(HudConfig(style, title), coroutineScope) diff --git a/hud/src/iosTest/kotlin/IOSHUDTests.kt b/hud/src/iosTest/kotlin/IOSHUDTests.kt index 7f6a275c0..dec6a3d00 100644 --- a/hud/src/iosTest/kotlin/IOSHUDTests.kt +++ b/hud/src/iosTest/kotlin/IOSHUDTests.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.runBlocking import platform.UIKit.UIViewController -import kotlin.native.concurrent.ensureNeverFrozen import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertNull @@ -42,10 +41,6 @@ class IOSHUDTests : HUDTests() { class HUDViewController : UIViewController(null, null) { - init { - this.ensureNeverFrozen() - } - var mockPresentingHUD: MockPresentingHUD? = null override fun presentedViewController(): UIViewController? { @@ -69,10 +64,6 @@ class IOSHUDTests : HUDTests() { class MockPresentingHUD(val hudViewController: UIViewController) : UIViewController(null, null) { - init { - this.ensureNeverFrozen() - } - var parent: UIViewController? = null override fun presentingViewController(): UIViewController? { diff --git a/kaluga-library-components/src/main/kotlin/Dependencies.kt b/kaluga-library-components/src/main/kotlin/Dependencies.kt index 9548006d6..8b93445eb 100644 --- a/kaluga-library-components/src/main/kotlin/Dependencies.kt +++ b/kaluga-library-components/src/main/kotlin/Dependencies.kt @@ -51,7 +51,7 @@ object Dependencies { object KotlinX { object Coroutines { - private const val version = "1.6.3-native-mt" + private const val version = "1.6.4" private const val group = "org.jetbrains.kotlinx" val Android = Dependency(group, "kotlinx-coroutines-android", version) val Core = Dependency(group, "kotlinx-coroutines-core", version) @@ -160,16 +160,6 @@ object Dependencies { val Napier = Dependency("io.github.aakira", "napier", "2.6.1") - object Stately { - private const val group = "co.touchlab" - private const val version = "1.2.3" - private const val isolateVersion = "1.2.3" - val Common = Dependency(group, "stately-common", version) - val Concurrency = Dependency(group, "stately-concurrency", version) - val Isolate = Dependency(group, "stately-isolate", isolateVersion) - val IsoCollections = Dependency(group, "stately-iso-collections", isolateVersion) - } - val JUnit = Dependency("junit", "junit", "4.13.2") object Mockito { diff --git a/location-permissions/src/commonMain/kotlin/Permission.kt b/location-permissions/src/commonMain/kotlin/Permission.kt index e1d48c7c8..2865ff967 100644 --- a/location-permissions/src/commonMain/kotlin/Permission.kt +++ b/location-permissions/src/commonMain/kotlin/Permission.kt @@ -35,7 +35,7 @@ data class LocationPermission(val background: Boolean = false, val precise: Bool override val name: String = listOfNotNull(if (background) "Background" else null, "Location", "-", if (precise) "Precise" else "Coarse").joinToString(" ") } -fun PermissionsBuilder.registerLocationPermission( +suspend fun PermissionsBuilder.registerLocationPermission( locationPermissionManagerBuilderBuilder: (PermissionContext) -> BaseLocationPermissionManagerBuilder = ::LocationPermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -44,7 +44,7 @@ fun PermissionsBuilder.registerLocationPermission( LocationPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) } -fun PermissionsBuilder.registerLocationPermission( +suspend fun PermissionsBuilder.registerLocationPermission( locationPermissionManagerBuilderBuilder: (PermissionContext) -> BaseLocationPermissionManagerBuilder = ::LocationPermissionManagerBuilder, locationPermissionStateRepoBuilder: (LocationPermission, BaseLocationPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = locationPermissionManagerBuilderBuilder(context).also { @@ -53,3 +53,22 @@ fun PermissionsBuilder.registerLocationPermission( locationPermissionStateRepoBuilder(permission, it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerLocationPermissionIfNotRegistered( + locationPermissionManagerBuilderBuilder: (PermissionContext) -> BaseLocationPermissionManagerBuilder = ::LocationPermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerLocationPermissionIfNotRegistered(locationPermissionManagerBuilderBuilder) { permission, builder, coroutineContext -> + LocationPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) + } + +suspend fun PermissionsBuilder.registerLocationPermissionIfNotRegistered( + locationPermissionManagerBuilderBuilder: (PermissionContext) -> BaseLocationPermissionManagerBuilder = ::LocationPermissionManagerBuilder, + locationPermissionStateRepoBuilder: (LocationPermission, BaseLocationPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = locationPermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { permission, coroutineContext -> + locationPermissionStateRepoBuilder(permission, it, coroutineContext) + } +} diff --git a/location-permissions/src/iosMain/kotlin/MainCLLocationManagerAccessor.kt b/location-permissions/src/iosMain/kotlin/MainCLLocationManagerAccessor.kt index 506efe09b..84f5e23ed 100644 --- a/location-permissions/src/iosMain/kotlin/MainCLLocationManagerAccessor.kt +++ b/location-permissions/src/iosMain/kotlin/MainCLLocationManagerAccessor.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.permissions.location -import co.touchlab.stately.concurrency.AtomicReference -import co.touchlab.stately.concurrency.value import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import platform.CoreLocation.CLLocationManager @@ -29,10 +27,10 @@ import platform.CoreLocation.CLLocationManager */ class MainCLLocationManagerAccessor(private val onInit: CLLocationManager.() -> Unit) { - private val locationManager = AtomicReference(null) + private var locationManager: CLLocationManager? = null - private fun createLocationManagerIfNotCreated(): CLLocationManager = locationManager.value ?: CLLocationManager().apply { - locationManager.set(this) + private fun createLocationManagerIfNotCreated(): CLLocationManager = locationManager ?: CLLocationManager().apply { + locationManager = this onInit() } diff --git a/location/src/androidLibMain/kotlin/location/LocationManager.kt b/location/src/androidLibMain/kotlin/location/LocationManager.kt index 0245b8c3f..e10a08597 100644 --- a/location/src/androidLibMain/kotlin/location/LocationManager.kt +++ b/location/src/androidLibMain/kotlin/location/LocationManager.kt @@ -20,7 +20,6 @@ package com.splendo.kaluga.location import android.content.Context import android.content.Intent import android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.ApplicationHolder import com.splendo.kaluga.base.monitor.EnableServiceActivity import com.splendo.kaluga.permissions.base.PermissionContext @@ -31,6 +30,8 @@ import com.splendo.kaluga.permissions.location.registerLocationPermission import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlin.coroutines.CoroutineContext actual class DefaultLocationManager( @@ -56,18 +57,19 @@ actual class DefaultLocationManager( } override val locationMonitor: LocationMonitor = LocationMonitor.Builder(context, locationManager).create() - private val monitoringLocationJob: AtomicReference = AtomicReference(null) + private val monitoringMutex = Mutex() + private var monitoringLocationJob: Job? = null override suspend fun requestEnableLocation() { EnableServiceActivity.showEnableServiceActivity(context, hashCode().toString(), Intent(ACTION_LOCATION_SOURCE_SETTINGS)).await() } override suspend fun startMonitoringLocation() { - if (monitoringLocationJob.get() != null) return // optimization to skip making a job + monitoringMutex.withLock { + if (monitoringLocationJob != null) return // optimization to skip making a job - val job = Job(this.coroutineContext[Job]) - - if (monitoringLocationJob.compareAndSet(null, job)) { + val job = Job(this.coroutineContext[Job]) + monitoringLocationJob = job locationProvider.startMonitoringLocation(locationPermission) launch(job) { locationProvider.location(locationPermission).collect { @@ -78,8 +80,9 @@ actual class DefaultLocationManager( } override suspend fun stopMonitoringLocation() { - monitoringLocationJob.get()?.let { - if (monitoringLocationJob.compareAndSet(it, null)) { + monitoringMutex.withLock { + monitoringLocationJob?.let { + monitoringLocationJob = it locationProvider.stopMonitoringLocation(locationPermission) it.cancel() } diff --git a/location/src/commonMain/kotlin/location/LocationManager.kt b/location/src/commonMain/kotlin/location/LocationManager.kt index 8d46c6d30..460f72b3e 100644 --- a/location/src/commonMain/kotlin/location/LocationManager.kt +++ b/location/src/commonMain/kotlin/location/LocationManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.location -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.flow.filterOnlyImportant import com.splendo.kaluga.logging.Logger import com.splendo.kaluga.logging.RestrictedLogLevel @@ -100,14 +99,8 @@ abstract class BaseLocationManager( abstract val locationMonitor: LocationMonitor - private val _monitoringPermissionsJob: AtomicReference = AtomicReference(null) - private var monitoringPermissionsJob: Job? - get() = _monitoringPermissionsJob.get() - set(value) { _monitoringPermissionsJob.set(value) } - private val _monitoringLocationEnabledJob: AtomicReference = AtomicReference(null) - private var monitoringLocationEnabledJob: Job? - get() = _monitoringLocationEnabledJob.get() - set(value) { _monitoringLocationEnabledJob.set(value) } + private var monitoringPermissionsJob: Job? = null + private var monitoringLocationEnabledJob: Job? = null override fun startMonitoringPermissions() { logger.debug(LOG_TAG) { "Start monitoring permission" } diff --git a/location/src/commonMain/kotlin/location/LocationStateRepo.kt b/location/src/commonMain/kotlin/location/LocationStateRepo.kt index b88b5f46e..bbdf047d9 100644 --- a/location/src/commonMain/kotlin/location/LocationStateRepo.kt +++ b/location/src/commonMain/kotlin/location/LocationStateRepo.kt @@ -32,11 +32,9 @@ import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlin.coroutines.CoroutineContext -import kotlin.native.concurrent.SharedImmutable typealias LocationStateFlowRepo = StateRepo> -@SharedImmutable // NOTE: replace with a limited parallelism dispatcher view when available private val defaultLocationDispatcher by lazy { singleThreadDispatcher("Location") } diff --git a/location/src/iosMain/kotlin/location/LocationManager.kt b/location/src/iosMain/kotlin/location/LocationManager.kt index cf632c054..079454385 100644 --- a/location/src/iosMain/kotlin/location/LocationManager.kt +++ b/location/src/iosMain/kotlin/location/LocationManager.kt @@ -17,14 +17,12 @@ package com.splendo.kaluga.location -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder import com.splendo.kaluga.permissions.location.LocationPermission import com.splendo.kaluga.permissions.location.MainCLLocationManagerAccessor import com.splendo.kaluga.permissions.location.registerLocationPermission import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import platform.CoreLocation.CLLocation @@ -87,11 +85,6 @@ actual class DefaultLocationManager( locationUpdateDelegate = Delegate(sharedLocations) } - private var _isMonitoringLocationJob = AtomicReference(null) - var isMonitoringLocationJob: Job? - get() = _isMonitoringLocationJob.get() - set(value) { _isMonitoringLocationJob.set(value) } - override suspend fun requestEnableLocation() { // No access to UIApplication.openSettingsURLString // We have to fallback to alert then? @@ -106,9 +99,6 @@ actual class DefaultLocationManager( } override suspend fun stopMonitoringLocation() { - isMonitoringLocationJob?.cancel() - isMonitoringLocationJob = null - launch { locationManager.updateLocationManager { stopUpdatingLocation() diff --git a/logging/build.gradle.kts b/logging/build.gradle.kts index b06cc6050..81b3548ed 100644 --- a/logging/build.gradle.kts +++ b/logging/build.gradle.kts @@ -13,13 +13,10 @@ kotlin { commonMain { dependencies { implement(Dependencies.Napier) - implement(Dependencies.Stately.Concurrency) } } commonTest { dependencies { - implement(Dependencies.Stately.Isolate) - implement(Dependencies.Stately.IsoCollections) api(project(":test-utils-base", "")) } } diff --git a/logging/src/commonMain/kotlin/NapierLogger.kt b/logging/src/commonMain/kotlin/NapierLogger.kt index 9590c9432..21914d717 100644 --- a/logging/src/commonMain/kotlin/NapierLogger.kt +++ b/logging/src/commonMain/kotlin/NapierLogger.kt @@ -16,12 +16,10 @@ */ package com.splendo.kaluga.logging -import kotlin.native.concurrent.SharedImmutable import com.splendo.kaluga.logging.LogLevel as KalugaLogLevel import io.github.aakira.napier.Antilog as NapierLog import io.github.aakira.napier.LogLevel as NapierLogLevel -@SharedImmutable val logLevel = arrayOf( NapierLogLevel.VERBOSE, NapierLogLevel.DEBUG, diff --git a/logging/src/commonTest/kotlin/LoggerMock.kt b/logging/src/commonTest/kotlin/LoggerMock.kt index 1cae1a5ec..016c59b97 100644 --- a/logging/src/commonTest/kotlin/LoggerMock.kt +++ b/logging/src/commonTest/kotlin/LoggerMock.kt @@ -18,26 +18,39 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.logging -import co.touchlab.stately.collections.IsoMutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock -class LoggerMock : Logger { +class LoggerMock(private val coroutineScope: CoroutineScope) : Logger { - val throwableList = IsoMutableList() - val messageList = IsoMutableList() - val tagList = IsoMutableList() - val levelList = IsoMutableList() + private val mutex = Mutex() + val throwableList = mutableListOf() + val messageList = mutableListOf() + val tagList = mutableListOf() + val levelList = mutableListOf() fun clear() { - levelList.clear() - messageList.clear() - tagList.clear() - throwableList.clear() + coroutineScope.launch { + mutex.withLock { + levelList.clear() + messageList.clear() + tagList.clear() + throwableList.clear() + } + } } override fun log(level: LogLevel, tag: String?, throwable: Throwable?, message: (() -> String)?) { - levelList.add(level) - tagList.add(tag) - throwableList.add(throwable) - messageList.add(message?.invoke()) + + coroutineScope.launch { + mutex.withLock { + levelList.add(level) + tagList.add(tag) + throwableList.add(throwable) + messageList.add(message?.invoke()) + } + } } } diff --git a/logging/src/iosMain/kotlin/log.kt b/logging/src/iosMain/kotlin/log.kt index d7707a47c..e5f2d5a32 100644 --- a/logging/src/iosMain/kotlin/log.kt +++ b/logging/src/iosMain/kotlin/log.kt @@ -17,15 +17,8 @@ package com.splendo.kaluga.logging -import co.touchlab.stately.concurrency.value import io.github.aakira.napier.DebugAntilog -import kotlin.native.concurrent.SharedImmutable -@SharedImmutable actual val defaultLogger: Logger = NapierLogger(DebugAntilog()) -@SharedImmutable -private val _logger = co.touchlab.stately.concurrency.AtomicReference(defaultLogger) -actual var logger - get() = _logger.value - set(value) { _logger.value = value } +actual var logger = defaultLogger diff --git a/microphone-permissions/src/commonMain/kotlin/Permission.kt b/microphone-permissions/src/commonMain/kotlin/Permission.kt index a681fd067..9c0418e99 100644 --- a/microphone-permissions/src/commonMain/kotlin/Permission.kt +++ b/microphone-permissions/src/commonMain/kotlin/Permission.kt @@ -32,7 +32,7 @@ object MicrophonePermission : Permission() { override val name: String = "Microphone" } -fun PermissionsBuilder.registerMicrophonePermission( +suspend fun PermissionsBuilder.registerMicrophonePermission( microphonePermissionManagerBuilderBuilder: (PermissionContext) -> BaseMicrophonePermissionManagerBuilder = ::MicrophonePermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -41,7 +41,7 @@ fun PermissionsBuilder.registerMicrophonePermission( MicrophonePermissionStateRepo(builder, monitoringInterval, settings, coroutineContext) } -fun PermissionsBuilder.registerMicrophonePermission( +suspend fun PermissionsBuilder.registerMicrophonePermission( microphonePermissionManagerBuilderBuilder: (PermissionContext) -> BaseMicrophonePermissionManagerBuilder = ::MicrophonePermissionManagerBuilder, microphonePermissionStateRepoBuilder: (BaseMicrophonePermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = microphonePermissionManagerBuilderBuilder(context).also { @@ -50,3 +50,22 @@ fun PermissionsBuilder.registerMicrophonePermission( microphonePermissionStateRepoBuilder(it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerMicrophonePermissionIfNotRegistered( + microphonePermissionManagerBuilderBuilder: (PermissionContext) -> BaseMicrophonePermissionManagerBuilder = ::MicrophonePermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerMicrophonePermissionIfNotRegistered(microphonePermissionManagerBuilderBuilder) { builder, coroutineContext -> + MicrophonePermissionStateRepo(builder, monitoringInterval, settings, coroutineContext) + } + +suspend fun PermissionsBuilder.registerMicrophonePermissionIfNotRegistered( + microphonePermissionManagerBuilderBuilder: (PermissionContext) -> BaseMicrophonePermissionManagerBuilder = ::MicrophonePermissionManagerBuilder, + microphonePermissionStateRepoBuilder: (BaseMicrophonePermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = microphonePermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { _, coroutineContext -> + microphonePermissionStateRepoBuilder(it, coroutineContext) + } +} diff --git a/notifications-permissions/src/commonMain/kotlin/Permission.kt b/notifications-permissions/src/commonMain/kotlin/Permission.kt index 1858b7e23..f37efc3a1 100644 --- a/notifications-permissions/src/commonMain/kotlin/Permission.kt +++ b/notifications-permissions/src/commonMain/kotlin/Permission.kt @@ -33,7 +33,7 @@ data class NotificationsPermission(val options: NotificationOptions? = null) : P override val name: String = "Notifications - ${options?.toString().orEmpty().ifEmpty { "Without Options" }}" } -fun PermissionsBuilder.registerNotificationsPermission( +suspend fun PermissionsBuilder.registerNotificationsPermission( notificationsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseNotificationsPermissionManagerBuilder = ::NotificationsPermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -42,7 +42,7 @@ fun PermissionsBuilder.registerNotificationsPermission( NotificationsPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) } -fun PermissionsBuilder.registerNotificationsPermission( +suspend fun PermissionsBuilder.registerNotificationsPermission( notificationsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseNotificationsPermissionManagerBuilder = ::NotificationsPermissionManagerBuilder, notificationsPermissionStateRepoBuilder: (NotificationsPermission, BaseNotificationsPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = notificationsPermissionManagerBuilderBuilder(context).also { @@ -51,3 +51,23 @@ fun PermissionsBuilder.registerNotificationsPermission( notificationsPermissionStateRepoBuilder(permission, it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerNotificationsPermissionIfNotRegistered( + notificationsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseNotificationsPermissionManagerBuilder = ::NotificationsPermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerNotificationsPermissionIfNotRegistered(notificationsPermissionManagerBuilderBuilder) { permission, builder, coroutineContext -> + NotificationsPermissionStateRepo(permission, builder, monitoringInterval, settings, coroutineContext) + } + +suspend fun PermissionsBuilder.registerNotificationsPermissionIfNotRegistered( + notificationsPermissionManagerBuilderBuilder: (PermissionContext) -> BaseNotificationsPermissionManagerBuilder = ::NotificationsPermissionManagerBuilder, + notificationsPermissionStateRepoBuilder: (NotificationsPermission, BaseNotificationsPermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = notificationsPermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { permission, coroutineContext -> + notificationsPermissionStateRepoBuilder(permission, it, coroutineContext) + } +} + diff --git a/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt b/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt index ddeb77866..50d68197a 100644 --- a/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt +++ b/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.permissions.notifications -import co.touchlab.stately.freeze import com.splendo.kaluga.logging.error import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.BasePermissionManager @@ -54,13 +53,10 @@ actual class DefaultNotificationsPermissionManager( val authorizationStatus = CompletableDeferred() val notificationCenter = notificationCenter coroutineScope.launch { - val deferred = CompletableDeferred() - val callback = { setting: UNNotificationSettings? -> - deferred.complete(setting) + notificationCenter.getNotificationSettingsWithCompletionHandler { setting -> + authorizationStatus.complete(setting?.authorizationStatus?.toAuthorizationStatus() ?: IOSPermissionsHelper.AuthorizationStatus.NotDetermined) Unit - }.freeze() - notificationCenter.getNotificationSettingsWithCompletionHandler(callback) - authorizationStatus.complete(deferred.await()?.authorizationStatus?.toAuthorizationStatus() ?: IOSPermissionsHelper.AuthorizationStatus.NotDetermined) + } } return authorizationStatus.await() } diff --git a/resources/src/iosMain/kotlin/uikit/ButtonStyle.kt b/resources/src/iosMain/kotlin/uikit/ButtonStyle.kt index 870d34a54..570459d32 100644 --- a/resources/src/iosMain/kotlin/uikit/ButtonStyle.kt +++ b/resources/src/iosMain/kotlin/uikit/ButtonStyle.kt @@ -44,7 +44,6 @@ import platform.objc.sel_registerName class UIControlClosure(private val action: () -> Unit) : NSObject() { - @ThreadLocal object Registry { val registeredUIControl = NSMapTable(NSPointerFunctionsWeakMemory, NSPointerFunctionsStrongMemory, 0) } diff --git a/resources/src/iosMain/kotlin/uikit/UILinkTapGesture.kt b/resources/src/iosMain/kotlin/uikit/UILinkTapGesture.kt index 2cf519099..7b59273a2 100644 --- a/resources/src/iosMain/kotlin/uikit/UILinkTapGesture.kt +++ b/resources/src/iosMain/kotlin/uikit/UILinkTapGesture.kt @@ -53,7 +53,6 @@ import kotlin.math.min class UILinkTapGesture(private val label: UILabel, private val urlRanges: List, NSURL>>) : NSObject() { - @ThreadLocal object Registry { val registeredGestures = NSMapTable(NSPointerFunctionsWeakMemory, NSPointerFunctionsStrongMemory, 0) } diff --git a/resources/src/iosMain/kotlin/uikit/UILinkTextViewDelegate.kt b/resources/src/iosMain/kotlin/uikit/UILinkTextViewDelegate.kt index b28eed0b7..9050eebf5 100644 --- a/resources/src/iosMain/kotlin/uikit/UILinkTextViewDelegate.kt +++ b/resources/src/iosMain/kotlin/uikit/UILinkTextViewDelegate.kt @@ -32,7 +32,6 @@ import platform.darwin.NSObject class UILinkTextViewDelegate : NSObject(), UITextViewDelegateProtocol { - @ThreadLocal object Registry { val registeredDelegates = NSMapTable(NSPointerFunctionsWeakMemory, NSPointerFunctionsStrongMemory, 0) } diff --git a/scientific/src/commonMain/kotlin/unit/AccelerationUnit.kt b/scientific/src/commonMain/kotlin/unit/AccelerationUnit.kt index 6d02cf349..84241bbe5 100644 --- a/scientific/src/commonMain/kotlin/unit/AccelerationUnit.kt +++ b/scientific/src/commonMain/kotlin/unit/AccelerationUnit.kt @@ -22,7 +22,6 @@ import com.splendo.kaluga.scientific.PhysicalQuantity import com.splendo.kaluga.scientific.convert import com.splendo.kaluga.scientific.invoke import kotlinx.serialization.Serializable -import kotlin.native.concurrent.ThreadLocal val MetricAccelerationUnits: Set get() = MetricSpeedUnits.flatMap { speed -> TimeUnits.map { speed per it } @@ -70,7 +69,5 @@ data class ImperialAcceleration( infix fun MetricSpeed.per(time: Time) = MetricAcceleration(this, time) infix fun ImperialSpeed.per(time: Time) = ImperialAcceleration(this, time) -@ThreadLocal val MetricStandardGravityAcceleration = 9.80665(Meter per Second per Second) -@ThreadLocal val ImperialStandardGravityAcceleration = MetricStandardGravityAcceleration.convert(Foot per Second per Second) diff --git a/scientific/src/commonMain/kotlin/unit/AmountOfSubstanceUnit.kt b/scientific/src/commonMain/kotlin/unit/AmountOfSubstanceUnit.kt index 6eced7448..6ea353920 100644 --- a/scientific/src/commonMain/kotlin/unit/AmountOfSubstanceUnit.kt +++ b/scientific/src/commonMain/kotlin/unit/AmountOfSubstanceUnit.kt @@ -21,7 +21,6 @@ import com.splendo.kaluga.base.utils.Decimal import com.splendo.kaluga.base.utils.toDecimal import com.splendo.kaluga.scientific.PhysicalQuantity import kotlinx.serialization.Serializable -import kotlin.native.concurrent.ThreadLocal val AmountOfSubstanceUnits: Set get() = setOf( Mole, @@ -70,5 +69,4 @@ object Megamole : AmountOfSubstance(), MetricMultipleUnit by Giga(Mole) -@ThreadLocal val AvogadroConstant = 6.02214076e23.toDecimal() diff --git a/scientific/src/commonMain/kotlin/unit/ElectricChargeUnit.kt b/scientific/src/commonMain/kotlin/unit/ElectricChargeUnit.kt index 3c25ca9db..2cb10d255 100644 --- a/scientific/src/commonMain/kotlin/unit/ElectricChargeUnit.kt +++ b/scientific/src/commonMain/kotlin/unit/ElectricChargeUnit.kt @@ -21,7 +21,6 @@ import com.splendo.kaluga.base.utils.Decimal import com.splendo.kaluga.scientific.PhysicalQuantity import com.splendo.kaluga.scientific.invoke import kotlinx.serialization.Serializable -import kotlin.native.concurrent.ThreadLocal val ElectricChargeUnits: Set get() = setOf( Coulomb, @@ -75,5 +74,4 @@ object Megacoulomb : ElectricCharge(), MetricMultipleUnit by Giga(Coulomb) -@ThreadLocal val elementaryCharge = 1.602176634e-19(Coulomb) diff --git a/scientific/src/commonMain/kotlin/unit/SpeedUnit.kt b/scientific/src/commonMain/kotlin/unit/SpeedUnit.kt index 0856da0fd..18242c689 100644 --- a/scientific/src/commonMain/kotlin/unit/SpeedUnit.kt +++ b/scientific/src/commonMain/kotlin/unit/SpeedUnit.kt @@ -22,7 +22,6 @@ import com.splendo.kaluga.scientific.PhysicalQuantity import com.splendo.kaluga.scientific.convert import com.splendo.kaluga.scientific.invoke import kotlinx.serialization.Serializable -import kotlin.native.concurrent.ThreadLocal val MetricSpeedUnits: Set get() = MetricLengthUnits.flatMap { length -> TimeUnits.map { MetricSpeed(length, it) } @@ -57,7 +56,5 @@ data class ImperialSpeed(override val distance: ImperialLength, override val per infix fun MetricLength.per(time: Time) = MetricSpeed(this, time) infix fun ImperialLength.per(time: Time) = ImperialSpeed(this, time) -@ThreadLocal val MetricSpeedOfLight = 299792458(Meter per Second) -@ThreadLocal val ImperialSpeedOfLight = MetricSpeedOfLight.convert(Foot per Second) diff --git a/storage-permissions/src/commonMain/kotlin/Permission.kt b/storage-permissions/src/commonMain/kotlin/Permission.kt index 316f417e5..7a127361c 100644 --- a/storage-permissions/src/commonMain/kotlin/Permission.kt +++ b/storage-permissions/src/commonMain/kotlin/Permission.kt @@ -34,7 +34,7 @@ data class StoragePermission(val allowWrite: Boolean = false) : Permission() { override val name: String = "Storage - ${if (allowWrite) "ReadWrite" else "ReadOnly"}" } -fun PermissionsBuilder.registerStoragePermission( +suspend fun PermissionsBuilder.registerStoragePermission( storagePermissionManagerBuilderBuilder: (PermissionContext) -> BaseStoragePermissionManagerBuilder = ::StoragePermissionManagerBuilder, monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() @@ -49,7 +49,7 @@ fun PermissionsBuilder.registerStoragePermission( ) } -fun PermissionsBuilder.registerStoragePermission( +suspend fun PermissionsBuilder.registerStoragePermission( storagePermissionManagerBuilderBuilder: (PermissionContext) -> BaseStoragePermissionManagerBuilder = ::StoragePermissionManagerBuilder, storagePermissionStateRepoBuilder: (StoragePermission, BaseStoragePermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo ) = storagePermissionManagerBuilderBuilder(context).also { @@ -58,3 +58,28 @@ fun PermissionsBuilder.registerStoragePermission( storagePermissionStateRepoBuilder(permission, it, coroutineContext) } } + +suspend fun PermissionsBuilder.registerStoragePermissionIfNotRegistered( + storagePermissionManagerBuilderBuilder: (PermissionContext) -> BaseStoragePermissionManagerBuilder = ::StoragePermissionManagerBuilder, + monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, + settings: BasePermissionManager.Settings = BasePermissionManager.Settings() +) = + registerStoragePermissionIfNotRegistered(storagePermissionManagerBuilderBuilder) { storagePermission, baseStoragePermissionManagerBuilder, coroutineContext -> + StoragePermissionStateRepo( + storagePermission, + baseStoragePermissionManagerBuilder, + monitoringInterval, + settings, + coroutineContext + ) + } + +suspend fun PermissionsBuilder.registerStoragePermissionIfNotRegistered( + storagePermissionManagerBuilderBuilder: (PermissionContext) -> BaseStoragePermissionManagerBuilder = ::StoragePermissionManagerBuilder, + storagePermissionStateRepoBuilder: (StoragePermission, BaseStoragePermissionManagerBuilder, CoroutineContext) -> PermissionStateRepo +) = storagePermissionManagerBuilderBuilder(context).also { + registerOrGet(it) + registerOrGetPermissionStateRepoBuilder { permission, coroutineContext -> + storagePermissionStateRepoBuilder(permission, it, coroutineContext) + } +} diff --git a/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt b/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt index 4608c70ec..53ca90fd5 100644 --- a/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt +++ b/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.permissions.storage -import co.touchlab.stately.freeze import com.splendo.kaluga.logging.error import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.BasePermissionManager @@ -59,11 +58,10 @@ actual class DefaultStoragePermissionManager( if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSPhotoLibraryUsageDescription).isEmpty()) { permissionHandler.requestAuthorizationStatus(timerHelper, CoroutineScope(coroutineContext)) { val deferred = CompletableDeferred() - val callback = { status: PHAuthorizationStatus -> + PHPhotoLibrary.requestAuthorization { status -> deferred.complete(status) Unit - }.freeze() - PHPhotoLibrary.requestAuthorization(callback) + } deferred.await().toAuthorizationStatus() } diff --git a/system/src/commonMain/kotlin/com/splendo/kaluga/system/network/state/NetworkStateRepo.kt b/system/src/commonMain/kotlin/com/splendo/kaluga/system/network/state/NetworkStateRepo.kt index 4bf428b15..824b21094 100644 --- a/system/src/commonMain/kotlin/com/splendo/kaluga/system/network/state/NetworkStateRepo.kt +++ b/system/src/commonMain/kotlin/com/splendo/kaluga/system/network/state/NetworkStateRepo.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.system.network.state -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.runBlocking import com.splendo.kaluga.state.ColdStateRepo import com.splendo.kaluga.system.network.BaseNetworkManager @@ -35,22 +34,19 @@ open class NetworkStateRepo( fun create(): NetworkStateRepo } - private val _lastKnownNetwork = AtomicReference(Network.Unknown.WithoutLastNetwork(Network.Unknown.Reason.NOT_CLEAR)) - internal var lastKnownNetwork: Network - get() = _lastKnownNetwork.get() - set(value) { _lastKnownNetwork.set(value) } + internal var lastKnownNetwork: Network = Network.Unknown.WithoutLastNetwork(Network.Unknown.Reason.NOT_CLEAR) - private val _networkManager = AtomicReference(null) + private var _networkManager: BaseNetworkManager? = null internal var networkManager: BaseNetworkManager? - get() = _networkManager.get() + get() = _networkManager set(value) { - _networkManager.get()?.let { + _networkManager?.let { if (value == null) { it.dispose() } } - _networkManager.set(value) + _networkManager = value } override suspend fun deinitialize(state: NetworkState) { diff --git a/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt b/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt index a8c753861..e8d2ddf82 100644 --- a/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt +++ b/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.system.network -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.IOSVersion import com.splendo.kaluga.logging.debug import kotlinx.cinterop.COpaquePointer @@ -121,10 +120,7 @@ actual class NetworkManager internal constructor( override val onNetworkStateChange: NetworkStateChange ) : AppleNetworkManager { - private var _reachability = AtomicReference(null) - private var reachability: SCNetworkReachabilityRef? - get() = _reachability.get() - set(value) = _reachability.set(value) + private var reachability: SCNetworkReachabilityRef? = SCNetworkReachabilityCreateWithName(null, "www.appleiphonecell.com") private val onNetworkStateChanged: SCNetworkReachabilityCallBack = staticCFunction { _: SCNetworkReachabilityRef?, flags: SCNetworkReachabilityFlags, info: COpaquePointer? -> if (info == null) { @@ -136,8 +132,6 @@ actual class NetworkManager internal constructor( } init { - reachability = SCNetworkReachabilityCreateWithName(null, "www.appleiphonecell.com") - val context = nativeHeap.alloc() context.info = StableRef.create(this@SCNetworkManager).asCPointer() diff --git a/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt b/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt index 07b2892ef..5ea1922ab 100644 --- a/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt +++ b/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt @@ -16,9 +16,6 @@ */ package com.splendo.kaluga.test.alerts -import co.touchlab.stately.collections.IsoMutableList -import co.touchlab.stately.concurrency.AtomicBoolean -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.alerts.Alert import com.splendo.kaluga.alerts.BaseAlertPresenter import com.splendo.kaluga.test.base.mock.call @@ -42,7 +39,7 @@ class MockAlertPresenter(val alert: Alert, setupMocks: Boolean = true) : BaseAle /** * The [MockAlertPresenter] created by this builder */ - val builtAlerts = IsoMutableList() + val builtAlerts = mutableListOf() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for the [create] function @@ -64,19 +61,12 @@ class MockAlertPresenter(val alert: Alert, setupMocks: Boolean = true) : BaseAle private fun createAlertFromAlert(alert: Alert, coroutineScope: CoroutineScope): MockAlertPresenter = createMock.call(alert, coroutineScope) } - private var _isPresented = AtomicBoolean(false) - /** * `true` if the [MockAlertPresenter] is currently being presented */ - var isPresented - get() = _isPresented.value - private set(value) { _isPresented.value = value } - - private var _afterHandler = AtomicReference<((Alert.Action?) -> Unit)?>(null) - private var afterHandler: ((Alert.Action?) -> Unit)? - get() = _afterHandler.get() - set(value) = _afterHandler.set(value) + var isPresented = false + + private var afterHandler: ((Alert.Action?) -> Unit)? = null /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for the [showAsync] function diff --git a/test-utils-architecture/src/commonMain/kotlin/ViewModelTest.kt b/test-utils-architecture/src/commonMain/kotlin/ViewModelTest.kt index fb32b0cab..3feae2a24 100644 --- a/test-utils-architecture/src/commonMain/kotlin/ViewModelTest.kt +++ b/test-utils-architecture/src/commonMain/kotlin/ViewModelTest.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.architecture -import co.touchlab.stately.ensureNeverFrozen import com.splendo.kaluga.architecture.viewmodel.LifecycleViewModel import com.splendo.kaluga.test.base.BaseTest import com.splendo.kaluga.test.base.BaseUIThreadTest @@ -25,11 +24,7 @@ import com.splendo.kaluga.test.base.UIThreadTest import kotlinx.coroutines.CoroutineScope import kotlin.test.BeforeTest -abstract class ViewModelTest(allowFreezing: Boolean = false) : BaseTest() { - - init { - if (!allowFreezing) ensureNeverFrozen() - } +abstract class ViewModelTest : BaseTest() { lateinit var viewModel: VM @@ -43,7 +38,7 @@ abstract class ViewModelTest(allowFreezing: Boolean = f } abstract class SimpleUIThreadViewModelTest : - UIThreadViewModelTest, VM>(allowFreezing = true) { + UIThreadViewModelTest, VM>() { override val createTestContext: suspend (CoroutineScope) -> ViewModelTestContext = { LazyViewModelTestContext(it, ::createViewModel) } @@ -54,8 +49,8 @@ abstract class SimpleUIThreadViewModelTest : /** * A [UIThreadTest] that takes a [ViewModelTestContext] */ -abstract class UIThreadViewModelTest, VM : LifecycleViewModel>(allowFreezing: Boolean = false) : - UIThreadTest(allowFreezing) { +abstract class UIThreadViewModelTest, VM : LifecycleViewModel>() : + UIThreadTest() { /** * [ViewModelTestContext] that lazily creates the view model @@ -73,8 +68,8 @@ abstract class UIThreadViewModelTest, VM : LifecycleViewModel>(allowFreezing: Boolean = false) : - BaseUIThreadTest(allowFreezing) { +abstract class BaseUIThreadViewModelTest, VM : LifecycleViewModel>() : + BaseUIThreadTest() { /** * [ViewModelTestContext] that lazily creates the view model diff --git a/test-utils-architecture/src/commonTest/kotlin/UIThreadViewModelTestTest.kt b/test-utils-architecture/src/commonTest/kotlin/UIThreadViewModelTestTest.kt index db64ffaff..85a3a1fb3 100644 --- a/test-utils-architecture/src/commonTest/kotlin/UIThreadViewModelTestTest.kt +++ b/test-utils-architecture/src/commonTest/kotlin/UIThreadViewModelTestTest.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.architecture -import co.touchlab.stately.concurrency.AtomicBoolean import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.observable.toInitializedSubject import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel @@ -35,7 +34,7 @@ import kotlin.test.fail class LazyUIThreadViewModelTestTest : UIThreadViewModelTest() { companion object { - val isDisposed = AtomicBoolean(false) + var isDisposed = false } class ViewModel : BaseLifecycleViewModel() { @@ -46,7 +45,7 @@ class LazyUIThreadViewModelTestTest : UIThreadViewModelTest(coroutineScope, { ViewModel() }) { override fun dispose() { - isDisposed.value = true + isDisposed = true } } @@ -73,12 +72,12 @@ class LazyUIThreadViewModelTestTest : UIThreadViewModelTest = suspend TC.(T) -> Unit typealias ActionBlock = suspend() -> Unit @@ -77,7 +73,6 @@ Context for each tests needs to be created and kept on the main thread for iOS. Since the class itself is created in the test thread */ -@ThreadLocal // thread local on native, global on non-native, but still accessed from only the main thread. val contextMap = mutableMapOf() private suspend fun testContext(cookie: Long, testContext: suspend () -> TC): TC { @@ -86,17 +81,12 @@ private suspend fun testContext(cookie: Long, testContext: su return contextMap[cookie] as TC } -@SharedImmutable -private val cookieTin = AtomicLong(0L) +private var cookieTin = 0L abstract class BaseFlowTest>(val scope: CoroutineScope = MainScope()) : BaseUIThreadTest(), CoroutineScope by scope { // used for thread local map access to store the testContext - private val cookie = cookieTin.incrementAndGet() - - init { - ensureNeverFrozen() - } + private val cookie = ++cookieTin abstract val flowFromTestContext: suspend TC.() -> F @@ -131,7 +121,7 @@ abstract class BaseFlowTest>(val scope: Coro testChannel = Channel(Channel.UNLIMITED) } - protected val waitForTestToSucceed = 6000L * 10 + protected val waitForTestToSucceed = 60.seconds private suspend fun awaitTestBlocks() { @@ -177,16 +167,13 @@ abstract class BaseFlowTest>(val scope: Coro val f = if (createFlowInMainScope) { - flow.freeze() - scope.freeze() withContext(Dispatchers.Main.immediate) { - (flow(testContext(cookie) { createTestContextWithConfiguration(configuration, scope) })).freeze() + (flow(testContext(cookie) { createTestContextWithConfiguration(configuration, scope) })) } } else { - createTestContextWithConfiguration.freeze() flow( withContext(Dispatchers.Main.immediate) { - (testContext(cookie) { createTestContextWithConfiguration(configuration, scope) }).freeze() + (testContext(cookie) { createTestContextWithConfiguration(configuration, scope) }) } ) } @@ -205,7 +192,6 @@ abstract class BaseFlowTest>(val scope: Coro @Suppress("SuspendFunctionOnCoroutineScope") private suspend fun startFlow(configuration: C, flow: F) { - this.ensureNeverFrozen() debug("launch flow scope...") val testChannel = testChannel val started = EmptyCompletableDeferred() @@ -213,7 +199,6 @@ abstract class BaseFlowTest>(val scope: Coro val scope = scope val createTestContextWithConfiguration = createTestContextWithConfiguration val cookie = cookie - createTestContextWithConfiguration.freeze() try { job = launch(Dispatchers.Main.immediate) { started.complete() @@ -257,7 +242,6 @@ abstract class BaseFlowTest>(val scope: Coro val createTestContextWithConfiguration = createTestContextWithConfiguration val scope = scope require(lateConfiguration != null) { "Only use mainAction from inside `testWith` methods" } - createTestContextWithConfiguration.freeze() val configuration = lateConfiguration!! withContext(Dispatchers.Main.immediate) { debug("in main scope for mainAction") @@ -276,10 +260,8 @@ abstract class BaseFlowTest>(val scope: Coro var firstTestBlock = true suspend fun test(skip: Int = 0, test: TestBlock) { - test.freeze() if (firstTestBlock) { firstTestBlock = false - tests.ensureNeverFrozen() debug("first test offered, starting collection") require(lateflow != null && lateConfiguration != null) { "Only use test from inside `testWith` methods" } startFlow(lateConfiguration!!, lateflow!!) diff --git a/test-utils-base/src/commonMain/kotlin/assertions.kt b/test-utils-base/src/commonMain/kotlin/assertions.kt deleted file mode 100644 index 177c2c470..000000000 --- a/test-utils-base/src/commonMain/kotlin/assertions.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright 2021 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.test.base - -import co.touchlab.stately.isFrozen -import kotlin.native.concurrent.SharedImmutable -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -@SharedImmutable -private val WARNING = "Checking if a Boolean is frozen, did you accidentally add `.isFrozen` in your assert? If intentional add `allowBoolean = true`." - -fun assertFrozen(any: Any, message: String? = null, allowBoolean: Boolean = false) { - if (!allowBoolean && any is Boolean) - error(WARNING) - assertTrue(any.isFrozen, message) -} - -fun assertNotFrozen(any: Any, message: String? = null, suppressWarning: Boolean = false) { - if (!suppressWarning && any is Boolean) - error(WARNING) - assertFalse(any.isFrozen, message) -} diff --git a/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt b/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt index bf5a5c6f4..b91a47220 100644 --- a/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt +++ b/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt @@ -17,9 +17,6 @@ package com.splendo.kaluga.test.base.mock -import co.touchlab.stately.collections.IsoMutableList -import co.touchlab.stately.collections.IsoMutableMap -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.test.base.mock.answer.Answer import com.splendo.kaluga.test.base.mock.answer.BaseAnswer import com.splendo.kaluga.test.base.mock.answer.SuspendedAnswer @@ -58,7 +55,7 @@ sealed class BaseMethodMock< > { abstract val matchers: M - protected val answer = AtomicReference(null) + protected var answer: A? = null protected abstract fun createAnswer(result: (V) -> R): A @@ -67,7 +64,7 @@ sealed class BaseMethodMock< * @param answer The [BaseAnswer] [A] to provide the answer for this stub. */ fun doAnswer(answer: A) { - this.answer.set(answer) + this.answer = answer } /** @@ -89,8 +86,8 @@ sealed class BaseMethodMock< fun doThrow(throwable: Throwable) = doExecute { throw throwable } } - private val stubs: IsoMutableMap = IsoMutableMap() - private val callParameters: IsoMutableList = IsoMutableList() + private val stubs = mutableMapOf() + private val callParameters = mutableListOf() protected abstract val ParametersSpec: W protected abstract fun createStub(matcher: M): S @@ -211,7 +208,7 @@ class MethodMock testBlockingAndCancelScope( context: CoroutineContext = EmptyCoroutineContext, - freezeResult: Boolean = true, crossinline block: suspend CoroutineScope.() -> T ): T { try { return runBlocking(context) { - block().also { cancel(DeliberateCancellationException(it, freezeResult)) } + block().also { cancel(DeliberateCancellationException(it)) } } } catch (e: Throwable) { if (e is DeliberateCancellationException && e.result is T) diff --git a/test-utils-base/src/commonMain/kotlin/ui_thread_testing.kt b/test-utils-base/src/commonMain/kotlin/ui_thread_testing.kt index 817feb5ee..5c161d43d 100644 --- a/test-utils-base/src/commonMain/kotlin/ui_thread_testing.kt +++ b/test-utils-base/src/commonMain/kotlin/ui_thread_testing.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.test.base -import co.touchlab.stately.ensureNeverFrozen -import co.touchlab.stately.freeze import com.splendo.kaluga.base.runBlocking import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -42,7 +40,7 @@ open class SimpleUIThreadTest : UIThreadTest(allowFreezing: Boolean = false) : BaseUIThreadTest(allowFreezing) { +abstract class UIThreadTest : BaseUIThreadTest() { interface TestContext : BaseUIThreadTest.TestContext class EmptyTestContext private constructor() : TestContext { @@ -90,11 +88,7 @@ abstract class UIThreadTest(allowFreezing: Boolea * as it eases dealing with immutability and allows a shared context. * */ -abstract class BaseUIThreadTest(allowFreezing: Boolean = false) : BaseTest() { - - init { - if (!allowFreezing) ensureNeverFrozen() - } +abstract class BaseUIThreadTest() : BaseTest() { interface TestContext { fun dispose() {} @@ -143,8 +137,6 @@ abstract class BaseUIThreadTest(allowFreez testContext.dispose() } } - createTestContextWithConfiguration.freeze() - test.freeze() if (cancelScopeAfterTest) testBlockingAndCancelScope(Dispatchers.Main) { test(this) } diff --git a/test-utils-bluetooth/src/androidLibMain/kotlin/MockDeviceWrapper.kt b/test-utils-bluetooth/src/androidLibMain/kotlin/MockDeviceWrapper.kt index f56a2c4ba..355499399 100644 --- a/test-utils-bluetooth/src/androidLibMain/kotlin/MockDeviceWrapper.kt +++ b/test-utils-bluetooth/src/androidLibMain/kotlin/MockDeviceWrapper.kt @@ -20,7 +20,6 @@ package com.splendo.kaluga.test.bluetooth import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothGattCallback import android.content.Context -import co.touchlab.stately.collections.sharedMutableListOf import com.splendo.kaluga.bluetooth.device.BluetoothGattWrapper import com.splendo.kaluga.bluetooth.device.DeviceWrapper import com.splendo.kaluga.bluetooth.device.Identifier @@ -35,7 +34,7 @@ class MockDeviceWrapper( setupMocks: Boolean = true ) : DeviceWrapper { - val gattWrappers = sharedMutableListOf() + val gattWrappers = mutableListOf() val connectGattMock = ::connectGatt.mock() val removeBondMock = ::removeBond.mock() val createBondMock = ::createBond.mock() diff --git a/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt b/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt index eb5d7869b..badf1001c 100644 --- a/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt +++ b/test-utils-bluetooth/src/commonMain/kotlin/device/MockDeviceConnectionManager.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.test.bluetooth.device -import co.touchlab.stately.collections.sharedMutableListOf -import co.touchlab.stately.concurrency.AtomicBoolean import com.splendo.kaluga.base.utils.toHexString import com.splendo.kaluga.bluetooth.asBytes import com.splendo.kaluga.bluetooth.device.BaseDeviceConnectionManager @@ -60,7 +58,7 @@ class MockDeviceConnectionManager( /** * List of created [MockDeviceConnectionManager] */ - val createdDeviceConnectionManager = sharedMutableListOf() + val createdDeviceConnectionManager = mutableListOf() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] @@ -86,14 +84,10 @@ class MockDeviceConnectionManager( } } - private val _willActionSucceed = AtomicBoolean(initialWillActionSucceed) - /** * Configure whether a [DeviceAction] will succeed */ - var willActionSucceed: Boolean - get() = _willActionSucceed.value - set(value) { _willActionSucceed.value = value } + var willActionSucceed: Boolean = initialWillActionSucceed /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [getCurrentState] diff --git a/test-utils-bluetooth/src/commonMain/kotlin/scanner/MockScanner.kt b/test-utils-bluetooth/src/commonMain/kotlin/scanner/MockScanner.kt index 30b289681..53e0966e0 100644 --- a/test-utils-bluetooth/src/commonMain/kotlin/scanner/MockScanner.kt +++ b/test-utils-bluetooth/src/commonMain/kotlin/scanner/MockScanner.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.bluetooth.scanner -import co.touchlab.stately.collections.sharedMutableListOf import com.splendo.kaluga.bluetooth.UUID import com.splendo.kaluga.bluetooth.device.Identifier import com.splendo.kaluga.bluetooth.scanner.BaseScanner @@ -93,7 +92,7 @@ class MockBaseScanner( /** * List of created [MockScanner] */ - val createdScanners = sharedMutableListOf() + val createdScanners = mutableListOf() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] diff --git a/test-utils-bluetooth/src/iosMain/kotlin/IOSMockCharacteristicWrapper.kt b/test-utils-bluetooth/src/iosMain/kotlin/IOSMockCharacteristicWrapper.kt index e35deafd8..178f61a01 100644 --- a/test-utils-bluetooth/src/iosMain/kotlin/IOSMockCharacteristicWrapper.kt +++ b/test-utils-bluetooth/src/iosMain/kotlin/IOSMockCharacteristicWrapper.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.bluetooth -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.toNSData import com.splendo.kaluga.base.utils.EmptyCompletableDeferred import com.splendo.kaluga.base.utils.complete @@ -37,10 +36,7 @@ class IOSMockCharacteristicWrapper( val isWriteCompleted = CompletableDeferred() val isNotificationCompleted = CompletableDeferred() - val _value = AtomicReference(null) - override var value: NSData? - get() = _value.get() - set(value) { _value.set(value) } + override var value: NSData? = null override val descriptors: List = descriptorUUIDs .map(::IOSMockDescriptorWrapper) diff --git a/test-utils-bluetooth/src/iosMain/kotlin/IOSMockDescriptorWrapper.kt b/test-utils-bluetooth/src/iosMain/kotlin/IOSMockDescriptorWrapper.kt index 592b6a6e7..472405232 100644 --- a/test-utils-bluetooth/src/iosMain/kotlin/IOSMockDescriptorWrapper.kt +++ b/test-utils-bluetooth/src/iosMain/kotlin/IOSMockDescriptorWrapper.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.bluetooth -import co.touchlab.stately.concurrency.AtomicReference import com.splendo.kaluga.base.toNSData import com.splendo.kaluga.base.utils.EmptyCompletableDeferred import com.splendo.kaluga.base.utils.complete @@ -35,10 +34,7 @@ class IOSMockDescriptorWrapper(override val uuid: CBUUID = CBUUID()) : MockDescr this.value = value?.toNSData() } - private val _value = AtomicReference(null) - override var value: NSData? - get() = _value.get() - set(value) { _value.set(value) } + override var value: NSData? = null override fun readValue(peripheral: CBPeripheral) { isReadCompleted.complete() diff --git a/test-utils-hud/src/commonMain/kotlin/MockHUD.kt b/test-utils-hud/src/commonMain/kotlin/MockHUD.kt index ab14554a0..f7c5ff48e 100644 --- a/test-utils-hud/src/commonMain/kotlin/MockHUD.kt +++ b/test-utils-hud/src/commonMain/kotlin/MockHUD.kt @@ -17,8 +17,6 @@ package com.splendo.kaluga.test.hud -import co.touchlab.stately.collections.IsoMutableList -import co.touchlab.stately.concurrency.AtomicBoolean import com.splendo.kaluga.hud.BaseHUD import com.splendo.kaluga.hud.HudConfig import com.splendo.kaluga.test.base.mock.call @@ -47,7 +45,7 @@ class MockHUD( /** * List of build [MockHUD] */ - val builtHUDs = IsoMutableList() + val builtHUDs = mutableListOf() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] @@ -69,16 +67,10 @@ class MockHUD( } } - private val _isVisible = AtomicBoolean(false) - /** * If `true` the HUD is currently being displayed */ - override var isVisible: Boolean - get() = _isVisible.value - private set(value) { - _isVisible.value = value - } + override var isVisible: Boolean = false /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [present] diff --git a/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt b/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt index 1d72fe5e2..67570307e 100644 --- a/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt +++ b/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt @@ -17,20 +17,18 @@ package com.splendo.kaluga.test.keyboard -import co.touchlab.stately.concurrency.AtomicBoolean import com.splendo.kaluga.keyboard.FocusHandler open class BaseMockFocusHandler { - private val _isFocused = AtomicBoolean(false) - val isFocused: Boolean - get() = _isFocused.value + var isFocused: Boolean = false + private set protected fun giveFocus() { - _isFocused.value = true + isFocused = true } protected fun removeFocus() { - _isFocused.value = false + isFocused = false } } diff --git a/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt b/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt index 2ea38df57..d01faef82 100644 --- a/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt +++ b/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt @@ -17,9 +17,6 @@ package com.splendo.kaluga.test.keyboard -import co.touchlab.stately.collections.IsoMutableList -import co.touchlab.stately.concurrency.AtomicReference -import co.touchlab.stately.concurrency.value import com.splendo.kaluga.keyboard.BaseKeyboardManager import com.splendo.kaluga.keyboard.FocusHandler import com.splendo.kaluga.test.base.mock.call @@ -42,7 +39,7 @@ class MockKeyboardManager(setupMocks: Boolean = true) : BaseKeyboardManager { /** * List of created [MockKeyboardManager] */ - val builtKeyboardManagers = IsoMutableList() + val builtKeyboardManagers = mutableListOf() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] @@ -62,14 +59,10 @@ class MockKeyboardManager(setupMocks: Boolean = true) : BaseKeyboardManager { override fun create(coroutineScope: CoroutineScope): MockKeyboardManager = createMock.call(coroutineScope) } - val _focusHandler = AtomicReference(null) - /** * Gets the current [FocusHandler] */ - var focusHandler: FocusHandler? - get() = _focusHandler.value - private set(value) = _focusHandler.set(value) + var focusHandler: FocusHandler? = null /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [show] diff --git a/test-utils-koin/src/commonMain/kotlin/KoinUIThreadTest.kt b/test-utils-koin/src/commonMain/kotlin/KoinUIThreadTest.kt index 55d6eddb5..dcb8bf033 100644 --- a/test-utils-koin/src/commonMain/kotlin/KoinUIThreadTest.kt +++ b/test-utils-koin/src/commonMain/kotlin/KoinUIThreadTest.kt @@ -26,7 +26,7 @@ import org.koin.core.context.stopKoin import org.koin.core.module.Module import org.koin.dsl.KoinAppDeclaration -abstract class KoinUIThreadTest(allowFreezing: Boolean = false) : BaseKoinUIThreadTest(allowFreezing) { +abstract class KoinUIThreadTest : BaseKoinUIThreadTest() { open class KoinTestContext( appDeclaration: KoinAppDeclaration? = null, @@ -51,7 +51,7 @@ abstract class KoinUIThreadTest(allowFree fun testOnUIThread(cancelScopeAfterTest: Boolean = false, block: suspend TC.() -> Unit) = testOnUIThread(Unit, cancelScopeAfterTest, block) } -abstract class BaseKoinUIThreadTest(allowFreezing: Boolean = false) : BaseUIThreadTest(allowFreezing) { +abstract class BaseKoinUIThreadTest : BaseUIThreadTest() { open class KoinTestContext( appDeclaration: KoinAppDeclaration? = null, diff --git a/test-utils-koin/src/commonMain/kotlin/KoinUIThreadViewModelTest.kt b/test-utils-koin/src/commonMain/kotlin/KoinUIThreadViewModelTest.kt index 9cb8d7226..2fc455db1 100644 --- a/test-utils-koin/src/commonMain/kotlin/KoinUIThreadViewModelTest.kt +++ b/test-utils-koin/src/commonMain/kotlin/KoinUIThreadViewModelTest.kt @@ -23,8 +23,8 @@ import com.splendo.kaluga.test.architecture.UIThreadViewModelTest import org.koin.core.module.Module import org.koin.dsl.KoinAppDeclaration -abstract class KoinUIThreadViewModelTest, VM : LifecycleViewModel>(allowFreezing: Boolean = false) : - KoinUIThreadTest(allowFreezing) { +abstract class KoinUIThreadViewModelTest, VM : LifecycleViewModel> : + KoinUIThreadTest() { abstract class KoinViewModelTestContext( appDeclaration: KoinAppDeclaration? = null, @@ -39,8 +39,8 @@ abstract class KoinUIThreadViewModelTest, VM : LifecycleViewModel>(allowFreezing: Boolean = false) : - BaseKoinUIThreadTest(allowFreezing) { +abstract class BaseKoinUIThreadViewModelTest, VM : LifecycleViewModel> : + BaseKoinUIThreadTest() { abstract class KoinViewModelTestContext( appDeclaration: KoinAppDeclaration? = null, diff --git a/test-utils-location/src/commonMain/kotlin/MockLocationManager.kt b/test-utils-location/src/commonMain/kotlin/MockLocationManager.kt index 27b40601c..05796d071 100644 --- a/test-utils-location/src/commonMain/kotlin/MockLocationManager.kt +++ b/test-utils-location/src/commonMain/kotlin/MockLocationManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.location -import co.touchlab.stately.collections.sharedMutableListOf import com.splendo.kaluga.location.BaseLocationManager import com.splendo.kaluga.location.Location import com.splendo.kaluga.location.LocationManager @@ -78,7 +77,7 @@ class MockBaseLocationManager( /** * Ths list of build [MockBaseLocationManager] */ - val builtLocationManagers = sharedMutableListOf() + val builtLocationManagers = mutableListOf() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] diff --git a/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt b/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt index 81737b21b..1755fec3a 100644 --- a/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt +++ b/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.location -import co.touchlab.stately.collections.IsoMutableList import com.splendo.kaluga.location.BaseLocationManager import com.splendo.kaluga.location.LocationStateRepo import com.splendo.kaluga.permissions.base.Permissions @@ -42,7 +41,7 @@ class MockLocationStateRepoBuilder( /** * List of build [LocationStateRepo] */ - val builtLocationStateRepo = IsoMutableList() + val builtLocationStateRepo = mutableListOf() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] diff --git a/test-utils-permissions/src/commonMain/kotlin/MockPermissionManager.kt b/test-utils-permissions/src/commonMain/kotlin/MockPermissionManager.kt index 20d27b4a7..973592c01 100644 --- a/test-utils-permissions/src/commonMain/kotlin/MockPermissionManager.kt +++ b/test-utils-permissions/src/commonMain/kotlin/MockPermissionManager.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.permissions -import co.touchlab.stately.collections.sharedMutableListOf import com.splendo.kaluga.logging.debug import com.splendo.kaluga.permissions.base.BasePermissionManager import com.splendo.kaluga.permissions.base.Permission @@ -58,7 +57,7 @@ class MockPermissionManager

( /** * List of built [MockPermissionManager] */ - val createdManagers = sharedMutableListOf>() + val createdManagers = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] diff --git a/test-utils-permissions/src/commonMain/kotlin/MockPermissionsBuilder.kt b/test-utils-permissions/src/commonMain/kotlin/MockPermissionsBuilder.kt index 85f83138c..98292b568 100644 --- a/test-utils-permissions/src/commonMain/kotlin/MockPermissionsBuilder.kt +++ b/test-utils-permissions/src/commonMain/kotlin/MockPermissionsBuilder.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.test.permissions -import co.touchlab.stately.collections.IsoMutableList import com.splendo.kaluga.permissions.base.BasePermissionStateRepo import com.splendo.kaluga.permissions.base.BasePermissionsBuilder import com.splendo.kaluga.permissions.base.Permission @@ -55,7 +54,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [CameraPermission] */ - val buildCameraStateRepos = IsoMutableList>() + val buildCameraStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [CameraPermission] @@ -66,7 +65,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [ContactsPermission] */ - val buildContactsStateRepos = IsoMutableList>() + val buildContactsStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [ContactsPermission] @@ -77,7 +76,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [MicrophonePermission] */ - val buildMicrophoneStateRepos = IsoMutableList>() + val buildMicrophoneStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [MicrophonePermission] @@ -88,7 +87,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [NotificationsPermission] */ - val buildNotificationsStateRepos = IsoMutableList>() + val buildNotificationsStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [NotificationsPermission] @@ -99,7 +98,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [BluetoothPermission] */ - val buildBluetoothStateRepos = IsoMutableList>() + val buildBluetoothStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [BluetoothPermission] @@ -110,7 +109,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [LocationPermission] */ - val buildLocationStateRepos = IsoMutableList>() + val buildLocationStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [LocationPermission] @@ -121,7 +120,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [CalendarPermission] */ - val buildCalendarStateRepos = IsoMutableList>() + val buildCalendarStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [CalendarPermission] @@ -132,7 +131,7 @@ class MockPermissionsBuilder( /** * List of created [PermissionStateRepo] for [StoragePermission] */ - val buildStorageStateRepos = IsoMutableList>() + val buildStorageStateRepos = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for creating [PermissionStateRepo] for [StoragePermission] @@ -195,7 +194,7 @@ class MockPermissionsBuilder( /** * Registers all [MockPermissionManager] and [MockPermissionStateRepo] */ - fun registerAllPermissionsBuilders() { + suspend fun registerAllPermissionsBuilders() { register(MockBasePermissionsBuilder()) registerPermissionStateRepoBuilder(::bluetoothStateRepoBuilder) register(MockBasePermissionsBuilder()) diff --git a/test-utils/src/commonMain/kotlin/legacy.kt b/test-utils/src/commonMain/kotlin/legacy.kt index 8ebe26df5..d0613752e 100644 --- a/test-utils/src/commonMain/kotlin/legacy.kt +++ b/test-utils/src/commonMain/kotlin/legacy.kt @@ -41,7 +41,7 @@ open class SimpleUIThreadTest : UIThreadTest SimpleTestContext = { SimpleTestContext(it) } } @Deprecated("Moved to test-utils-base", ReplaceWith("UIThreadTest", "com.splendo.kaluga.test.base.UIThreadTest")) -abstract class UIThreadTest(allowFreezing: Boolean = false) : com.splendo.kaluga.test.base.UIThreadTest(allowFreezing) { +abstract class UIThreadTest : com.splendo.kaluga.test.base.UIThreadTest() { @Deprecated("Moved to test-utils-base", ReplaceWith("TestContext", "com.splendo.kaluga.test.base.UIThreadTest.TestContext")) interface TestContext : com.splendo.kaluga.test.base.UIThreadTest.TestContext } @@ -62,7 +62,7 @@ abstract class BaseFlowTest> : com } @Deprecated("Moved to test-utils-koin", ReplaceWith("KoinUIThreadTest", "com.splendo.kaluga.test.koin.KoinUIThreadTest")) -abstract class KoinUIThreadTest(allowFreezing: Boolean = false) : com.splendo.kaluga.test.koin.KoinUIThreadTest(allowFreezing) { +abstract class KoinUIThreadTest : com.splendo.kaluga.test.koin.KoinUIThreadTest() { @Deprecated("Moved to test-utils-koin", ReplaceWith("KoinUIThreadTest.KoinTestContext", "com.splendo.kaluga.test.koin.KoinUIThreadTest")) open class KoinTestContext( appDeclaration: KoinAppDeclaration? = null, @@ -89,8 +89,8 @@ abstract class KoinFlowTest, VM : ViewModel>(allowFreezing: Boolean = false) : - KoinUIThreadTest(allowFreezing) { +abstract class KoinUIThreadViewModelTest, VM : ViewModel> : + KoinUIThreadTest() { @Deprecated("Moved to test-utils-koin", ReplaceWith("KoinViewModelTestContext", "com.splendo.kaluga.test.koin.KoinUIThreadViewModelTest")) abstract class KoinViewModelTestContext( @@ -106,10 +106,6 @@ abstract class KoinUIThreadViewModelTest awaitAllBlocking(vararg deferreds: Deferred): List = com.splendo.kaluga.test.base.awaitAllBlocking(*deferreds) @Deprecated("Moved to test-utils-base", ReplaceWith("captureFor(duration)", "com.splendo.kaluga.test.base.captureFor")) From a83791c6bface144157b830cb5b3298dad70cb54 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 2 Nov 2022 15:02:34 +0100 Subject: [PATCH 007/227] New memory model continued --- adding-a-new-module/README.md | 2 +- .../kotlin/AndroidAlertPresenterTest.kt | 3 +- .../androidLibMain/kotlin/AlertPresenter.kt | 4 +- alerts/src/commonMain/kotlin/Alerts.kt | 258 +++++++++--------- alerts/src/iosMain/kotlin/AlertPresenter.kt | 2 +- alerts/src/jsMain/kotlin/AlertPresenter.kt | 4 +- alerts/src/jvmMain/kotlin/AlertPresenter.kt | 4 +- .../kotlin/observable/Observables.kt | 2 +- .../kotlin/observable/ObservableBaseTest.kt | 10 +- .../viewmodel/ViewModelLifecycleManager.kt | 12 +- .../kotlin/observable/ThreadingTest.kt | 28 +- .../kotlin/AndroidPermissionsManagerTest.kt | 26 +- .../src/commonMain/kotlin/Permission.kt | 1 - .../kotlin/PermissionRefreshScheduler.kt | 10 +- .../commonMain/kotlin/state/KalugaState.kt | 12 +- .../commonTest/kotlin/AtomicReferenceTest.kt | 36 --- base/src/iosMain/kotlin/GCScheduler.kt | 1 - beacons/src/commonMain/kotlin/Beacons.kt | 8 +- .../src/commonTest/kotlin/BeaconFlowTest.kt | 6 +- .../src/commonTest/kotlin/BeaconLostTest.kt | 3 +- .../androidLibMain/kotlin/BluetoothBuilder.kt | 10 +- bluetooth/src/commonMain/kotlin/Bluetooth.kt | 2 +- .../device/DefaultDeviceConnectionManager.kt | 1 + .../kotlin/scanner/ScanningStateRepo.kt | 4 +- .../commonTest/kotlin/BluetoothFlowTest.kt | 5 +- .../src/iosMain/kotlin/BluetoothBuilder.kt | 4 +- .../device/DefaultDeviceConnectionManager.kt | 4 +- .../src/jsMain/kotlin/BluetoothBuilder.kt | 6 +- .../src/jvmMain/kotlin/BluetoothBuilder.kt | 6 +- .../kotlin/CalendarPermissionManager.kt | 3 +- .../kotlin/ContactsPermissionManager.kt | 3 +- .../AndroidDateTimePickerPresenterTest.kt | 3 +- .../kotlin/DateTimePickerPresenter.kt | 5 +- .../src/commonMain/kotlin/DateTimePicker.kt | 155 +++++------ .../iosMain/kotlin/DateTimePickerPresenter.kt | 6 +- .../jsMain/kotlin/DateTimePickerPresenter.kt | 4 +- .../jvmMain/kotlin/DateTimePickerPresenter.kt | 4 +- .../com/splendo/kaluga/example/di/Modules.kt | 3 +- .../kotlin/beacons/KNBeaconsFramework.kt | 3 +- .../kotlin/AndroidHUDTests.kt | 3 +- hud/src/commonMain/kotlin/HUD.kt | 33 +-- hud/src/commonMain/kotlin/HudConfig.kt | 24 +- .../kotlin/location/LocationManager.kt | 6 +- .../kotlin/location/LocationStateRepo.kt | 4 +- .../kotlin/location/LocationStateTest.kt | 23 +- .../kotlin/location/LocationManager.kt | 6 +- location/src/jsMain/kotlin/LocationManager.kt | 6 +- .../src/jvmMain/kotlin/LocationManager.kt | 6 +- logging/src/commonTest/kotlin/LogTest.kt | 167 +++++++----- logging/src/commonTest/kotlin/LoggerMock.kt | 1 - .../src/commonMain/kotlin/Permission.kt | 1 - .../kotlin/NotificationsPermissionManager.kt | 13 +- .../kotlin/registerAllPermissions.kt | 2 +- .../kotlin/stylable/GradientStyle.kt | 2 +- .../kotlin/StoragePermissionManager.kt | 2 +- .../commonMain/kotlin/MockAlertPresenter.kt | 2 +- .../src/commonMain/kotlin/mock/MockMethod.kt | 8 +- .../kotlin/testBlockingAndCancelScope.kt | 3 +- .../src/iosMain/kotlin/mainBackground.kt | 3 +- .../kotlin/MockLocationStateRepoBuilder.kt | 6 +- test-utils/src/commonMain/kotlin/legacy.kt | 5 +- 61 files changed, 481 insertions(+), 508 deletions(-) delete mode 100644 base/src/commonTest/kotlin/AtomicReferenceTest.kt diff --git a/adding-a-new-module/README.md b/adding-a-new-module/README.md index 350e5620e..691540d16 100644 --- a/adding-a-new-module/README.md +++ b/adding-a-new-module/README.md @@ -87,7 +87,7 @@ Add unit tests which should run on a device or emulator Example: ```kotlin -const val DEFAULT_TIMEOUT = 20_000L +val DEFAULT_TIMEOUT = 20.seconds.inWholeMilliseconds fun UiDevice.assertTextAppears(text: String) { assertNotNull(this.wait(Until.findObject(By.text(text)), DEFAULT_TIMEOUT)) diff --git a/alerts/src/androidLibAndroidTest/kotlin/AndroidAlertPresenterTest.kt b/alerts/src/androidLibAndroidTest/kotlin/AndroidAlertPresenterTest.kt index d216dc65e..83304e37d 100644 --- a/alerts/src/androidLibAndroidTest/kotlin/AndroidAlertPresenterTest.kt +++ b/alerts/src/androidLibAndroidTest/kotlin/AndroidAlertPresenterTest.kt @@ -36,6 +36,7 @@ import kotlin.test.Ignore import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds class AndroidAlertPresenterTest : AlertPresenterTests() { @@ -54,7 +55,7 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { override val builder get() = activity!!.viewModel.alertBuilder companion object { - const val DEFAULT_TIMEOUT = 20_000L + val DEFAULT_TIMEOUT = 20.seconds.inWholeMilliseconds } @Test diff --git a/alerts/src/androidLibMain/kotlin/AlertPresenter.kt b/alerts/src/androidLibMain/kotlin/AlertPresenter.kt index 9215dabb3..6d6043ec4 100644 --- a/alerts/src/androidLibMain/kotlin/AlertPresenter.kt +++ b/alerts/src/androidLibMain/kotlin/AlertPresenter.kt @@ -46,8 +46,8 @@ actual class AlertPresenter( actual class Builder( private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver() ) : BaseAlertPresenter.Builder(), LifecycleSubscribable by lifecycleManagerObserver { - actual override fun create(coroutineScope: CoroutineScope) = - AlertPresenter(createAlert(), lifecycleManagerObserver, coroutineScope) + actual override fun create(alert: Alert, coroutineScope: CoroutineScope) = + AlertPresenter(alert, lifecycleManagerObserver, coroutineScope) } private companion object { diff --git a/alerts/src/commonMain/kotlin/Alerts.kt b/alerts/src/commonMain/kotlin/Alerts.kt index 26dc6b8ab..4b7f08b73 100644 --- a/alerts/src/commonMain/kotlin/Alerts.kt +++ b/alerts/src/commonMain/kotlin/Alerts.kt @@ -21,8 +21,6 @@ package com.splendo.kaluga.alerts import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlin.coroutines.resume typealias AlertActionHandler = () -> Unit @@ -52,92 +50,13 @@ data class Alert( } /** - * An action that represents a button in the alert - * - * @property title The title of the action's button - * @property style The style that is applied to the action's button - * @property handler The block to execute when the user taps a button - */ - data class Action( - val title: String, - val style: Style = Style.DEFAULT, - val handler: AlertActionHandler = {} - ) { - enum class Style(val value: Int) { - DEFAULT(0), - POSITIVE(DEFAULT.value), - DESTRUCTIVE(1), - NEUTRAL(DESTRUCTIVE.value), - CANCEL(2), - NEGATIVE(CANCEL.value) - } - } - - /** - * An action that represents an input field in the alert and its initial state - * - * @param text The initial text of the input field - * @param placeholder The hint of the input field - * @param textObserver The block to execute when the user edits the text in the input field - */ - data class TextInputAction( - val text: String?, - val placeholder: String?, - val textObserver: AlertTextObserver - ) -} - -/** - * Interface that defines actions that can be applied to the alert. - */ -interface AlertActions { - /** - * Presents an alert - * - * @param animated Pass `true` to animate the presentation - * @param completion The block to execute after the presentation finishes - */ - fun showAsync(animated: Boolean = true, completion: () -> Unit = {}) - - /** - * Presents an alert and suspends - * - * @param animated - * @return The action that was performed by button click - * or `null` if the alert was cancelled by pressing back (on Android) - */ - suspend fun show(animated: Boolean = true): Alert.Action? - - /** - * Dismisses the alert, which was presented previously - * - * @param animated Pass `true` to animate the transition - */ - fun dismiss(animated: Boolean = true) -} - -/** - * Abstract alert presenter, used to show and dismiss given [Alert] - * @see [AlertPresenter] - * - * @property alert The alert to present (and dismiss if needed) - */ -abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { - - /** - * Abstract alert builder class, used to create an [Alert]. - * The resulting Alert that can be shown and dismissed using an [AlertPresenter]. - * - * @see [AlertPresenter.Builder] + * Builder class for creating an [Alert] */ - abstract class Builder : LifecycleSubscribableMarker { - + class Builder(private var style: Style = Style.ALERT) { private var title: String? = null private var message: String? = null - private var actions: MutableList = mutableListOf() - private var textInputAction: Alert.TextInputAction? = null - private var style: Alert.Style = Alert.Style.ALERT - internal val lock = Mutex() + private var actions: MutableList = mutableListOf() + private var textInputAction: TextInputAction? = null /** * Sets the [title] displayed in the alert @@ -161,7 +80,7 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { * @param handler The block to execute after user taps a button */ fun setPositiveButton(title: String, handler: AlertActionHandler = {}) = apply { - addAction(Alert.Action(title, Alert.Action.Style.POSITIVE, handler)) + addAction(Action(title, Action.Style.POSITIVE, handler)) } /** @@ -172,7 +91,7 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { * @param handler The block to execute after user taps a button */ fun setNegativeButton(title: String, handler: AlertActionHandler = {}) = apply { - addAction(Alert.Action(title, Alert.Action.Style.NEGATIVE, handler)) + addAction(Action(title, Action.Style.NEGATIVE, handler)) } /** @@ -183,7 +102,7 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { * @param handler The block to execute after user taps a button */ fun setNeutralButton(title: String, handler: AlertActionHandler = {}) = apply { - addAction(Alert.Action(title, Alert.Action.Style.NEUTRAL, handler)) + addAction(Action(title, Action.Style.NEUTRAL, handler)) } /** @@ -198,7 +117,7 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { placeholder: String?, textObserver: AlertTextObserver ) = apply { - setTextInputAction(Alert.TextInputAction(text, placeholder, textObserver)) + setTextInputAction(TextInputAction(text, placeholder, textObserver)) } /** @@ -206,56 +125,45 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { * * @param actions The list of action objects */ - fun addActions(actions: List) = apply { this.actions.addAll(actions) } + fun addActions(actions: List) = apply { this.actions.addAll(actions) } /** * Adds a list of [actions] to the alert * * @param actions The list of action objects */ - fun addActions(vararg actions: Alert.Action) = apply { this.actions.addAll(actions) } + fun addActions(vararg actions: Action) = apply { this.actions.addAll(actions) } /** * Sets a style of the alert * * @param style The style of an alert */ - internal fun setStyle(style: Alert.Style) = apply { this.style = style } + internal fun setStyle(style: Style) = apply { this.style = style } /** * Adds an [action] to the alert * * @param action The action object */ - private fun addAction(action: Alert.Action) = apply { this.actions.add(action) } + private fun addAction(action: Action) = apply { this.actions.add(action) } /** * Adds an [Alert.TextInputAction] to the alert * * @param action The action object */ - private fun setTextInputAction(action: Alert.TextInputAction) = + private fun setTextInputAction(action: TextInputAction) = apply { this.textInputAction = action } - /** - * Reset builder into initial state - */ - internal fun reset() = apply { - this.title = null - this.message = null - this.actions = mutableListOf() - this.textInputAction = null - this.style = Alert.Style.ALERT - } - /** * Creates an alert based on [title], [message], [actions] and [textInputAction] properties * * @return The alert object * @throws IllegalArgumentException in case missing title and/or message or actions */ - protected fun createAlert(): Alert { - if (style == Alert.Style.ALERT) { + fun build(): Alert { + if (style == Style.ALERT) { require(title != null || message != null) { "Please set title and/or message for the Alert" } } // Action sheet on iOS can be without title and message @@ -263,14 +171,97 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { return Alert(title, message, actions, textInputAction, style) } + } + + /** + * An action that represents a button in the alert + * + * @property title The title of the action's button + * @property style The style that is applied to the action's button + * @property handler The block to execute when the user taps a button + */ + data class Action( + val title: String, + val style: Style = Style.DEFAULT, + val handler: AlertActionHandler = {} + ) { + enum class Style(val value: Int) { + DEFAULT(0), + POSITIVE(DEFAULT.value), + DESTRUCTIVE(1), + NEUTRAL(DESTRUCTIVE.value), + CANCEL(2), + NEGATIVE(CANCEL.value) + } + } + + /** + * An action that represents an input field in the alert and its initial state + * + * @param text The initial text of the input field + * @param placeholder The hint of the input field + * @param textObserver The block to execute when the user edits the text in the input field + */ + data class TextInputAction( + val text: String?, + val placeholder: String?, + val textObserver: AlertTextObserver + ) +} + +/** + * Interface that defines actions that can be applied to the alert. + */ +interface AlertActions { + /** + * Presents an alert + * + * @param animated Pass `true` to animate the presentation + * @param completion The block to execute after the presentation finishes + */ + fun showAsync(animated: Boolean = true, completion: () -> Unit = {}) + + /** + * Presents an alert and suspends + * + * @param animated + * @return The action that was performed by button click + * or `null` if the alert was cancelled by pressing back (on Android) + */ + suspend fun show(animated: Boolean = true): Alert.Action? + + /** + * Dismisses the alert, which was presented previously + * + * @param animated Pass `true` to animate the transition + */ + fun dismiss(animated: Boolean = true) +} + +/** + * Abstract alert presenter, used to show and dismiss given [Alert] + * @see [AlertPresenter] + * + * @property alert The alert to present (and dismiss if needed) + */ +abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { + + /** + * Abstract alert builder class, used to create an [Alert]. + * The resulting Alert that can be shown and dismissed using an [AlertPresenter]. + * + * @see [AlertPresenter.Builder] + */ + abstract class Builder : LifecycleSubscribableMarker { /** * Creates the [BaseAlertPresenter] described by this builder. * + * @param alert The [Alert] to be presented by the built presenter. * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. * @return The [BaseAlertPresenter] described by this builder. */ - abstract fun create(coroutineScope: CoroutineScope): BaseAlertPresenter + abstract fun create(alert: Alert, coroutineScope: CoroutineScope): BaseAlertPresenter } override fun showAsync(animated: Boolean, completion: () -> Unit) { @@ -307,10 +298,11 @@ expect class AlertPresenter : BaseAlertPresenter { /** * Creates an [AlertPresenter] * + * @param alert The [Alert] to be presented with the built presenter. * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. * @return The created [AlertPresenter] */ - override fun create(coroutineScope: CoroutineScope): AlertPresenter + override fun create(alert: Alert, coroutineScope: CoroutineScope): AlertPresenter } } @@ -321,15 +313,15 @@ expect class AlertPresenter : BaseAlertPresenter { * @param initialize The block to construct an Alert * @return The built alert interface object */ -suspend fun BaseAlertPresenter.Builder.buildAlert( +fun BaseAlertPresenter.Builder.buildAlert( coroutineScope: CoroutineScope, - initialize: BaseAlertPresenter.Builder.() -> Unit -): BaseAlertPresenter = lock.withLock { - reset() - setStyle(Alert.Style.ALERT) - initialize() - return create(coroutineScope) -} + initialize: Alert.Builder.() -> Unit +): BaseAlertPresenter = create( + Alert.Builder(Alert.Style.ALERT).apply { + initialize() + }.build(), + coroutineScope +) /** * Builds an alert of type [Alert.Style.ACTION_LIST] using DSL syntax (thread safe) @@ -338,15 +330,15 @@ suspend fun BaseAlertPresenter.Builder.buildAlert( * @param initialize The block to construct an Alert * @return The built alert interface object */ -suspend fun BaseAlertPresenter.Builder.buildActionSheet( +fun BaseAlertPresenter.Builder.buildActionSheet( coroutineScope: CoroutineScope, - initialize: BaseAlertPresenter.Builder.() -> Unit -): BaseAlertPresenter = lock.withLock { - reset() - setStyle(Alert.Style.ACTION_LIST) - initialize() - return create(coroutineScope) -} + initialize: Alert.Builder.() -> Unit +): BaseAlertPresenter = create( + Alert.Builder(Alert.Style.ACTION_LIST).apply { + initialize() + }.build(), + coroutineScope +) /** * Builds an alert of type [Alert.Style.TEXT_INPUT] using DSL syntax (thread safe) @@ -355,12 +347,12 @@ suspend fun BaseAlertPresenter.Builder.buildActionSheet( * @param initialize The block to construct an Alert * @return The built alert interface object */ -suspend fun BaseAlertPresenter.Builder.buildAlertWithInput( +fun BaseAlertPresenter.Builder.buildAlertWithInput( coroutineScope: CoroutineScope, - initialize: BaseAlertPresenter.Builder.() -> Unit -): BaseAlertPresenter = lock.withLock { - reset() - setStyle(Alert.Style.TEXT_INPUT) - initialize() - return create(coroutineScope) -} + initialize: Alert.Builder.() -> Unit +): BaseAlertPresenter = create( + Alert.Builder(Alert.Style.TEXT_INPUT).apply { + initialize() + }.build(), + coroutineScope +) diff --git a/alerts/src/iosMain/kotlin/AlertPresenter.kt b/alerts/src/iosMain/kotlin/AlertPresenter.kt index d2363b9c6..3d87fd164 100644 --- a/alerts/src/iosMain/kotlin/AlertPresenter.kt +++ b/alerts/src/iosMain/kotlin/AlertPresenter.kt @@ -75,7 +75,7 @@ actual class AlertPresenter( } actual class Builder(private val viewController: UIViewController) : BaseAlertPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope) = AlertPresenter(createAlert(), viewController) + actual override fun create(alert: Alert, coroutineScope: CoroutineScope) = AlertPresenter(alert, viewController) } override fun dismissAlert(animated: Boolean) { diff --git a/alerts/src/jsMain/kotlin/AlertPresenter.kt b/alerts/src/jsMain/kotlin/AlertPresenter.kt index ed075a3b3..281ff6107 100644 --- a/alerts/src/jsMain/kotlin/AlertPresenter.kt +++ b/alerts/src/jsMain/kotlin/AlertPresenter.kt @@ -26,8 +26,8 @@ actual class AlertPresenter( actual class Builder : BaseAlertPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope): AlertPresenter { - return AlertPresenter(createAlert()) + actual override fun create(alert: Alert, coroutineScope: CoroutineScope): AlertPresenter { + return AlertPresenter(alert) } } diff --git a/alerts/src/jvmMain/kotlin/AlertPresenter.kt b/alerts/src/jvmMain/kotlin/AlertPresenter.kt index ed075a3b3..281ff6107 100644 --- a/alerts/src/jvmMain/kotlin/AlertPresenter.kt +++ b/alerts/src/jvmMain/kotlin/AlertPresenter.kt @@ -26,8 +26,8 @@ actual class AlertPresenter( actual class Builder : BaseAlertPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope): AlertPresenter { - return AlertPresenter(createAlert()) + actual override fun create(alert: Alert, coroutineScope: CoroutineScope): AlertPresenter { + return AlertPresenter(alert) } } diff --git a/architecture/src/commonMain/kotlin/observable/Observables.kt b/architecture/src/commonMain/kotlin/observable/Observables.kt index c9fcfcd06..aca239e07 100644 --- a/architecture/src/commonMain/kotlin/observable/Observables.kt +++ b/architecture/src/commonMain/kotlin/observable/Observables.kt @@ -17,7 +17,7 @@ package com.splendo.kaluga.architecture.observable -import com.splendo.kaluga.architecture.observable.ObservableOptional.* +import com.splendo.kaluga.architecture.observable.ObservableOptional.Value import com.splendo.kaluga.base.flow.HotFlowable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow diff --git a/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt b/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt index 9819c9e54..1b7ed7772 100644 --- a/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt +++ b/architecture/src/commonTest/kotlin/observable/ObservableBaseTest.kt @@ -158,9 +158,9 @@ abstract class ObservableBaseTest : BaseTest() { *updates.map { it.asUpdate() }.toTypedArray() ) - private val updateSemaphore = AtomicReference(null) + private var updateSemaphore: Semaphore? = null suspend fun waitForUpdate() { - updateSemaphore.get()?.acquire() ?: error("call testObservable to collect your flowOfWithDelays instead of doing this directly") + updateSemaphore?.acquire() ?: error("call testObservable to collect your flowOfWithDelays instead of doing this directly") } suspend fun , O : BasicObservable> testObservable( @@ -171,8 +171,8 @@ abstract class ObservableBaseTest : BaseTest() { vararg updates: (O) -> ObservableOptional ) { val permits = updates.size + 1 // +1 for initial state - val semaphore = Semaphore(permits).freeze() - updateSemaphore.set(semaphore) + val semaphore = Semaphore(permits) + updateSemaphore = semaphore repeat(permits) { semaphore.acquire() } val observableOptional by observable @@ -206,7 +206,7 @@ abstract class ObservableBaseTest : BaseTest() { var disposableInitialized: Disposable? = null if (observable is Initialized<*, *>) { observedInitializedValue = unusedValue - disposableInitialized = (observable as Initialized).observeInitialized { observedInitializedValue.set(it) } + disposableInitialized = (observable as Initialized).observeInitialized { observedInitializedValue = it } } when (initialExpected) { diff --git a/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt b/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt index 657fb55d9..0ee29847b 100644 --- a/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt +++ b/architecture/src/iosMain/kotlin/viewmodel/ViewModelLifecycleManager.kt @@ -19,6 +19,8 @@ package com.splendo.kaluga.architecture.viewmodel import com.splendo.kaluga.architecture.observable.Disposable import com.splendo.kaluga.architecture.observable.DisposeBag +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import platform.UIKit.UIViewController import platform.UIKit.addChildViewController import platform.UIKit.addSubview @@ -66,8 +68,10 @@ internal class ViewModelLifecycleManager(private va super.viewDidAppear(animated) viewModel.didResume() - onLifecycle().forEach { - lifecycleManager.disposeBag.add(it) + MainScope().launch { + onLifecycle().forEach { + lifecycleManager.disposeBag.add(it) + } } } @@ -75,7 +79,9 @@ internal class ViewModelLifecycleManager(private va super.viewDidDisappear(animated) viewModel.didPause() - lifecycleManager.disposeBag.dispose() + MainScope().launch { + lifecycleManager.disposeBag.dispose() + } } } diff --git a/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt b/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt index c23e2c733..ed34ed6a7 100644 --- a/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt +++ b/architecture/src/iosTest/kotlin/observable/ThreadingTest.kt @@ -18,8 +18,6 @@ package com.splendo.kaluga.architecture.observable import com.splendo.kaluga.base.runBlocking -import com.splendo.kaluga.test.base.assertFrozen -import com.splendo.kaluga.test.base.assertNotFrozen import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.test.Test @@ -32,32 +30,19 @@ class ThreadingTest { @Test fun testThreadingMainOnly() = runBlocking(Dispatchers.Main) { val s = subjectOf(null) - assertNotFrozen(s) val value = Value() - assertNotFrozen(value) - assertNotFrozen(s) s.set(value) assertEquals(value, s.currentOrNull) - - // still nothing should be frozen - assertNotFrozen(value) - assertNotFrozen(s) - assertEquals(value, s.stateFlow.value) - assertNotFrozen(value) // using StateFlow from the same thread should not freeze the value - assertNotFrozen(s) // the subject itself is also not frozen } @Test fun testThreadingDefaultThenMain() = runBlocking(Dispatchers.Default) { val s = subjectOf(null) withContext(Dispatchers.Main) { - assertFrozen(s) // due to the context switch the subject itself is frozen val value = Value() - assertNotFrozen(value) // of course not s.set(value) - assertFrozen(value) // due to the context switch the var inside subject is frozen } } @@ -65,29 +50,22 @@ class ThreadingTest { fun testThreadingMainThenDefault() = runBlocking(Dispatchers.Main) { val s = subjectOf(null) val value = Value() - s.stateFlow.value = value - assertNotFrozen(value) + s.set(value) withContext(Dispatchers.Default) { - assertFrozen(s) // due to the context switch the subject itself is frozen - assertFrozen(value) + assertEquals(value, s.currentOrNull) + assertEquals(value, s.stateFlow.value) } } @Test fun testThreadingMainThenDefaultObservers() = runBlocking(Dispatchers.Main) { val s = subjectOf(null) - val value = Value() val observer: (Value?) -> Unit = { } val disposable = s.observe(observer) - assertNotFrozen(value) withContext(Dispatchers.Default) { - // due to the context switch the subject itself is frozen - assertFrozen(s) - assertFrozen(value) val observer2: (Value?) -> Unit = { } val disposable2 = s.observe(observer2) - assertFrozen(observer2) disposable2.dispose() } diff --git a/base-permissions/src/androidLibTest/kotlin/AndroidPermissionsManagerTest.kt b/base-permissions/src/androidLibTest/kotlin/AndroidPermissionsManagerTest.kt index 0b5fe91aa..8dfaf2adf 100644 --- a/base-permissions/src/androidLibTest/kotlin/AndroidPermissionsManagerTest.kt +++ b/base-permissions/src/androidLibTest/kotlin/AndroidPermissionsManagerTest.kt @@ -24,7 +24,8 @@ import android.content.pm.PackageInfo import android.content.pm.PackageManager import com.splendo.kaluga.base.runBlocking import com.splendo.kaluga.test.base.BaseTest -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import org.junit.Test import org.mockito.ArgumentMatchers @@ -64,11 +65,26 @@ class AndroidPermissionsManagerTest : BaseTest() { @BeforeTest override fun beforeTest() { super.beforeTest() - MockitoAnnotations.initMocks(this) + MockitoAnnotations.openMocks(this) `when`(context.packageManager).thenReturn(packageManager) `when`(context.packageName).thenReturn(packageName) - `when`(packageManager.getPackageInfo(Mockito.eq(packageName), Mockito.eq(PackageManager.GET_PERMISSIONS))).thenReturn(packageInfo) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + `when`( + packageManager.getPackageInfo( + Mockito.eq(packageName), + Mockito.eq(PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())) + ) + ) + } else { + @Suppress("DEPRECATION") + `when`( + packageManager.getPackageInfo( + Mockito.eq(packageName), + Mockito.eq(PackageManager.GET_PERMISSIONS) + ) + ) + }.thenReturn(packageInfo) AndroidPermissionsManager.permissionsStates.clear() } @@ -78,7 +94,7 @@ class AndroidPermissionsManagerTest : BaseTest() { } @Test - fun testMissingDeclaration() = runBlockingTest { + fun testMissingDeclaration() = runTest(UnconfinedTestDispatcher()) { val onPermissionChanged = MockPermissionStateHandler() androidPermissionsManager = AndroidPermissionsManager(context, permissions, this, onPermissionChanged = onPermissionChanged) packageInfo.requestedPermissions = emptyArray() @@ -88,7 +104,7 @@ class AndroidPermissionsManagerTest : BaseTest() { } @Test - fun testRequestPermissions() = runBlockingTest { + fun testRequestPermissions() = runTest(UnconfinedTestDispatcher()) { val onPermissionChangedFlow = MockPermissionStateHandler() androidPermissionsManager = AndroidPermissionsManager(context, permissions, this, onPermissionChanged = onPermissionChangedFlow) packageInfo.requestedPermissions = permissions diff --git a/base-permissions/src/commonMain/kotlin/Permission.kt b/base-permissions/src/commonMain/kotlin/Permission.kt index e5d009917..1997c1139 100644 --- a/base-permissions/src/commonMain/kotlin/Permission.kt +++ b/base-permissions/src/commonMain/kotlin/Permission.kt @@ -108,7 +108,6 @@ open class PermissionsBuilder(val context: PermissionContext = defaultPermission repoBuilders.getOrPut(permission) { createPermissionStateRepoBuilder(permissionStateRepoBuilder) } as PermissionStateRepoBuilder

} - suspend fun

unregisterPermissionStateRepoBuilder(permission: P) { repoBuildersLock.withLock { repoBuilders.remove(permission::class) diff --git a/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt b/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt index 5fd2695a5..aaa7f2600 100644 --- a/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt +++ b/base-permissions/src/iosMain/kotlin/PermissionRefreshScheduler.kt @@ -78,12 +78,12 @@ class PermissionRefreshScheduler( val timerJobState = timerState if (timerJobState is TimerJobState.TimerNotRunning) { this.timerState = timerJobState.startTimer(interval, this) { - val status = currentAuthorizationStatusProvider.provide() - if (!waitingLock.isLocked && lastPermission != status) { - lastPermission = currentAuthorizationStatusProvider.provide() - onPermissionChangedFlow.status(status) - } + val status = currentAuthorizationStatusProvider.provide() + if (!waitingLock.isLocked && lastPermission != status) { + lastPermission = currentAuthorizationStatusProvider.provide() + onPermissionChangedFlow.status(status) } + } } } } diff --git a/base/src/commonMain/kotlin/state/KalugaState.kt b/base/src/commonMain/kotlin/state/KalugaState.kt index 450514936..5633e7bce 100644 --- a/base/src/commonMain/kotlin/state/KalugaState.kt +++ b/base/src/commonMain/kotlin/state/KalugaState.kt @@ -359,9 +359,11 @@ abstract class BaseHotStateRepo>( val isInitialized = lazyMutableSharedFlow.isInitialized() val flow = lazyMutableSharedFlow.value launch(coroutineContext) { - if (lock.withLock { + if ( + lock.withLock { (!isInitialized && !initialized).also { if (it) initialized = true } - }) { + } + ) { initialize() } } @@ -393,13 +395,13 @@ abstract class BaseColdStateRepo>( val isInitialized = lazyMutableFlow.isInitialized() val flow = lazyMutableFlow.value launch(coroutineContext) { - if (lock.withLock { + if ( + lock.withLock { (!isInitialized && !initialized).also { if (it) initialized = true } } - ) - { + ) { flow.onCollectionEvent { event -> when (event) { NoMoreCollections -> noMoreCollections().also { it.finalState() } diff --git a/base/src/commonTest/kotlin/AtomicReferenceTest.kt b/base/src/commonTest/kotlin/AtomicReferenceTest.kt deleted file mode 100644 index 30b4a9b22..000000000 --- a/base/src/commonTest/kotlin/AtomicReferenceTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2021 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class AtomicReferenceTest { - - @Test - fun testAtomicReferenceDelegate() { - - data class Class(val property: Int) - - var reference: Class? by AtomicReferenceDelegate(Class(0)) - assertEquals(0, reference?.property) - reference = Class(1) - assertEquals(1, reference?.property) - reference = null - assertNull(reference) - } -} diff --git a/base/src/iosMain/kotlin/GCScheduler.kt b/base/src/iosMain/kotlin/GCScheduler.kt index 9c00ee80f..0a655635c 100644 --- a/base/src/iosMain/kotlin/GCScheduler.kt +++ b/base/src/iosMain/kotlin/GCScheduler.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlin.native.internal.GC - object GCScheduler { private val collectingMutex = Mutex() diff --git a/beacons/src/commonMain/kotlin/Beacons.kt b/beacons/src/commonMain/kotlin/Beacons.kt index cf72c9fd3..9b2d8964d 100644 --- a/beacons/src/commonMain/kotlin/Beacons.kt +++ b/beacons/src/commonMain/kotlin/Beacons.kt @@ -34,13 +34,15 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds private typealias BeaconJob = Pair private typealias BeaconsMap = MutableMap class Beacons( private val bluetooth: BluetoothService, - private val timeoutMs: Long = 10_000, + private val timeout: Duration = 10.seconds, ) { private companion object { const val TAG = "Beacons" } @@ -48,7 +50,7 @@ class Beacons( private val cache = mutableMapOf() private val cacheJob = Job() private val coroutineScope = CoroutineScope(Dispatchers.Default + cacheJob) - private var monitoringJob: Job? by AtomicReferenceDelegate() + private var monitoringJob: Job? = null private val _beacons = MutableStateFlow(emptySet()) val beacons: StateFlow> @@ -92,7 +94,7 @@ class Beacons( } cache[beacon.beaconID] = beacon to coroutineScope.launch { debug(TAG, "[Added] $beacon") - delay(beacon.lastSeen.millisecondSinceEpoch + timeoutMs - DefaultKalugaDate.now().millisecondSinceEpoch) + delay(beacon.lastSeen.millisecondSinceEpoch + timeout.inWholeMilliseconds - DefaultKalugaDate.now().millisecondSinceEpoch) debug(TAG, "[Lost] $beacon") cache.remove(beacon.beaconID) updateList() diff --git a/beacons/src/commonTest/kotlin/BeaconFlowTest.kt b/beacons/src/commonTest/kotlin/BeaconFlowTest.kt index 55509481f..474c989b5 100644 --- a/beacons/src/commonTest/kotlin/BeaconFlowTest.kt +++ b/beacons/src/commonTest/kotlin/BeaconFlowTest.kt @@ -20,13 +20,15 @@ package com.splendo.kaluga.bluetooth.beacons import com.splendo.kaluga.bluetooth.device.Device import com.splendo.kaluga.test.base.SimpleFlowTest import kotlinx.coroutines.CoroutineScope +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds abstract class BeaconFlowTest( - timeoutMs: Long = 3_000 + timeout: Duration = 3.seconds ) : SimpleFlowTest>() { private val bluetooth = BluetoothMock(scope) - private val beacons = Beacons(bluetooth, timeoutMs = timeoutMs) + private val beacons = Beacons(bluetooth, timeout = timeout) override val flow = suspend { beacons.beacons } diff --git a/beacons/src/commonTest/kotlin/BeaconLostTest.kt b/beacons/src/commonTest/kotlin/BeaconLostTest.kt index b0703479f..d3aa02703 100644 --- a/beacons/src/commonTest/kotlin/BeaconLostTest.kt +++ b/beacons/src/commonTest/kotlin/BeaconLostTest.kt @@ -22,8 +22,9 @@ import kotlinx.coroutines.delay import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds -class BeaconLostTest : BeaconFlowTest(timeoutMs = 2_000) { +class BeaconLostTest : BeaconFlowTest(timeout = 2.seconds) { @Test fun testLostOnTimeout() = testWithFlow { diff --git a/bluetooth/src/androidLibMain/kotlin/BluetoothBuilder.kt b/bluetooth/src/androidLibMain/kotlin/BluetoothBuilder.kt index 4223453be..c4b40d3e3 100644 --- a/bluetooth/src/androidLibMain/kotlin/BluetoothBuilder.kt +++ b/bluetooth/src/androidLibMain/kotlin/BluetoothBuilder.kt @@ -8,17 +8,17 @@ import com.splendo.kaluga.bluetooth.scanner.DefaultScanner import com.splendo.kaluga.permissions.base.PermissionContext import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder -import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission -import com.splendo.kaluga.permissions.location.registerLocationPermission +import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermissionIfNotRegistered +import com.splendo.kaluga.permissions.location.registerLocationPermissionIfNotRegistered import kotlin.coroutines.CoroutineContext actual class BluetoothBuilder( private val applicationContext: Context = ApplicationHolder.applicationContext, - private val permissionsBuilder: (CoroutineContext) -> Permissions = { context -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( PermissionsBuilder(PermissionContext(applicationContext)).apply { - registerBluetoothPermission() - registerLocationPermission() + registerBluetoothPermissionIfNotRegistered() + registerLocationPermissionIfNotRegistered() }, coroutineContext = context ) diff --git a/bluetooth/src/commonMain/kotlin/Bluetooth.kt b/bluetooth/src/commonMain/kotlin/Bluetooth.kt index 7b415715c..74a22fa4e 100644 --- a/bluetooth/src/commonMain/kotlin/Bluetooth.kt +++ b/bluetooth/src/commonMain/kotlin/Bluetooth.kt @@ -64,7 +64,7 @@ interface BluetoothService { } class Bluetooth internal constructor( - scannerSettingsBuilder: (CoroutineContext) -> BaseScanner.Settings, + scannerSettingsBuilder: suspend (CoroutineContext) -> BaseScanner.Settings, connectionSettings: ConnectionSettings, scannerBuilder: BaseScanner.Builder, coroutineContext: CoroutineContext, diff --git a/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt b/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt index bcea97016..75540831a 100644 --- a/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt +++ b/bluetooth/src/commonMain/kotlin/device/DefaultDeviceConnectionManager.kt @@ -150,6 +150,7 @@ abstract class BaseDeviceConnectionManager( override fun handleDisconnect(onDisconnect: (suspend () -> Unit)?) { val currentAction = this.currentAction + currentAction?.completedSuccessfully?.cancel() val notifyingCharacteristics = this.notifyingCharacteristics val clean = suspend { this.currentAction = null diff --git a/bluetooth/src/commonMain/kotlin/scanner/ScanningStateRepo.kt b/bluetooth/src/commonMain/kotlin/scanner/ScanningStateRepo.kt index 9402245fd..3af108290 100644 --- a/bluetooth/src/commonMain/kotlin/scanner/ScanningStateRepo.kt +++ b/bluetooth/src/commonMain/kotlin/scanner/ScanningStateRepo.kt @@ -59,7 +59,7 @@ abstract class BaseScanningStateRepo( ) open class ScanningStateImplRepo( - createScanner: () -> Scanner, + createScanner: suspend () -> Scanner, private val createDevice: (Identifier, DeviceInfoImpl, DeviceWrapper, BaseDeviceConnectionManager.Builder) -> Device, coroutineContext: CoroutineContext ) : BaseScanningStateRepo( @@ -138,7 +138,7 @@ open class ScanningStateImplRepo( } class ScanningStateRepo( - settingsBuilder: (CoroutineContext) -> BaseScanner.Settings, + settingsBuilder: suspend (CoroutineContext) -> BaseScanner.Settings, builder: BaseScanner.Builder, createDevice: (Identifier, DeviceInfoImpl, DeviceWrapper, BaseDeviceConnectionManager.Builder) -> Device, coroutineContext: CoroutineContext, diff --git a/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt b/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt index 47a242696..4de81a1a4 100644 --- a/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt +++ b/bluetooth/src/commonTest/kotlin/BluetoothFlowTest.kt @@ -148,9 +148,7 @@ abstract class BluetoothFlowTest + permissionsBuilder.registerAllPermissionsBuilders() BaseScanner.Settings( Permissions( permissionsBuilder, diff --git a/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt b/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt index c057f30b8..92d5ce2e5 100644 --- a/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt +++ b/bluetooth/src/iosMain/kotlin/BluetoothBuilder.kt @@ -5,7 +5,7 @@ import com.splendo.kaluga.bluetooth.scanner.BaseScanner import com.splendo.kaluga.bluetooth.scanner.DefaultScanner import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder -import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission +import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermissionIfNotRegistered import platform.Foundation.NSBundle import kotlin.coroutines.CoroutineContext @@ -14,7 +14,7 @@ actual class BluetoothBuilder( private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( PermissionsBuilder(bundle).apply { - registerBluetoothPermission() + registerBluetoothPermissionIfNotRegistered() }, context ) diff --git a/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt b/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt index c2db6d850..b81a75686 100644 --- a/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt +++ b/bluetooth/src/iosMain/kotlin/device/DefaultDeviceConnectionManager.kt @@ -204,7 +204,7 @@ internal actual class DefaultDeviceConnectionManager( handleUpdatedDescriptor(descriptor.UUID, succeeded = error == null) } - private suspend fun didDiscoverServices() { + private fun didDiscoverServices() { launch { discoveringMutext.withLock { discoveringServices.addAll( @@ -234,7 +234,7 @@ internal actual class DefaultDeviceConnectionManager( } } - private suspend fun didDiscoverDescriptors(forCharacteristic: CBCharacteristic) { + private fun didDiscoverDescriptors(forCharacteristic: CBCharacteristic) { launch { discoveringMutext.withLock { discoveringCharacteristics.remove(forCharacteristic.UUID) diff --git a/bluetooth/src/jsMain/kotlin/BluetoothBuilder.kt b/bluetooth/src/jsMain/kotlin/BluetoothBuilder.kt index ef957e53e..bcf292f32 100644 --- a/bluetooth/src/jsMain/kotlin/BluetoothBuilder.kt +++ b/bluetooth/src/jsMain/kotlin/BluetoothBuilder.kt @@ -5,14 +5,14 @@ import com.splendo.kaluga.bluetooth.scanner.BaseScanner import com.splendo.kaluga.bluetooth.scanner.DefaultScanner import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder -import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission +import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermissionIfNotRegistered import kotlin.coroutines.CoroutineContext actual class BluetoothBuilder( - private val permissionsBuilder: (CoroutineContext) -> Permissions = { context -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( PermissionsBuilder().apply { - registerBluetoothPermission() + registerBluetoothPermissionIfNotRegistered() }, context ) diff --git a/bluetooth/src/jvmMain/kotlin/BluetoothBuilder.kt b/bluetooth/src/jvmMain/kotlin/BluetoothBuilder.kt index ef957e53e..bcf292f32 100644 --- a/bluetooth/src/jvmMain/kotlin/BluetoothBuilder.kt +++ b/bluetooth/src/jvmMain/kotlin/BluetoothBuilder.kt @@ -5,14 +5,14 @@ import com.splendo.kaluga.bluetooth.scanner.BaseScanner import com.splendo.kaluga.bluetooth.scanner.DefaultScanner import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder -import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission +import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermissionIfNotRegistered import kotlin.coroutines.CoroutineContext actual class BluetoothBuilder( - private val permissionsBuilder: (CoroutineContext) -> Permissions = { context -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( PermissionsBuilder().apply { - registerBluetoothPermission() + registerBluetoothPermissionIfNotRegistered() }, context ) diff --git a/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt b/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt index 5c109a805..edfd4d7c0 100644 --- a/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt +++ b/calendar-permissions/src/iosMain/kotlin/CalendarPermissionManager.kt @@ -18,9 +18,9 @@ package com.splendo.kaluga.permissions.calendar import com.splendo.kaluga.logging.error -import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.BasePermissionManager import com.splendo.kaluga.permissions.base.CurrentAuthorizationStatusProvider +import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.IOSPermissionsHelper import com.splendo.kaluga.permissions.base.PermissionContext import com.splendo.kaluga.permissions.base.PermissionRefreshScheduler @@ -35,7 +35,6 @@ import platform.EventKit.EKAuthorizationStatusRestricted import platform.EventKit.EKEntityType import platform.EventKit.EKEventStore import platform.Foundation.NSBundle -import platform.Foundation.NSError import kotlin.time.Duration const val NSCalendarsUsageDescription = "NSCalendarsUsageDescription" diff --git a/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt b/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt index df9135897..b4e3c0785 100644 --- a/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt +++ b/contacts-permissions/src/iosMain/kotlin/ContactsPermissionManager.kt @@ -18,9 +18,9 @@ package com.splendo.kaluga.permissions.contacts import com.splendo.kaluga.logging.error -import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.BasePermissionManager import com.splendo.kaluga.permissions.base.CurrentAuthorizationStatusProvider +import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.IOSPermissionsHelper import com.splendo.kaluga.permissions.base.PermissionContext import com.splendo.kaluga.permissions.base.PermissionRefreshScheduler @@ -35,7 +35,6 @@ import platform.Contacts.CNAuthorizationStatusRestricted import platform.Contacts.CNContactStore import platform.Contacts.CNEntityType import platform.Foundation.NSBundle -import platform.Foundation.NSError import kotlin.time.Duration const val NSContactsUsageDescription = "NSContactsUsageDescription" diff --git a/date-time-picker/src/androidLibAndroidTest/kotlin/AndroidDateTimePickerPresenterTest.kt b/date-time-picker/src/androidLibAndroidTest/kotlin/AndroidDateTimePickerPresenterTest.kt index 82e4f3aaa..6f996c5e4 100644 --- a/date-time-picker/src/androidLibAndroidTest/kotlin/AndroidDateTimePickerPresenterTest.kt +++ b/date-time-picker/src/androidLibAndroidTest/kotlin/AndroidDateTimePickerPresenterTest.kt @@ -37,6 +37,7 @@ import kotlin.test.Ignore import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds class AndroidDateTimePickerPresenterTest : DateTimePickerPresenterTests() { @@ -55,7 +56,7 @@ class AndroidDateTimePickerPresenterTest : DateTimePickerPresenterTests() { override val builder get() = activity!!.viewModel.dateTimePickerBuilder companion object { - const val DEFAULT_TIMEOUT = 20_000L + val DEFAULT_TIMEOUT = 20.seconds.inWholeMilliseconds } @Test diff --git a/date-time-picker/src/androidLibMain/kotlin/DateTimePickerPresenter.kt b/date-time-picker/src/androidLibMain/kotlin/DateTimePickerPresenter.kt index 93d4fbb68..d3bed6c38 100644 --- a/date-time-picker/src/androidLibMain/kotlin/DateTimePickerPresenter.kt +++ b/date-time-picker/src/androidLibMain/kotlin/DateTimePickerPresenter.kt @@ -45,7 +45,10 @@ actual class DateTimePickerPresenter( private val themeResourceId: Int = 0, private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver() ) : BaseDateTimePickerPresenter.Builder(), LifecycleSubscribable by lifecycleManagerObserver { - actual override fun create(coroutineScope: CoroutineScope) = DateTimePickerPresenter(createDateTimePicker(), themeResourceId, lifecycleManagerObserver, coroutineScope) + actual override fun create( + dateTimePicker: DateTimePicker, + coroutineScope: CoroutineScope + ) = DateTimePickerPresenter(dateTimePicker, themeResourceId, lifecycleManagerObserver, coroutineScope) } private sealed class DialogPresentation { diff --git a/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt b/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt index 2f0b3cd35..974bc4a69 100644 --- a/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt +++ b/date-time-picker/src/commonMain/kotlin/DateTimePicker.kt @@ -25,8 +25,6 @@ import com.splendo.kaluga.base.utils.Locale import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlin.coroutines.resume data class DateTimePicker( @@ -56,59 +54,13 @@ data class DateTimePicker( */ object TimeType : Type() } -} - -/** - * Interface that defines actions that can be applied to the alert. - */ -interface DateTimePickerActions { - /** - * Presents an DateTimePicker - * - * @param animated Pass `true` to animate the presentation - * @param completion The callback invoked when a Date is selected or the dialog is cancelled - */ - fun showAsync(animated: Boolean = true, completion: (KalugaDate?) -> Unit = {}) - - /** - * Presents an DateTimePicker and suspends - * - * @param animated - * @return The [KalugaDate] that was selected or `null` if the DateTimePicker was cancelled - */ - suspend fun show(animated: Boolean = true): KalugaDate? - - /** - * Dismisses the DateTimePicker, which was presented previously - * - * @param animated Pass `true` to animate the transition - */ - fun dismiss(animated: Boolean) -} - -/** - * Abstract DateTimePicker presenter, used to show and dismiss given [DateTimePicker] - * @see [DateTimePickerPresenter] - * - * @property dateTimePicker The alert to present (and dismiss if needed) - */ -abstract class BaseDateTimePickerPresenter(private val dateTimePicker: DateTimePicker) : DateTimePickerActions { - - /** - * Abstract alert builder class, used to create an [DateTimePicker]. - * The resulting DateTimePicker that can be shown and dismissed using an [DateTimePickerPresenter]. - * - * @see [DateTimePickerPresenter.Builder] - */ - abstract class Builder : LifecycleSubscribableMarker { + class Builder(private var type: Type = Type.TimeType) { private var message: String? = null private var cancelButtonTitle: String = "" private var confirmButtonTitle: String = "" private var locale: Locale = defaultLocale private var selectedDate: KalugaDate = DefaultKalugaDate.epoch() - private var type: DateTimePicker.Type = DateTimePicker.Type.TimeType - internal val lock = Mutex() /** * Sets the [message] displayed in the DateTimePicker @@ -139,39 +91,74 @@ abstract class BaseDateTimePickerPresenter(private val dateTimePicker: DateTimeP * * @param type The style of an alert */ - internal fun setType(type: DateTimePicker.Type) = apply { this.type = type } - - /** - * Reset builder into initial state - */ - internal fun reset() = apply { - this.message = null - this.cancelButtonTitle = "" - this.confirmButtonTitle = "" - this.locale = defaultLocale - this.selectedDate = DefaultKalugaDate.epoch() - this.type = DateTimePicker.Type.TimeType - } + internal fun setType(type: Type) = apply { this.type = type } /** - * Creates a DataTimePicker + * Creates a [DateTimePicker] * - * @return The DateTimePicker object + * @return The [DateTimePicker] object * @throws IllegalArgumentException in case missing cancel or confirm titles */ - protected fun createDateTimePicker(): DateTimePicker { + fun build(): DateTimePicker { require(cancelButtonTitle.isNotEmpty() && confirmButtonTitle.isNotEmpty()) { "Please set Cancel and Confirm Titles" } return DateTimePicker(message, cancelButtonTitle, confirmButtonTitle, type, locale, selectedDate) } + } +} + +/** + * Interface that defines actions that can be applied to the alert. + */ +interface DateTimePickerActions { + /** + * Presents an DateTimePicker + * + * @param animated Pass `true` to animate the presentation + * @param completion The callback invoked when a Date is selected or the dialog is cancelled + */ + fun showAsync(animated: Boolean = true, completion: (KalugaDate?) -> Unit = {}) + + /** + * Presents an DateTimePicker and suspends + * + * @param animated + * @return The [KalugaDate] that was selected or `null` if the DateTimePicker was cancelled + */ + suspend fun show(animated: Boolean = true): KalugaDate? + + /** + * Dismisses the DateTimePicker, which was presented previously + * + * @param animated Pass `true` to animate the transition + */ + fun dismiss(animated: Boolean) +} + +/** + * Abstract DateTimePicker presenter, used to show and dismiss given [DateTimePicker] + * @see [DateTimePickerPresenter] + * + * @property dateTimePicker The alert to present (and dismiss if needed) + */ +abstract class BaseDateTimePickerPresenter(private val dateTimePicker: DateTimePicker) : DateTimePickerActions { + + /** + * Abstract alert builder class, used to create an [DateTimePicker]. + * The resulting DateTimePicker that can be shown and dismissed using an [DateTimePickerPresenter]. + * + * @see [DateTimePickerPresenter.Builder] + */ + abstract class Builder : LifecycleSubscribableMarker { /** * Creates the [BaseDateTimePickerPresenter] described by this builder. * + * @param dateTimePicker The [DateTimePicker] to be presented with the built presenter * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. * @return The [BaseDateTimePickerPresenter] described by this builder. */ - abstract fun create(coroutineScope: CoroutineScope): BaseDateTimePickerPresenter + abstract fun create(dateTimePicker: DateTimePicker, coroutineScope: CoroutineScope): BaseDateTimePickerPresenter } override fun showAsync(animated: Boolean, completion: (KalugaDate?) -> Unit) { @@ -207,10 +194,11 @@ expect class DateTimePickerPresenter : BaseDateTimePickerPresenter { /** * Creates DateTimePickerPresenter object * + * @param dateTimePicker The [DateTimePicker] to be presented with the built presenter. * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. * @return The DateTimePickerPresenter object */ - override fun create(coroutineScope: CoroutineScope): DateTimePickerPresenter + override fun create(dateTimePicker: DateTimePicker, coroutineScope: CoroutineScope): DateTimePickerPresenter } } @@ -218,20 +206,20 @@ expect class DateTimePickerPresenter : BaseDateTimePickerPresenter { * Builds an alert using DSL syntax (thread safe) * * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. - * @param initialize The block to construct an Alert + * @param initialize The block to construct an [DateTimePicker] * @return The built alert interface object */ -suspend fun BaseDateTimePickerPresenter.Builder.buildDatePicker( +fun BaseDateTimePickerPresenter.Builder.buildDatePicker( coroutineScope: CoroutineScope, earliestDate: KalugaDate? = null, latestDate: KalugaDate? = null, - initialize: BaseDateTimePickerPresenter.Builder.() -> Unit -): BaseDateTimePickerPresenter = lock.withLock { - reset() - setType(DateTimePicker.Type.DateType(earliestDate, latestDate)) - initialize() - return create(coroutineScope) -} + initialize: DateTimePicker.Builder.() -> Unit +): BaseDateTimePickerPresenter = create( + DateTimePicker.Builder(DateTimePicker.Type.DateType(earliestDate, latestDate)).apply { + initialize() + }.build(), + coroutineScope +) /** * Builds an alert using DSL syntax (thread safe) @@ -240,9 +228,12 @@ suspend fun BaseDateTimePickerPresenter.Builder.buildDatePicker( * @param initialize The block to construct an Alert * @return The built alert interface object */ -suspend fun BaseDateTimePickerPresenter.Builder.buildTimePicker(coroutineScope: CoroutineScope, initialize: BaseDateTimePickerPresenter.Builder.() -> Unit): BaseDateTimePickerPresenter = lock.withLock { - reset() - setType(DateTimePicker.Type.TimeType) - initialize() - return create(coroutineScope) -} +fun BaseDateTimePickerPresenter.Builder.buildTimePicker( + coroutineScope: CoroutineScope, + initialize: DateTimePicker.Builder.() -> Unit +): BaseDateTimePickerPresenter = create( + DateTimePicker.Builder(DateTimePicker.Type.TimeType).apply { + initialize() + }.build(), + coroutineScope +) diff --git a/date-time-picker/src/iosMain/kotlin/DateTimePickerPresenter.kt b/date-time-picker/src/iosMain/kotlin/DateTimePickerPresenter.kt index ee1f7b492..fee0dc399 100644 --- a/date-time-picker/src/iosMain/kotlin/DateTimePickerPresenter.kt +++ b/date-time-picker/src/iosMain/kotlin/DateTimePickerPresenter.kt @@ -156,8 +156,10 @@ actual class DateTimePickerPresenter( } actual class Builder(private val viewController: UIViewController) : BaseDateTimePickerPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope) = - DateTimePickerPresenter(createDateTimePicker(), viewController) + actual override fun create( + dateTimePicker: DateTimePicker, + coroutineScope: CoroutineScope + ) = DateTimePickerPresenter(dateTimePicker, viewController) } override fun dismissDateTimePicker(animated: Boolean) { diff --git a/date-time-picker/src/jsMain/kotlin/DateTimePickerPresenter.kt b/date-time-picker/src/jsMain/kotlin/DateTimePickerPresenter.kt index fb8c4e791..53f834c8c 100644 --- a/date-time-picker/src/jsMain/kotlin/DateTimePickerPresenter.kt +++ b/date-time-picker/src/jsMain/kotlin/DateTimePickerPresenter.kt @@ -27,8 +27,8 @@ actual class DateTimePickerPresenter( actual class Builder : BaseDateTimePickerPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope): DateTimePickerPresenter { - return DateTimePickerPresenter(createDateTimePicker()) + actual override fun create(dateTimePicker: DateTimePicker, coroutineScope: CoroutineScope): DateTimePickerPresenter { + return DateTimePickerPresenter(dateTimePicker) } } diff --git a/date-time-picker/src/jvmMain/kotlin/DateTimePickerPresenter.kt b/date-time-picker/src/jvmMain/kotlin/DateTimePickerPresenter.kt index 26e82477f..ed9e7b7bf 100644 --- a/date-time-picker/src/jvmMain/kotlin/DateTimePickerPresenter.kt +++ b/date-time-picker/src/jvmMain/kotlin/DateTimePickerPresenter.kt @@ -26,8 +26,8 @@ actual class DateTimePickerPresenter( actual class Builder : BaseDateTimePickerPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope): DateTimePickerPresenter { - return DateTimePickerPresenter(createDateTimePicker()) + actual override fun create(dateTimePicker: DateTimePicker, coroutineScope: CoroutineScope): DateTimePickerPresenter { + return DateTimePickerPresenter(dateTimePicker) } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt b/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt index 7a20aa8d9..025e57dea 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt @@ -79,6 +79,7 @@ import com.splendo.kaluga.permissions.registerAllPermissions import com.splendo.kaluga.resources.StyledStringBuilder import com.splendo.kaluga.review.ReviewManager import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder +import kotlin.time.Duration.Companion.minutes import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module @@ -99,7 +100,7 @@ val utilitiesModule = module { single { BluetoothBuilder().create({ BaseScanner.Settings(get()) }) } - single { Beacons(get(), timeoutMs = 60_000) } + single { Beacons(get(), timeout = 1.minutes) } } val viewModelModule = module { diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt index e00f0884c..fe4e4166a 100644 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt +++ b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt @@ -22,6 +22,7 @@ import com.splendo.kaluga.bluetooth.BluetoothBuilder import com.splendo.kaluga.bluetooth.beacons.Beacons import com.splendo.kaluga.bluetooth.device.ConnectionSettings import com.splendo.kaluga.bluetooth.scanner.BaseScanner +import kotlin.time.Duration.Companion.minutes import kotlinx.coroutines.MainScope import permissions.KNPermissionsFramework @@ -33,6 +34,6 @@ class KNBeaconsFramework { ConnectionSettings(), singleThreadDispatcher("Bluetooth") ), - timeoutMs = 60_000 + timeout = 1.minutes ) } diff --git a/hud/src/androidLibAndroidTest/kotlin/AndroidHUDTests.kt b/hud/src/androidLibAndroidTest/kotlin/AndroidHUDTests.kt index 656bc4b11..a221be9c4 100644 --- a/hud/src/androidLibAndroidTest/kotlin/AndroidHUDTests.kt +++ b/hud/src/androidLibAndroidTest/kotlin/AndroidHUDTests.kt @@ -42,8 +42,9 @@ import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds -const val DEFAULT_TIMEOUT = 20_000L +val DEFAULT_TIMEOUT = 20.seconds.inWholeMilliseconds fun UiDevice.assertTextAppears(text: String) { waitForIdle() diff --git a/hud/src/commonMain/kotlin/HUD.kt b/hud/src/commonMain/kotlin/HUD.kt index 60c1e0bd0..983938e7f 100644 --- a/hud/src/commonMain/kotlin/HUD.kt +++ b/hud/src/commonMain/kotlin/HUD.kt @@ -23,8 +23,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock /** * Style of the Loading Indicator @@ -46,26 +44,6 @@ abstract class BaseHUD(coroutineScope: CoroutineScope) : CoroutineScope by corou */ abstract class Builder : LifecycleSubscribableMarker { - internal val lock = Mutex() - - /** The style of the loading indicator */ - internal var style: HUDStyle = HUDStyle.SYSTEM - - /** Sets the style for the loading indicator */ - fun setStyle(style: HUDStyle) = apply { this.style = style } - - /** The title of the loading indicator */ - internal var title: String? = null - - /** Set the title for the loading indicator */ - fun setTitle(title: String?) = apply { this.title = title } - - /** Sets default style and empty title */ - internal fun clear() { - setStyle(HUDStyle.SYSTEM) - setTitle(null) - } - /** */ /** * Builds a [BaseHUD] based on some [HudConfig]. @@ -142,8 +120,9 @@ suspend fun BaseHUD.presentDuring(animated: Boolean = true, block: suspend B * @param coroutineScope The [CoroutineScope] managing the HUD lifecycle. * @param initialize Method for initializing the [HUD.Builder] */ -suspend fun BaseHUD.Builder.build(coroutineScope: CoroutineScope, initialize: BaseHUD.Builder.() -> Unit = { }): BaseHUD = lock.withLock { - clear() - initialize() - return create(HudConfig(style, title), coroutineScope) -} +fun BaseHUD.Builder.build(coroutineScope: CoroutineScope, initialize: HudConfig.Builder.() -> Unit = { }): BaseHUD = create( + HudConfig.Builder().apply { + initialize() + }.build(), + coroutineScope +) diff --git a/hud/src/commonMain/kotlin/HudConfig.kt b/hud/src/commonMain/kotlin/HudConfig.kt index 409029297..c5fbfaa6f 100644 --- a/hud/src/commonMain/kotlin/HudConfig.kt +++ b/hud/src/commonMain/kotlin/HudConfig.kt @@ -23,4 +23,26 @@ data class HudConfig( val style: HUDStyle = HUDStyle.SYSTEM, /** Optional title of the HUD */ val title: String? = null -) +) { + + class Builder { + /** The style of the loading indicator */ + internal var style: HUDStyle = HUDStyle.SYSTEM + + /** Sets the style for the loading indicator */ + fun setStyle(style: HUDStyle) = apply { this.style = style } + + /** The title of the loading indicator */ + internal var title: String? = null + + /** Set the title for the loading indicator */ + fun setTitle(title: String?) = apply { this.title = title } + + /** + * Creates a [HudConfig] + * + * @return The [HudConfig] created + */ + fun build(): HudConfig = HudConfig(style, title) + } +} diff --git a/location/src/androidLibMain/kotlin/location/LocationManager.kt b/location/src/androidLibMain/kotlin/location/LocationManager.kt index e10a08597..fab305c9b 100644 --- a/location/src/androidLibMain/kotlin/location/LocationManager.kt +++ b/location/src/androidLibMain/kotlin/location/LocationManager.kt @@ -26,7 +26,7 @@ import com.splendo.kaluga.permissions.base.PermissionContext import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.location.registerLocationPermission +import com.splendo.kaluga.permissions.location.registerLocationPermissionIfNotRegistered import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -92,9 +92,9 @@ actual class DefaultLocationManager( actual class LocationStateRepoBuilder( private val context: Context = ApplicationHolder.applicationContext, - private val permissionsBuilder: (CoroutineContext) -> Permissions = { coroutineContext -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { coroutineContext -> Permissions( - PermissionsBuilder(PermissionContext(context)).apply { registerLocationPermission() }, + PermissionsBuilder(PermissionContext(context)).apply { registerLocationPermissionIfNotRegistered() }, coroutineContext ) } diff --git a/location/src/commonMain/kotlin/location/LocationStateRepo.kt b/location/src/commonMain/kotlin/location/LocationStateRepo.kt index bbdf047d9..188b45322 100644 --- a/location/src/commonMain/kotlin/location/LocationStateRepo.kt +++ b/location/src/commonMain/kotlin/location/LocationStateRepo.kt @@ -64,7 +64,7 @@ abstract class BaseLocationStateRepo( ) open class LocationStateImplRepo( - createLocationManager: () -> LocationManager, + createLocationManager: suspend () -> LocationManager, coroutineContext: CoroutineContext = Dispatchers.Main.immediate ) : BaseLocationStateRepo( createNotInitializedState = { LocationStateImpl.NotInitialized }, @@ -135,7 +135,7 @@ open class LocationStateImplRepo( * @param locationManagerBuilder The [BaseLocationManager.Builder] to create the [LocationManager] managing the location state. */ class LocationStateRepo( - settingsBuilder: (CoroutineContext) -> BaseLocationManager.Settings, + settingsBuilder: suspend (CoroutineContext) -> BaseLocationManager.Settings, builder: BaseLocationManager.Builder, coroutineContext: CoroutineContext ) : LocationStateImplRepo( diff --git a/location/src/commonTest/kotlin/location/LocationStateTest.kt b/location/src/commonTest/kotlin/location/LocationStateTest.kt index f9a56f328..56701931a 100644 --- a/location/src/commonTest/kotlin/location/LocationStateTest.kt +++ b/location/src/commonTest/kotlin/location/LocationStateTest.kt @@ -72,18 +72,19 @@ class LocationStateTest : TestContext { val permissionsBuilder: MockPermissionsBuilder = MockPermissionsBuilder( initialActiveState = configuration.initialPermissionState - ).apply { - registerAllPermissionsBuilders() - } - val permissions = Permissions( - permissionsBuilder, - coroutineContext = coroutineScope.coroutineContext - ).apply { - // Make sure permissionState has been created as it may break the tests otherwise - get(configuration.locationPermission) - } + ) + val locationStateRepoBuilder = MockLocationStateRepoBuilder( - permissions, + { + permissionsBuilder.registerAllPermissionsBuilders() + Permissions( + permissionsBuilder, + coroutineContext = coroutineScope.coroutineContext + ).apply { + // Make sure permissionState has been created as it may break the tests otherwise + get(configuration.locationPermission) + } + }, MockBaseLocationManager.Builder(configuration.locationEnabled) ) diff --git a/location/src/iosMain/kotlin/location/LocationManager.kt b/location/src/iosMain/kotlin/location/LocationManager.kt index 079454385..c7e7099cd 100644 --- a/location/src/iosMain/kotlin/location/LocationManager.kt +++ b/location/src/iosMain/kotlin/location/LocationManager.kt @@ -21,7 +21,7 @@ import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder import com.splendo.kaluga.permissions.location.LocationPermission import com.splendo.kaluga.permissions.location.MainCLLocationManagerAccessor -import com.splendo.kaluga.permissions.location.registerLocationPermission +import com.splendo.kaluga.permissions.location.registerLocationPermissionIfNotRegistered import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @@ -110,10 +110,10 @@ actual class DefaultLocationManager( actual class LocationStateRepoBuilder( private val bundle: NSBundle = NSBundle.mainBundle, - private val permissionsBuilder: (CoroutineContext) -> Permissions = { context -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( PermissionsBuilder(bundle).apply { - registerLocationPermission() + registerLocationPermissionIfNotRegistered() }, context ) diff --git a/location/src/jsMain/kotlin/LocationManager.kt b/location/src/jsMain/kotlin/LocationManager.kt index 55debc6e7..687a7f3a8 100644 --- a/location/src/jsMain/kotlin/LocationManager.kt +++ b/location/src/jsMain/kotlin/LocationManager.kt @@ -20,7 +20,7 @@ package com.splendo.kaluga.location import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.location.registerLocationPermission +import com.splendo.kaluga.permissions.location.registerLocationPermissionIfNotRegistered import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.CoroutineContext @@ -54,9 +54,9 @@ actual class DefaultLocationManager( } actual class LocationStateRepoBuilder( - private val permissionsBuilder: (CoroutineContext) -> Permissions = { context -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( - PermissionsBuilder().apply { registerLocationPermission() }, + PermissionsBuilder().apply { registerLocationPermissionIfNotRegistered() }, context ) } diff --git a/location/src/jvmMain/kotlin/LocationManager.kt b/location/src/jvmMain/kotlin/LocationManager.kt index 55debc6e7..687a7f3a8 100644 --- a/location/src/jvmMain/kotlin/LocationManager.kt +++ b/location/src/jvmMain/kotlin/LocationManager.kt @@ -20,7 +20,7 @@ package com.splendo.kaluga.location import com.splendo.kaluga.permissions.base.Permissions import com.splendo.kaluga.permissions.base.PermissionsBuilder import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.location.registerLocationPermission +import com.splendo.kaluga.permissions.location.registerLocationPermissionIfNotRegistered import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.CoroutineContext @@ -54,9 +54,9 @@ actual class DefaultLocationManager( } actual class LocationStateRepoBuilder( - private val permissionsBuilder: (CoroutineContext) -> Permissions = { context -> + private val permissionsBuilder: suspend (CoroutineContext) -> Permissions = { context -> Permissions( - PermissionsBuilder().apply { registerLocationPermission() }, + PermissionsBuilder().apply { registerLocationPermissionIfNotRegistered() }, context ) } diff --git a/logging/src/commonTest/kotlin/LogTest.kt b/logging/src/commonTest/kotlin/LogTest.kt index d4b9b57f2..506f39a46 100644 --- a/logging/src/commonTest/kotlin/LogTest.kt +++ b/logging/src/commonTest/kotlin/LogTest.kt @@ -18,51 +18,87 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.logging -import kotlin.test.AfterTest -import kotlin.test.BeforeTest +import com.splendo.kaluga.test.base.UIThreadTest +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.yield import kotlin.test.Test import kotlin.test.assertEquals -class LogTest { +class LogTest : UIThreadTest() { - private val mockLogger = LoggerMock() - @BeforeTest - fun setMockLogger() { - logger = mockLogger - } + class Context(scope: CoroutineScope) : TestContext { + val mockLogger = LoggerMock(scope) - @AfterTest - fun resetMockLogger() { - resetLogger() - } + init { + logger = mockLogger + } - private fun assertLog( - logLevel: LogLevel, - tag: String? = null, - throwable: Exception? = null, - message: String? = null - ) { - - listOf( - mockLogger.tagList.size, - mockLogger.levelList.size, - mockLogger.throwableList.size, - mockLogger.messageList.size, - mockLogger.levelList.size - ).forEach { - assertEquals(1, it) + suspend fun assertLog( + logLevel: LogLevel, + tag: String? = null, + throwable: Exception? = null, + message: String? = null + ) { + yield() + listOf( + mockLogger.tagList.size, + mockLogger.levelList.size, + mockLogger.throwableList.size, + mockLogger.messageList.size, + mockLogger.levelList.size + ).forEach { + assertEquals(1, it) + } + + assertEquals(logLevel, mockLogger.levelList[0]) + assertEquals(tag, mockLogger.tagList[0]) + assertEquals(throwable, mockLogger.throwableList[0]) + assertEquals(message, mockLogger.messageList[0]) + + mockLogger.clear() } - assertEquals(logLevel, mockLogger.levelList[0]) - assertEquals(tag, mockLogger.tagList[0]) - assertEquals(throwable, mockLogger.throwableList[0]) - assertEquals(message, mockLogger.messageList[0]) + suspend fun test( + logLevel: LogLevel, + method1: (tag: String?, message: String) -> Unit, + method2: (message: String) -> Unit, + method3: (tag: String?, throwable: Throwable) -> Unit, + method4: (throwable: Throwable) -> Unit, + method5: (tag: String?, message: () -> String) -> Unit, + method6: (message: () -> String) -> Unit, + method7: (tag: String?, throwable: Throwable?, message: () -> String) -> Unit, + ) { + val tag = logLevel.name.lowercase() + .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } + val message = logLevel.name.uppercase() + val throwable = RuntimeException(logLevel.name.lowercase()) + + method1(tag, message) + assertLog(logLevel, tag, message = message) + method2.invoke(message) + assertLog(logLevel, message = message) + method3(tag, throwable) + assertLog(logLevel, tag = tag, throwable = throwable) + method4(throwable) + assertLog(logLevel, throwable = throwable) + method5(tag) { message } + assertLog(logLevel, tag = tag, message = message) + method6 { message } + assertLog(logLevel, message = message) + method7(tag, throwable) { message } + assertLog(logLevel, tag, throwable, message) + } - mockLogger.clear() + override fun dispose() { + super.dispose() + resetLogger() + } } + override val createTestContext: suspend (scope: CoroutineScope) -> Context = { Context(it) } + @Test - fun testTransformer() { + fun testTransformer() = testOnUIThread { val exception = RuntimeException("original exception for testing") val wrappedException = RuntimeException("wrapped exception for testing", exception) val nullPointerException = NullPointerException() @@ -71,9 +107,14 @@ class LogTest { TransformLogger( logger, transformLogLevel = { LogLevel.ERROR }, - transformTag = { it?.toUpperCase() ?: "NULL" }, + transformTag = { it?.uppercase() ?: "NULL" }, transformThrowable = { it?.let { wrappedException } ?: nullPointerException }, - transformMessage = { it?.toLowerCase()?.capitalize() ?: "Null" } + transformMessage = { + it + ?.lowercase() + ?.replaceFirstChar { firstChar -> if (firstChar.isLowerCase()) firstChar.titlecase() else firstChar.toString() } + ?: "Null" + } ) } @@ -84,45 +125,15 @@ class LogTest { assertLog(LogLevel.ERROR, "NULL", nullPointerException, "Message") resetLogger() - setMockLogger() + logger = mockLogger debug("tAg", exception) { "mEsSaGe" } assertLog(LogLevel.DEBUG, "tAg", exception, "mEsSaGe") resetLogger() debug("we can still log") } - fun test( - logLevel: LogLevel, - method1: (tag: String?, message: String) -> Unit, - method2: (message: String) -> Unit, - method3: (tag: String?, throwable: Throwable) -> Unit, - method4: (throwable: Throwable) -> Unit, - method5: (tag: String?, message: () -> String) -> Unit, - method6: (message: () -> String) -> Unit, - method7: (tag: String?, throwable: Throwable?, message: () -> String) -> Unit, - ) { - val tag = logLevel.name.toLowerCase().capitalize() - val message = logLevel.name.toUpperCase() - val throwable = RuntimeException(logLevel.name.toLowerCase()) - - method1(tag, message) - assertLog(logLevel, tag, message = message) - method2.invoke(message) - assertLog(logLevel, message = message) - method3(tag, throwable) - assertLog(logLevel, tag = tag, throwable = throwable) - method4(throwable) - assertLog(logLevel, throwable = throwable) - method5(tag) { message } - assertLog(logLevel, tag = tag, message = message) - method6 { message } - assertLog(logLevel, message = message) - method7(tag, throwable) { message } - assertLog(logLevel, tag, throwable, message) - } - @Test - fun testDebug() = + fun testDebug() = testOnUIThread { test( logLevel = LogLevel.DEBUG, method1 = ::debug, @@ -133,9 +144,10 @@ class LogTest { method6 = ::debug, method7 = ::debug ) + } @Test - fun testD() = + fun testD() = testOnUIThread { test( logLevel = LogLevel.DEBUG, method1 = ::d, @@ -146,9 +158,10 @@ class LogTest { method6 = ::d, method7 = ::d ) + } @Test - fun testInfo() = + fun testInfo() = testOnUIThread { test( logLevel = LogLevel.INFO, method1 = ::info, @@ -159,9 +172,10 @@ class LogTest { method6 = ::info, method7 = ::info ) + } @Test - fun testI() = + fun testI() = testOnUIThread { test( logLevel = LogLevel.INFO, method1 = ::i, @@ -172,9 +186,10 @@ class LogTest { method6 = ::i, method7 = ::i ) + } @Test - fun testWarn() = + fun testWarn() = testOnUIThread { test( logLevel = LogLevel.WARN, method1 = ::warn, @@ -185,9 +200,10 @@ class LogTest { method6 = ::warn, method7 = ::warn ) + } @Test - fun testW() = + fun testW() = testOnUIThread { test( logLevel = LogLevel.WARN, method1 = ::w, @@ -198,9 +214,10 @@ class LogTest { method6 = ::w, method7 = ::w ) + } @Test - fun testError() = + fun testError() = testOnUIThread { test( logLevel = LogLevel.ERROR, method1 = ::error, @@ -211,9 +228,10 @@ class LogTest { method6 = ::error, method7 = ::error ) + } @Test - fun testE() = + fun testE() = testOnUIThread { test( logLevel = LogLevel.ERROR, method1 = ::e, @@ -224,4 +242,5 @@ class LogTest { method6 = ::e, method7 = ::e ) + } } diff --git a/logging/src/commonTest/kotlin/LoggerMock.kt b/logging/src/commonTest/kotlin/LoggerMock.kt index 016c59b97..5ddeb8468 100644 --- a/logging/src/commonTest/kotlin/LoggerMock.kt +++ b/logging/src/commonTest/kotlin/LoggerMock.kt @@ -43,7 +43,6 @@ class LoggerMock(private val coroutineScope: CoroutineScope) : Logger { } override fun log(level: LogLevel, tag: String?, throwable: Throwable?, message: (() -> String)?) { - coroutineScope.launch { mutex.withLock { levelList.add(level) diff --git a/notifications-permissions/src/commonMain/kotlin/Permission.kt b/notifications-permissions/src/commonMain/kotlin/Permission.kt index f37efc3a1..a9ef213d8 100644 --- a/notifications-permissions/src/commonMain/kotlin/Permission.kt +++ b/notifications-permissions/src/commonMain/kotlin/Permission.kt @@ -70,4 +70,3 @@ suspend fun PermissionsBuilder.registerNotificationsPermissionIfNotRegistered( notificationsPermissionStateRepoBuilder(permission, it, coroutineContext) } } - diff --git a/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt b/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt index 50d68197a..72d711ab8 100644 --- a/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt +++ b/notifications-permissions/src/iosMain/kotlin/NotificationsPermissionManager.kt @@ -18,9 +18,9 @@ package com.splendo.kaluga.permissions.notifications import com.splendo.kaluga.logging.error -import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.BasePermissionManager import com.splendo.kaluga.permissions.base.CurrentAuthorizationStatusProvider +import com.splendo.kaluga.permissions.base.DefaultAuthorizationStatusHandler import com.splendo.kaluga.permissions.base.IOSPermissionsHelper import com.splendo.kaluga.permissions.base.PermissionContext import com.splendo.kaluga.permissions.base.PermissionRefreshScheduler @@ -36,7 +36,6 @@ import platform.UserNotifications.UNAuthorizationStatusAuthorized import platform.UserNotifications.UNAuthorizationStatusDenied import platform.UserNotifications.UNAuthorizationStatusNotDetermined import platform.UserNotifications.UNAuthorizationStatusProvisional -import platform.UserNotifications.UNNotificationSettings import platform.UserNotifications.UNUserNotificationCenter import kotlin.time.Duration @@ -71,14 +70,12 @@ actual class DefaultNotificationsPermissionManager( override fun requestPermissionDidStart() { permissionHandler.requestAuthorizationStatus(timerHelper, CoroutineScope(coroutineContext)) { val deferred = CompletableDeferred() - val callback = { authorization: Boolean, error: NSError? -> + notificationCenter.requestAuthorizationWithOptions( + permission.options?.options ?: UNAuthorizationOptionNone + ) { authorization: Boolean, error: NSError? -> error?.let { deferred.completeExceptionally(Throwable(error.localizedDescription)) } ?: run { deferred.complete(authorization) } Unit - }.freeze() - notificationCenter.requestAuthorizationWithOptions( - permission.options?.options ?: UNAuthorizationOptionNone, - callback - ) + } try { if (deferred.await()) diff --git a/permissions/src/commonMain/kotlin/registerAllPermissions.kt b/permissions/src/commonMain/kotlin/registerAllPermissions.kt index e3990bab0..a5bc71753 100644 --- a/permissions/src/commonMain/kotlin/registerAllPermissions.kt +++ b/permissions/src/commonMain/kotlin/registerAllPermissions.kt @@ -29,7 +29,7 @@ import com.splendo.kaluga.permissions.notifications.registerNotificationsPermiss import com.splendo.kaluga.permissions.storage.registerStoragePermission import kotlin.time.Duration -fun PermissionsBuilder.registerAllPermissions( +suspend fun PermissionsBuilder.registerAllPermissions( monitoringInterval: Duration = PermissionStateRepo.defaultMonitoringInterval, settings: BasePermissionManager.Settings = BasePermissionManager.Settings() ) { diff --git a/resources/src/commonMain/kotlin/stylable/GradientStyle.kt b/resources/src/commonMain/kotlin/stylable/GradientStyle.kt index 5eb026eac..fac8ee338 100644 --- a/resources/src/commonMain/kotlin/stylable/GradientStyle.kt +++ b/resources/src/commonMain/kotlin/stylable/GradientStyle.kt @@ -24,7 +24,7 @@ sealed class GradientStyle(val colorPoints: List) { data class ColorPoint(val color: KalugaColor, val offset: Float) data class CenterPoint(val x: Float, val y: Float) - class Linear private constructor (colorPoints: List, val orientation: Orientation) : GradientStyle(colorPoints) { + class Linear private constructor(colorPoints: List, val orientation: Orientation) : GradientStyle(colorPoints) { enum class Orientation { BOTTOM_LEFT_TOP_RIGHT, BOTTOM_TOP, diff --git a/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt b/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt index 53ca90fd5..ab07b9f85 100644 --- a/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt +++ b/storage-permissions/src/iosMain/kotlin/StoragePermissionManager.kt @@ -58,7 +58,7 @@ actual class DefaultStoragePermissionManager( if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSPhotoLibraryUsageDescription).isEmpty()) { permissionHandler.requestAuthorizationStatus(timerHelper, CoroutineScope(coroutineContext)) { val deferred = CompletableDeferred() - PHPhotoLibrary.requestAuthorization { status -> + PHPhotoLibrary.requestAuthorization { status -> deferred.complete(status) Unit } diff --git a/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt b/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt index 5ea1922ab..4ff310e0b 100644 --- a/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt +++ b/test-utils-alerts/src/commonMain/kotlin/MockAlertPresenter.kt @@ -57,7 +57,7 @@ class MockAlertPresenter(val alert: Alert, setupMocks: Boolean = true) : BaseAle } } - override fun create(coroutineScope: CoroutineScope): BaseAlertPresenter = createAlertFromAlert(createAlert(), coroutineScope) + override fun create(alert: Alert, coroutineScope: CoroutineScope): BaseAlertPresenter = createAlertFromAlert(alert, coroutineScope) private fun createAlertFromAlert(alert: Alert, coroutineScope: CoroutineScope): MockAlertPresenter = createMock.call(alert, coroutineScope) } diff --git a/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt b/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt index b91a47220..9786e63e5 100644 --- a/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt +++ b/test-utils-base/src/commonMain/kotlin/mock/MockMethod.kt @@ -1,18 +1,18 @@ /* Copyright 2022 Splendo Consulting B.V. The Netherlands - + 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 com.splendo.kaluga.test.base.mock diff --git a/test-utils-base/src/commonMain/kotlin/testBlockingAndCancelScope.kt b/test-utils-base/src/commonMain/kotlin/testBlockingAndCancelScope.kt index 7b4a57abd..4f16907df 100644 --- a/test-utils-base/src/commonMain/kotlin/testBlockingAndCancelScope.kt +++ b/test-utils-base/src/commonMain/kotlin/testBlockingAndCancelScope.kt @@ -24,8 +24,7 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext class DeliberateCancellationException(val result: Any?) : - kotlinx.coroutines.CancellationException("Scope canceled deliberately by testAndCancelScope") { -} + kotlinx.coroutines.CancellationException("Scope canceled deliberately by testAndCancelScope") inline fun testBlockingAndCancelScope( context: CoroutineContext = EmptyCoroutineContext, diff --git a/test-utils-base/src/iosMain/kotlin/mainBackground.kt b/test-utils-base/src/iosMain/kotlin/mainBackground.kt index df4d54be8..84ef3eeec 100644 --- a/test-utils-base/src/iosMain/kotlin/mainBackground.kt +++ b/test-utils-base/src/iosMain/kotlin/mainBackground.kt @@ -21,14 +21,13 @@ import com.splendo.kaluga.logging.debug import platform.CoreFoundation.CFRunLoopRun import kotlin.native.concurrent.TransferMode import kotlin.native.concurrent.Worker -import kotlin.native.concurrent.freeze import kotlin.native.internal.test.testLauncherEntryPoint import kotlin.system.exitProcess fun mainBackground(args: Array) { debug("using background thread for iOS tests") val worker = Worker.start(name = "kaluga-test-background") - worker.execute(TransferMode.SAFE, { args.freeze() }) { + worker.execute(TransferMode.SAFE, { args }) { val result = testLauncherEntryPoint(it) exitProcess(result) } diff --git a/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt b/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt index 1755fec3a..93ebaae32 100644 --- a/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt +++ b/test-utils-location/src/commonMain/kotlin/MockLocationStateRepoBuilder.kt @@ -28,12 +28,12 @@ import kotlin.coroutines.CoroutineContext /** * Mock implementation of [LocationStateRepo.Builder] - * @param permissions The [Permissions] to request permissions from + * @param permissionsBuilder Builds the [Permissions] to request permissions from * @param locationManagerBuilder The [BaseLocationManager.Builder] for building the location manager * @param setupMocks If `true` sets up [createMock] to automatically create a [LocationStateRepo] */ class MockLocationStateRepoBuilder( - private val permissions: Permissions, + private val permissionsBuilder: suspend () -> Permissions, val locationManagerBuilder: LMB, setupMocks: Boolean = true ) : LocationStateRepo.Builder { @@ -53,7 +53,7 @@ class MockLocationStateRepoBuilder( createMock.on() .doExecute { (locationPermission, settingsBuilder, coroutineContext) -> LocationStateRepo( - { settingsBuilder(locationPermission, permissions) }, + { settingsBuilder(locationPermission, permissionsBuilder()) }, locationManagerBuilder, coroutineContext ).also { diff --git a/test-utils/src/commonMain/kotlin/legacy.kt b/test-utils/src/commonMain/kotlin/legacy.kt index d0613752e..e5d454b8a 100644 --- a/test-utils/src/commonMain/kotlin/legacy.kt +++ b/test-utils/src/commonMain/kotlin/legacy.kt @@ -110,9 +110,8 @@ abstract class KoinUIThreadViewModelTest awaitAllBlocking(vararg deferreds: Deferred): List = com.splendo.kaluga.test.base.awaitAllBlocking(*deferreds) @Deprecated("Moved to test-utils-base", ReplaceWith("captureFor(duration)", "com.splendo.kaluga.test.base.captureFor")) suspend fun Flow.captureFor(duration: Duration): List = this.captureFor(duration) -@Deprecated("Moved to test-utils-base", ReplaceWith("testBlockingAndCancelScope(context, freezeResult, block)", "com.splendo.kaluga.test.base.testBlockingAndCancelScope")) +@Deprecated("Moved to test-utils-base", ReplaceWith("testBlockingAndCancelScope(context, block)", "com.splendo.kaluga.test.base.testBlockingAndCancelScope")) inline fun testBlockingAndCancelScope( context: CoroutineContext = EmptyCoroutineContext, - freezeResult: Boolean = true, crossinline block: suspend CoroutineScope.() -> T -) = com.splendo.kaluga.test.base.testBlockingAndCancelScope(context, freezeResult, block) +) = com.splendo.kaluga.test.base.testBlockingAndCancelScope(context, block) From ba83af52ea6080ec127210792fcc561d0456dcce Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 4 Nov 2022 11:24:45 +0100 Subject: [PATCH 008/227] API update --- alerts/api/androidLib/alerts.api | 38 +++++++++++-------- alerts/api/jvm/alerts.api | 38 +++++++++++-------- architecture/api/androidLib/architecture.api | 31 +++++---------- architecture/api/jvm/architecture.api | 31 +++++---------- .../api/androidLib/base-permissions.api | 16 +++++++- base-permissions/api/jvm/base-permissions.api | 16 +++++++- base/api/androidLib/base.api | 10 +---- base/api/jvm/base.api | 8 ---- beacons/api/androidLib/beacons.api | 2 +- beacons/api/jvm/beacons.api | 2 +- .../api/androidLib/bluetooth-permissions.api | 12 ++++-- .../api/jvm/bluetooth-permissions.api | 12 ++++-- bluetooth/api/androidLib/bluetooth.api | 10 ++--- bluetooth/api/jvm/bluetooth.api | 10 ++--- .../api/androidLib/calendar-permissions.api | 12 ++++-- .../api/jvm/calendar-permissions.api | 12 ++++-- .../api/androidLib/camera-permissions.api | 12 ++++-- .../api/jvm/camera-permissions.api | 12 ++++-- .../api/androidLib/contacts-permissions.api | 12 ++++-- .../api/jvm/contacts-permissions.api | 12 ++++-- .../api/androidLib/date-time-picker.api | 24 +++++++----- date-time-picker/api/jvm/date-time-picker.api | 24 +++++++----- hud/api/androidLib/hud.api | 9 ++++- hud/api/jvm/hud.api | 9 ++++- .../api/androidLib/location-permissions.api | 12 ++++-- .../api/jvm/location-permissions.api | 12 ++++-- location/api/androidLib/location.api | 10 ++--- location/api/jvm/location.api | 10 ++--- .../api/androidLib/microphone-permissions.api | 12 ++++-- .../api/jvm/microphone-permissions.api | 12 ++++-- .../androidLib/notifications-permissions.api | 12 ++++-- .../api/jvm/notifications-permissions.api | 12 ++++-- permissions/api/androidLib/permissions.api | 4 +- permissions/api/jvm/permissions.api | 4 +- .../api/androidLib/storage-permissions.api | 12 ++++-- .../api/jvm/storage-permissions.api | 12 ++++-- .../api/androidLib/test-utils-alerts.api | 5 ++- .../api/jvm/test-utils-alerts.api | 5 ++- .../androidLib/test-utils-architecture.api | 6 --- .../api/jvm/test-utils-architecture.api | 6 --- .../api/androidLib/test-utils-base.api | 18 ++------- test-utils-base/api/jvm/test-utils-base.api | 18 ++------- .../api/androidLib/test-utils-bluetooth.api | 6 +-- .../api/jvm/test-utils-bluetooth.api | 4 +- .../api/androidLib/test-utils-hud.api | 3 +- test-utils-hud/api/jvm/test-utils-hud.api | 3 +- .../api/androidLib/test-utils-keyboard.api | 4 +- .../api/jvm/test-utils-keyboard.api | 4 +- .../api/androidLib/test-utils-koin.api | 8 ---- test-utils-koin/api/jvm/test-utils-koin.api | 8 ---- .../api/androidLib/test-utils-location.api | 8 ++-- .../api/jvm/test-utils-location.api | 8 ++-- .../api/androidLib/test-utils-permissions.api | 20 +++++----- .../api/jvm/test-utils-permissions.api | 20 +++++----- test-utils/api/androidLib/test-utils.api | 10 ----- test-utils/api/jvm/test-utils.api | 10 ----- 56 files changed, 347 insertions(+), 325 deletions(-) diff --git a/alerts/api/androidLib/alerts.api b/alerts/api/androidLib/alerts.api index d865d736f..dc725ffc2 100644 --- a/alerts/api/androidLib/alerts.api +++ b/alerts/api/androidLib/alerts.api @@ -46,6 +46,25 @@ public final class com/splendo/kaluga/alerts/Alert$Action$Style : java/lang/Enum public static fun values ()[Lcom/splendo/kaluga/alerts/Alert$Action$Style; } +public final class com/splendo/kaluga/alerts/Alert$Builder { + public fun ()V + public fun (Lcom/splendo/kaluga/alerts/Alert$Style;)V + public synthetic fun (Lcom/splendo/kaluga/alerts/Alert$Style;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun addActions (Ljava/util/List;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun addActions ([Lcom/splendo/kaluga/alerts/Alert$Action;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun build ()Lcom/splendo/kaluga/alerts/Alert; + public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setNegativeButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setNegativeButton$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setNeutralButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setNeutralButton$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setPositiveButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setPositiveButton$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setTextInput (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setTextInput$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/Alert$Builder; +} + public final class com/splendo/kaluga/alerts/Alert$Style : java/lang/Enum { public static final field ACTION_LIST Lcom/splendo/kaluga/alerts/Alert$Style; public static final field ALERT Lcom/splendo/kaluga/alerts/Alert$Style; @@ -88,8 +107,8 @@ public final class com/splendo/kaluga/alerts/AlertPresenter$Builder : com/splend public fun ()V public fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;)V public synthetic fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/AlertPresenter; - public synthetic fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; + public fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/AlertPresenter; + public synthetic fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; public fun getManager ()Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager; public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager;)V public fun unsubscribe ()V @@ -118,19 +137,6 @@ public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter : com/splendo public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { public fun ()V - public final fun addActions (Ljava/util/List;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun addActions ([Lcom/splendo/kaluga/alerts/Alert$Action;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public abstract fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; - protected final fun createAlert ()Lcom/splendo/kaluga/alerts/Alert; - public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setNegativeButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setNegativeButton$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setNeutralButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setNeutralButton$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setPositiveButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setPositiveButton$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setTextInput (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setTextInput$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; + public abstract fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; } diff --git a/alerts/api/jvm/alerts.api b/alerts/api/jvm/alerts.api index d454aa9e5..525ae6b10 100644 --- a/alerts/api/jvm/alerts.api +++ b/alerts/api/jvm/alerts.api @@ -46,6 +46,25 @@ public final class com/splendo/kaluga/alerts/Alert$Action$Style : java/lang/Enum public static fun values ()[Lcom/splendo/kaluga/alerts/Alert$Action$Style; } +public final class com/splendo/kaluga/alerts/Alert$Builder { + public fun ()V + public fun (Lcom/splendo/kaluga/alerts/Alert$Style;)V + public synthetic fun (Lcom/splendo/kaluga/alerts/Alert$Style;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun addActions (Ljava/util/List;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun addActions ([Lcom/splendo/kaluga/alerts/Alert$Action;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun build ()Lcom/splendo/kaluga/alerts/Alert; + public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setNegativeButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setNegativeButton$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setNeutralButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setNeutralButton$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setPositiveButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setPositiveButton$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setTextInput (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public static synthetic fun setTextInput$default (Lcom/splendo/kaluga/alerts/Alert$Builder;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/Alert$Builder; + public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/Alert$Builder; +} + public final class com/splendo/kaluga/alerts/Alert$Style : java/lang/Enum { public static final field ACTION_LIST Lcom/splendo/kaluga/alerts/Alert$Style; public static final field ALERT Lcom/splendo/kaluga/alerts/Alert$Style; @@ -90,8 +109,8 @@ public final class com/splendo/kaluga/alerts/AlertPresenter : com/splendo/kaluga public final class com/splendo/kaluga/alerts/AlertPresenter$Builder : com/splendo/kaluga/alerts/BaseAlertPresenter$Builder { public fun ()V - public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/AlertPresenter; - public synthetic fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; + public fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/AlertPresenter; + public synthetic fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; } public final class com/splendo/kaluga/alerts/AlertsKt { @@ -113,19 +132,6 @@ public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter : com/splendo public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { public fun ()V - public final fun addActions (Ljava/util/List;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun addActions ([Lcom/splendo/kaluga/alerts/Alert$Action;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public abstract fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; - protected final fun createAlert ()Lcom/splendo/kaluga/alerts/Alert; - public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setNegativeButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setNegativeButton$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setNeutralButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setNeutralButton$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setPositiveButton (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setPositiveButton$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setTextInput (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public static synthetic fun setTextInput$default (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; - public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder; + public abstract fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; } diff --git a/architecture/api/androidLib/architecture.api b/architecture/api/androidLib/architecture.api index d93bda323..ea3093f16 100644 --- a/architecture/api/androidLib/architecture.api +++ b/architecture/api/androidLib/architecture.api @@ -1360,10 +1360,9 @@ public abstract class com/splendo/kaluga/architecture/observable/BaseObservable public abstract class com/splendo/kaluga/architecture/observable/BaseSimpleDisposable : com/splendo/kaluga/architecture/observable/Disposable { public fun (Lkotlin/jvm/functions/Function0;)V - public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V + public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected fun afterDispose ()V - public fun dispose ()V - public final fun isDisposed ()Z + public fun dispose (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract class com/splendo/kaluga/architecture/observable/BaseSubject : com/splendo/kaluga/architecture/observable/AbstractBaseSubject { @@ -1419,18 +1418,16 @@ public abstract interface class com/splendo/kaluga/architecture/observable/Defau } public abstract interface class com/splendo/kaluga/architecture/observable/Disposable { - public abstract fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V - public abstract fun dispose ()V + public abstract fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun dispose (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/splendo/kaluga/architecture/observable/DisposeBag : com/splendo/kaluga/architecture/observable/Disposable { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun add (Lcom/splendo/kaluga/architecture/observable/Disposable;)V - public final fun add (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V - public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V - public fun dispose ()V + public final fun add (Lcom/splendo/kaluga/architecture/observable/Disposable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun add (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispose (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/splendo/kaluga/architecture/observable/FlowInitializedObservable : com/splendo/kaluga/architecture/observable/BaseInitializedObservable { @@ -1760,8 +1757,6 @@ public abstract interface class com/splendo/kaluga/architecture/observable/WithS public class com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel : com/splendo/kaluga/architecture/viewmodel/LifecycleViewModel { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun didPause ()V public final fun didResume ()V protected fun onPause ()V @@ -1770,8 +1765,6 @@ public class com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel : public class com/splendo/kaluga/architecture/viewmodel/BaseViewModel : com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class com/splendo/kaluga/architecture/viewmodel/KalugaViewModelActivity : androidx/appcompat/app/AppCompatActivity { @@ -1804,7 +1797,6 @@ public final class com/splendo/kaluga/architecture/viewmodel/KalugaViewModelLife } public class com/splendo/kaluga/architecture/viewmodel/LifecycleViewModel : androidx/lifecycle/ViewModel { - public fun ()V public final fun getCoroutineScope ()Lkotlinx/coroutines/CoroutineScope; protected fun onCleared ()V } @@ -1814,15 +1806,12 @@ public final class com/splendo/kaluga/architecture/viewmodel/LifecycleViewModelK public static final fun bind (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Landroidx/fragment/app/Fragment;)Z } -public class com/splendo/kaluga/architecture/viewmodel/NavigatingViewModel : com/splendo/kaluga/architecture/viewmodel/BaseViewModel { - public fun (Lcom/splendo/kaluga/architecture/navigation/Navigator;Z)V - public synthetic fun (Lcom/splendo/kaluga/architecture/navigation/Navigator;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V +public class com/splendo/kaluga/architecture/viewmodel/NavigatingViewModel : com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel { + public fun (Lcom/splendo/kaluga/architecture/navigation/Navigator;)V public final fun getNavigator ()Lcom/splendo/kaluga/architecture/navigation/Navigator; } public class com/splendo/kaluga/architecture/viewmodel/ViewModel : com/splendo/kaluga/architecture/viewmodel/LifecycleViewModel { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } diff --git a/architecture/api/jvm/architecture.api b/architecture/api/jvm/architecture.api index a3f73463a..0e78abd77 100644 --- a/architecture/api/jvm/architecture.api +++ b/architecture/api/jvm/architecture.api @@ -622,10 +622,9 @@ public abstract class com/splendo/kaluga/architecture/observable/BaseObservable public abstract class com/splendo/kaluga/architecture/observable/BaseSimpleDisposable : com/splendo/kaluga/architecture/observable/Disposable { public fun (Lkotlin/jvm/functions/Function0;)V - public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V + public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected fun afterDispose ()V - public fun dispose ()V - public final fun isDisposed ()Z + public fun dispose (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract class com/splendo/kaluga/architecture/observable/BaseSubject : com/splendo/kaluga/architecture/observable/AbstractBaseSubject { @@ -675,18 +674,16 @@ public abstract interface class com/splendo/kaluga/architecture/observable/Defau } public abstract interface class com/splendo/kaluga/architecture/observable/Disposable { - public abstract fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V - public abstract fun dispose ()V + public abstract fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun dispose (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/splendo/kaluga/architecture/observable/DisposeBag : com/splendo/kaluga/architecture/observable/Disposable { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun add (Lcom/splendo/kaluga/architecture/observable/Disposable;)V - public final fun add (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V - public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;)V - public fun dispose ()V + public final fun add (Lcom/splendo/kaluga/architecture/observable/Disposable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun add (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun addTo (Lcom/splendo/kaluga/architecture/observable/DisposeBag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun dispose (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/splendo/kaluga/architecture/observable/FlowInitializedObservable : com/splendo/kaluga/architecture/observable/BaseInitializedObservable { @@ -1015,8 +1012,6 @@ public abstract interface class com/splendo/kaluga/architecture/observable/WithS public class com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel : com/splendo/kaluga/architecture/viewmodel/LifecycleViewModel { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun didPause ()V public final fun didResume ()V protected fun onPause ()V @@ -1025,26 +1020,20 @@ public class com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel : public class com/splendo/kaluga/architecture/viewmodel/BaseViewModel : com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/architecture/viewmodel/LifecycleViewModel { - public fun ()V public final fun clear ()V public final fun getCoroutineScope ()Lkotlinx/coroutines/CoroutineScope; protected fun onCleared ()V } -public class com/splendo/kaluga/architecture/viewmodel/NavigatingViewModel : com/splendo/kaluga/architecture/viewmodel/BaseViewModel { - public fun (Lcom/splendo/kaluga/architecture/navigation/Navigator;Z)V - public synthetic fun (Lcom/splendo/kaluga/architecture/navigation/Navigator;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V +public class com/splendo/kaluga/architecture/viewmodel/NavigatingViewModel : com/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel { + public fun (Lcom/splendo/kaluga/architecture/navigation/Navigator;)V public final fun getNavigator ()Lcom/splendo/kaluga/architecture/navigation/Navigator; } public class com/splendo/kaluga/architecture/viewmodel/ViewModel : com/splendo/kaluga/architecture/viewmodel/LifecycleViewModel { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } diff --git a/base-permissions/api/androidLib/base-permissions.api b/base-permissions/api/androidLib/base-permissions.api index 32e86932f..7b8b7d750 100644 --- a/base-permissions/api/androidLib/base-permissions.api +++ b/base-permissions/api/androidLib/base-permissions.api @@ -267,6 +267,10 @@ public final class com/splendo/kaluga/permissions/base/PermissionStateRepo$Compa public final fun getDefaultMonitoringInterval-UwyO8pc ()J } +public abstract interface class com/splendo/kaluga/permissions/base/PermissionStateRepoBuilder { + public abstract fun create (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/permissions/base/BasePermissionStateRepo; +} + public final class com/splendo/kaluga/permissions/base/Permissions { public fun (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/coroutines/CoroutineContext;)V public synthetic fun (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -294,7 +298,15 @@ public class com/splendo/kaluga/permissions/base/PermissionsBuilder { public final fun createPermissionStateRepo (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/permissions/base/BasePermissionStateRepo; public final fun get (Lcom/splendo/kaluga/permissions/base/Permission;)Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder; public final fun getContext ()Lcom/splendo/kaluga/permissions/base/PermissionContext; - public final fun register (Lkotlin/reflect/KClass;Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder;)Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder; - public final fun registerPermissionStateRepoBuilder (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;)V + public final fun register (Lkotlin/reflect/KClass;Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerOrGet (Lkotlin/reflect/KClass;Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerOrGetPermissionStateRepoBuilder (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerPermissionStateRepoBuilder (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unregister (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unregisterPermissionStateRepoBuilder (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/splendo/kaluga/permissions/base/PermissionsBuilderError : java/lang/Error { + public fun (Ljava/lang/String;)V } diff --git a/base-permissions/api/jvm/base-permissions.api b/base-permissions/api/jvm/base-permissions.api index 78f03389f..e7049f2e4 100644 --- a/base-permissions/api/jvm/base-permissions.api +++ b/base-permissions/api/jvm/base-permissions.api @@ -311,6 +311,10 @@ public final class com/splendo/kaluga/permissions/base/PermissionStateRepo$Compa public final fun getDefaultMonitoringInterval-UwyO8pc ()J } +public abstract interface class com/splendo/kaluga/permissions/base/PermissionStateRepoBuilder { + public abstract fun create (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/permissions/base/BasePermissionStateRepo; +} + public final class com/splendo/kaluga/permissions/base/Permissions { public fun (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/coroutines/CoroutineContext;)V public synthetic fun (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -326,7 +330,15 @@ public class com/splendo/kaluga/permissions/base/PermissionsBuilder { public final fun createPermissionStateRepo (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/permissions/base/BasePermissionStateRepo; public final fun get (Lcom/splendo/kaluga/permissions/base/Permission;)Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder; public final fun getContext ()Lcom/splendo/kaluga/permissions/base/PermissionContext; - public final fun register (Lkotlin/reflect/KClass;Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder;)Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder; - public final fun registerPermissionStateRepoBuilder (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;)V + public final fun register (Lkotlin/reflect/KClass;Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerOrGet (Lkotlin/reflect/KClass;Lcom/splendo/kaluga/permissions/base/BasePermissionsBuilder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerOrGetPermissionStateRepoBuilder (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerPermissionStateRepoBuilder (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unregister (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun unregisterPermissionStateRepoBuilder (Lcom/splendo/kaluga/permissions/base/Permission;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/splendo/kaluga/permissions/base/PermissionsBuilderError : java/lang/Error { + public fun (Ljava/lang/String;)V } diff --git a/base/api/androidLib/base.api b/base/api/androidLib/base.api index 07a9c0c0f..f68375283 100644 --- a/base/api/androidLib/base.api +++ b/base/api/androidLib/base.api @@ -9,14 +9,6 @@ public final class com/splendo/kaluga/base/ApplicationHolder$Companion { public final fun setApplication (Landroid/app/Application;)V } -public final class com/splendo/kaluga/base/AtomicReferenceDelegate { - public fun ()V - public fun (Ljava/lang/Object;)V - public synthetic fun (Ljava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; - public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V -} - public final class com/splendo/kaluga/base/KalugaThread { public static final field Companion Lcom/splendo/kaluga/base/KalugaThread$Companion; public fun (Ljava/lang/Thread;)V @@ -1001,7 +993,9 @@ public abstract class com/splendo/kaluga/state/HotStateRepo : com/splendo/kaluga public abstract interface class com/splendo/kaluga/state/KalugaState { public fun finalState (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun finalState$suspendImpl (Lcom/splendo/kaluga/state/KalugaState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun initialState (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun initialState$suspendImpl (Lcom/splendo/kaluga/state/KalugaState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remain ()Lkotlin/jvm/functions/Function1; } diff --git a/base/api/jvm/base.api b/base/api/jvm/base.api index 168de0bed..ffa73bc04 100644 --- a/base/api/jvm/base.api +++ b/base/api/jvm/base.api @@ -1,11 +1,3 @@ -public final class com/splendo/kaluga/base/AtomicReferenceDelegate { - public fun ()V - public fun (Ljava/lang/Object;)V - public synthetic fun (Ljava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; - public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V -} - public final class com/splendo/kaluga/base/KalugaThread { public static final field Companion Lcom/splendo/kaluga/base/KalugaThread$Companion; public fun (Ljava/lang/Thread;)V diff --git a/beacons/api/androidLib/beacons.api b/beacons/api/androidLib/beacons.api index 1832b5cb8..874a396c9 100644 --- a/beacons/api/androidLib/beacons.api +++ b/beacons/api/androidLib/beacons.api @@ -29,8 +29,8 @@ public final class com/splendo/kaluga/bluetooth/beacons/BeaconInfoKt { public final class com/splendo/kaluga/bluetooth/beacons/Beacons { public static final field TAG Ljava/lang/String; - public fun (Lcom/splendo/kaluga/bluetooth/BluetoothService;J)V public synthetic fun (Lcom/splendo/kaluga/bluetooth/BluetoothService;JILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lcom/splendo/kaluga/bluetooth/BluetoothService;JLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getBeacons ()Lkotlinx/coroutines/flow/StateFlow; public final fun isAnyInRange (Ljava/util/List;)Lkotlinx/coroutines/flow/Flow; public final fun isMonitoring (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/beacons/api/jvm/beacons.api b/beacons/api/jvm/beacons.api index 38b1efc74..28f7788ff 100644 --- a/beacons/api/jvm/beacons.api +++ b/beacons/api/jvm/beacons.api @@ -29,8 +29,8 @@ public final class com/splendo/kaluga/bluetooth/beacons/BeaconInfoKt { public final class com/splendo/kaluga/bluetooth/beacons/Beacons { public static final field TAG Ljava/lang/String; - public fun (Lcom/splendo/kaluga/bluetooth/BluetoothService;J)V public synthetic fun (Lcom/splendo/kaluga/bluetooth/BluetoothService;JILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lcom/splendo/kaluga/bluetooth/BluetoothService;JLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getBeacons ()Lkotlinx/coroutines/flow/StateFlow; public final fun isAnyInRange (Ljava/util/List;)Lkotlinx/coroutines/flow/Flow; public final fun isMonitoring (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/bluetooth-permissions/api/androidLib/bluetooth-permissions.api b/bluetooth-permissions/api/androidLib/bluetooth-permissions.api index c28f77cae..5d63b610f 100644 --- a/bluetooth-permissions/api/androidLib/bluetooth-permissions.api +++ b/bluetooth-permissions/api/androidLib/bluetooth-permissions.api @@ -25,9 +25,13 @@ public final class com/splendo/kaluga/permissions/bluetooth/DefaultBluetoothPerm } public final class com/splendo/kaluga/permissions/bluetooth/PermissionKt { - public static final fun registerBluetoothPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; - public static synthetic fun registerBluetoothPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; - public static final fun registerBluetoothPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; - public static synthetic fun registerBluetoothPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; + public static final fun registerBluetoothPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerBluetoothPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerBluetoothPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerBluetoothPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/bluetooth-permissions/api/jvm/bluetooth-permissions.api b/bluetooth-permissions/api/jvm/bluetooth-permissions.api index 37a3ae408..d675140ad 100644 --- a/bluetooth-permissions/api/jvm/bluetooth-permissions.api +++ b/bluetooth-permissions/api/jvm/bluetooth-permissions.api @@ -28,9 +28,13 @@ public final class com/splendo/kaluga/permissions/bluetooth/DefaultBluetoothPerm } public final class com/splendo/kaluga/permissions/bluetooth/PermissionKt { - public static final fun registerBluetoothPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; - public static synthetic fun registerBluetoothPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; - public static final fun registerBluetoothPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; - public static synthetic fun registerBluetoothPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/bluetooth/BaseBluetoothPermissionManagerBuilder; + public static final fun registerBluetoothPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerBluetoothPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerBluetoothPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerBluetoothPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerBluetoothPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/bluetooth/api/androidLib/bluetooth.api b/bluetooth/api/androidLib/bluetooth.api index 9ee60f1f4..89872a138 100644 --- a/bluetooth/api/androidLib/bluetooth.api +++ b/bluetooth/api/androidLib/bluetooth.api @@ -39,8 +39,8 @@ public final class com/splendo/kaluga/bluetooth/Bluetooth$ScanMode$Stopped : com public final class com/splendo/kaluga/bluetooth/BluetoothBuilder : com/splendo/kaluga/bluetooth/Bluetooth$Builder { public fun ()V - public fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;)V - public synthetic fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Landroid/content/Context;Lkotlin/jvm/functions/Function2;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;)V + public synthetic fun (Landroid/content/Context;Lkotlin/jvm/functions/Function2;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/bluetooth/device/ConnectionSettings;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/bluetooth/Bluetooth; } @@ -293,7 +293,7 @@ public abstract class com/splendo/kaluga/bluetooth/device/BaseDeviceConnectionMa protected final fun getCurrentAction ()Lcom/splendo/kaluga/bluetooth/device/DeviceAction; public final fun getDeviceWrapper ()Lcom/splendo/kaluga/bluetooth/device/DeviceWrapper; public fun getEvents ()Lkotlinx/coroutines/flow/Flow; - protected final fun getNotifyingCharacteristics ()Lco/touchlab/stately/collections/IsoMutableMap; + protected final fun getNotifyingCharacteristics ()Ljava/util/Map; public synthetic fun getRssi ()Lkotlinx/coroutines/flow/Flow; public fun getRssi ()Lkotlinx/coroutines/flow/SharedFlow; public fun handleConnect ()V @@ -1195,10 +1195,10 @@ public final class com/splendo/kaluga/bluetooth/scanner/ScanningStateImpl$Permit } public class com/splendo/kaluga/bluetooth/scanner/ScanningStateImplRepo : com/splendo/kaluga/bluetooth/scanner/BaseScanningStateRepo { - public fun (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V } public final class com/splendo/kaluga/bluetooth/scanner/ScanningStateRepo : com/splendo/kaluga/bluetooth/scanner/ScanningStateImplRepo { - public fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V + public fun (Lkotlin/jvm/functions/Function2;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V } diff --git a/bluetooth/api/jvm/bluetooth.api b/bluetooth/api/jvm/bluetooth.api index 71d809511..3538ffc3e 100644 --- a/bluetooth/api/jvm/bluetooth.api +++ b/bluetooth/api/jvm/bluetooth.api @@ -42,8 +42,8 @@ public final class com/splendo/kaluga/bluetooth/Bluetooth$ScanMode$Stopped : com public final class com/splendo/kaluga/bluetooth/BluetoothBuilder : com/splendo/kaluga/bluetooth/Bluetooth$Builder { public fun ()V - public fun (Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/bluetooth/device/ConnectionSettings;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/bluetooth/Bluetooth; } @@ -255,7 +255,7 @@ public abstract class com/splendo/kaluga/bluetooth/device/BaseDeviceConnectionMa protected final fun getCurrentAction ()Lcom/splendo/kaluga/bluetooth/device/DeviceAction; public final fun getDeviceWrapper ()Lcom/splendo/kaluga/bluetooth/device/DeviceWrapper; public fun getEvents ()Lkotlinx/coroutines/flow/Flow; - protected final fun getNotifyingCharacteristics ()Lco/touchlab/stately/collections/IsoMutableMap; + protected final fun getNotifyingCharacteristics ()Ljava/util/Map; public synthetic fun getRssi ()Lkotlinx/coroutines/flow/Flow; public fun getRssi ()Lkotlinx/coroutines/flow/SharedFlow; public fun handleConnect ()V @@ -1315,10 +1315,10 @@ public final class com/splendo/kaluga/bluetooth/scanner/ScanningStateImpl$Permit } public class com/splendo/kaluga/bluetooth/scanner/ScanningStateImplRepo : com/splendo/kaluga/bluetooth/scanner/BaseScanningStateRepo { - public fun (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V } public final class com/splendo/kaluga/bluetooth/scanner/ScanningStateRepo : com/splendo/kaluga/bluetooth/scanner/ScanningStateImplRepo { - public fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V + public fun (Lkotlin/jvm/functions/Function2;Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Builder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/CoroutineContext;)V } diff --git a/calendar-permissions/api/androidLib/calendar-permissions.api b/calendar-permissions/api/androidLib/calendar-permissions.api index 7bb7cd64c..f24800078 100644 --- a/calendar-permissions/api/androidLib/calendar-permissions.api +++ b/calendar-permissions/api/androidLib/calendar-permissions.api @@ -34,9 +34,13 @@ public final class com/splendo/kaluga/permissions/calendar/DefaultCalendarPermis } public final class com/splendo/kaluga/permissions/calendar/PermissionKt { - public static final fun registerCalendarPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; - public static synthetic fun registerCalendarPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; - public static final fun registerCalendarPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; - public static synthetic fun registerCalendarPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; + public static final fun registerCalendarPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCalendarPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCalendarPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCalendarPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/calendar-permissions/api/jvm/calendar-permissions.api b/calendar-permissions/api/jvm/calendar-permissions.api index 93f9e3f34..c519e3c56 100644 --- a/calendar-permissions/api/jvm/calendar-permissions.api +++ b/calendar-permissions/api/jvm/calendar-permissions.api @@ -37,9 +37,13 @@ public final class com/splendo/kaluga/permissions/calendar/DefaultCalendarPermis } public final class com/splendo/kaluga/permissions/calendar/PermissionKt { - public static final fun registerCalendarPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; - public static synthetic fun registerCalendarPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; - public static final fun registerCalendarPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; - public static synthetic fun registerCalendarPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/calendar/BaseCalendarPermissionManagerBuilder; + public static final fun registerCalendarPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCalendarPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCalendarPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCalendarPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCalendarPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/camera-permissions/api/androidLib/camera-permissions.api b/camera-permissions/api/androidLib/camera-permissions.api index c8801154a..18e3b739d 100644 --- a/camera-permissions/api/androidLib/camera-permissions.api +++ b/camera-permissions/api/androidLib/camera-permissions.api @@ -25,9 +25,13 @@ public final class com/splendo/kaluga/permissions/camera/DefaultCameraPermission } public final class com/splendo/kaluga/permissions/camera/PermissionKt { - public static final fun registerCameraPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; - public static synthetic fun registerCameraPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; - public static final fun registerCameraPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; - public static synthetic fun registerCameraPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; + public static final fun registerCameraPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCameraPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCameraPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCameraPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/camera-permissions/api/jvm/camera-permissions.api b/camera-permissions/api/jvm/camera-permissions.api index c60f8f42d..7a0799b4a 100644 --- a/camera-permissions/api/jvm/camera-permissions.api +++ b/camera-permissions/api/jvm/camera-permissions.api @@ -28,9 +28,13 @@ public final class com/splendo/kaluga/permissions/camera/DefaultCameraPermission } public final class com/splendo/kaluga/permissions/camera/PermissionKt { - public static final fun registerCameraPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; - public static synthetic fun registerCameraPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; - public static final fun registerCameraPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; - public static synthetic fun registerCameraPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/camera/BaseCameraPermissionManagerBuilder; + public static final fun registerCameraPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCameraPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCameraPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerCameraPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerCameraPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/contacts-permissions/api/androidLib/contacts-permissions.api b/contacts-permissions/api/androidLib/contacts-permissions.api index 19d9af11f..b52e79285 100644 --- a/contacts-permissions/api/androidLib/contacts-permissions.api +++ b/contacts-permissions/api/androidLib/contacts-permissions.api @@ -34,9 +34,13 @@ public final class com/splendo/kaluga/permissions/contacts/DefaultContactsPermis } public final class com/splendo/kaluga/permissions/contacts/PermissionKt { - public static final fun registerContactsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; - public static synthetic fun registerContactsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; - public static final fun registerContactsPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; - public static synthetic fun registerContactsPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; + public static final fun registerContactsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerContactsPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerContactsPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerContactsPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/contacts-permissions/api/jvm/contacts-permissions.api b/contacts-permissions/api/jvm/contacts-permissions.api index 2973d3cc8..bc2a3b513 100644 --- a/contacts-permissions/api/jvm/contacts-permissions.api +++ b/contacts-permissions/api/jvm/contacts-permissions.api @@ -37,9 +37,13 @@ public final class com/splendo/kaluga/permissions/contacts/DefaultContactsPermis } public final class com/splendo/kaluga/permissions/contacts/PermissionKt { - public static final fun registerContactsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; - public static synthetic fun registerContactsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; - public static final fun registerContactsPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; - public static synthetic fun registerContactsPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/contacts/BaseContactsPermissionManagerBuilder; + public static final fun registerContactsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerContactsPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerContactsPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerContactsPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerContactsPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/date-time-picker/api/androidLib/date-time-picker.api b/date-time-picker/api/androidLib/date-time-picker.api index 2a6488f0c..7b1d6874c 100644 --- a/date-time-picker/api/androidLib/date-time-picker.api +++ b/date-time-picker/api/androidLib/date-time-picker.api @@ -11,13 +11,7 @@ public abstract class com/splendo/kaluga/datetimepicker/BaseDateTimePickerPresen public abstract class com/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { public fun ()V - public abstract fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; - protected final fun createDateTimePicker ()Lcom/splendo/kaluga/datetimepicker/DateTimePicker; - public final fun setCancelButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setConfirmButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setLocale (Lcom/splendo/kaluga/base/utils/Locale;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setSelectedDate (Lcom/splendo/kaluga/base/utils/KalugaDate;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; + public abstract fun create (Lcom/splendo/kaluga/datetimepicker/DateTimePicker;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; } public final class com/splendo/kaluga/datetimepicker/DateTimePicker { @@ -41,6 +35,18 @@ public final class com/splendo/kaluga/datetimepicker/DateTimePicker { public fun toString ()Ljava/lang/String; } +public final class com/splendo/kaluga/datetimepicker/DateTimePicker$Builder { + public fun ()V + public fun (Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Type;)V + public synthetic fun (Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Type;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lcom/splendo/kaluga/datetimepicker/DateTimePicker; + public final fun setCancelButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setConfirmButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setLocale (Lcom/splendo/kaluga/base/utils/Locale;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setSelectedDate (Lcom/splendo/kaluga/base/utils/KalugaDate;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; +} + public abstract class com/splendo/kaluga/datetimepicker/DateTimePicker$Type { } @@ -80,8 +86,8 @@ public final class com/splendo/kaluga/datetimepicker/DateTimePickerPresenter$Bui public fun ()V public fun (ILcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;)V public synthetic fun (ILcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; - public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/DateTimePickerPresenter; + public synthetic fun create (Lcom/splendo/kaluga/datetimepicker/DateTimePicker;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; + public fun create (Lcom/splendo/kaluga/datetimepicker/DateTimePicker;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/DateTimePickerPresenter; public fun getManager ()Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager; public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager;)V public fun unsubscribe ()V diff --git a/date-time-picker/api/jvm/date-time-picker.api b/date-time-picker/api/jvm/date-time-picker.api index befd156eb..2a0fd0189 100644 --- a/date-time-picker/api/jvm/date-time-picker.api +++ b/date-time-picker/api/jvm/date-time-picker.api @@ -11,13 +11,7 @@ public abstract class com/splendo/kaluga/datetimepicker/BaseDateTimePickerPresen public abstract class com/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { public fun ()V - public abstract fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; - protected final fun createDateTimePicker ()Lcom/splendo/kaluga/datetimepicker/DateTimePicker; - public final fun setCancelButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setConfirmButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setLocale (Lcom/splendo/kaluga/base/utils/Locale;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; - public final fun setSelectedDate (Lcom/splendo/kaluga/base/utils/KalugaDate;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder; + public abstract fun create (Lcom/splendo/kaluga/datetimepicker/DateTimePicker;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; } public final class com/splendo/kaluga/datetimepicker/DateTimePicker { @@ -41,6 +35,18 @@ public final class com/splendo/kaluga/datetimepicker/DateTimePicker { public fun toString ()Ljava/lang/String; } +public final class com/splendo/kaluga/datetimepicker/DateTimePicker$Builder { + public fun ()V + public fun (Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Type;)V + public synthetic fun (Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Type;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lcom/splendo/kaluga/datetimepicker/DateTimePicker; + public final fun setCancelButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setConfirmButtonTitle (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setLocale (Lcom/splendo/kaluga/base/utils/Locale;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setMessage (Ljava/lang/String;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; + public final fun setSelectedDate (Lcom/splendo/kaluga/base/utils/KalugaDate;)Lcom/splendo/kaluga/datetimepicker/DateTimePicker$Builder; +} + public abstract class com/splendo/kaluga/datetimepicker/DateTimePicker$Type { } @@ -82,7 +88,7 @@ public final class com/splendo/kaluga/datetimepicker/DateTimePickerPresenter : c public final class com/splendo/kaluga/datetimepicker/DateTimePickerPresenter$Builder : com/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter$Builder { public fun ()V - public synthetic fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; - public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/DateTimePickerPresenter; + public synthetic fun create (Lcom/splendo/kaluga/datetimepicker/DateTimePicker;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/BaseDateTimePickerPresenter; + public fun create (Lcom/splendo/kaluga/datetimepicker/DateTimePicker;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/datetimepicker/DateTimePickerPresenter; } diff --git a/hud/api/androidLib/hud.api b/hud/api/androidLib/hud.api index ef8ff2005..c2be6f212 100644 --- a/hud/api/androidLib/hud.api +++ b/hud/api/androidLib/hud.api @@ -16,8 +16,6 @@ public abstract class com/splendo/kaluga/hud/BaseHUD : kotlinx/coroutines/Corout public abstract class com/splendo/kaluga/hud/BaseHUD$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { public fun ()V public abstract fun create (Lcom/splendo/kaluga/hud/HudConfig;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/hud/BaseHUD; - public final fun setStyle (Lcom/splendo/kaluga/hud/HUDStyle;)Lcom/splendo/kaluga/hud/BaseHUD$Builder; - public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/hud/BaseHUD$Builder; } public final class com/splendo/kaluga/hud/HUD : com/splendo/kaluga/hud/BaseHUD { @@ -72,3 +70,10 @@ public final class com/splendo/kaluga/hud/HudConfig { public fun toString ()Ljava/lang/String; } +public final class com/splendo/kaluga/hud/HudConfig$Builder { + public fun ()V + public final fun build ()Lcom/splendo/kaluga/hud/HudConfig; + public final fun setStyle (Lcom/splendo/kaluga/hud/HUDStyle;)Lcom/splendo/kaluga/hud/HudConfig$Builder; + public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/hud/HudConfig$Builder; +} + diff --git a/hud/api/jvm/hud.api b/hud/api/jvm/hud.api index 18f0a666f..596c8a70c 100644 --- a/hud/api/jvm/hud.api +++ b/hud/api/jvm/hud.api @@ -12,8 +12,6 @@ public abstract class com/splendo/kaluga/hud/BaseHUD : kotlinx/coroutines/Corout public abstract class com/splendo/kaluga/hud/BaseHUD$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { public fun ()V public abstract fun create (Lcom/splendo/kaluga/hud/HudConfig;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/hud/BaseHUD; - public final fun setStyle (Lcom/splendo/kaluga/hud/HUDStyle;)Lcom/splendo/kaluga/hud/BaseHUD$Builder; - public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/hud/BaseHUD$Builder; } public final class com/splendo/kaluga/hud/HUD : com/splendo/kaluga/hud/BaseHUD { @@ -63,3 +61,10 @@ public final class com/splendo/kaluga/hud/HudConfig { public fun toString ()Ljava/lang/String; } +public final class com/splendo/kaluga/hud/HudConfig$Builder { + public fun ()V + public final fun build ()Lcom/splendo/kaluga/hud/HudConfig; + public final fun setStyle (Lcom/splendo/kaluga/hud/HUDStyle;)Lcom/splendo/kaluga/hud/HudConfig$Builder; + public final fun setTitle (Ljava/lang/String;)Lcom/splendo/kaluga/hud/HudConfig$Builder; +} + diff --git a/location-permissions/api/androidLib/location-permissions.api b/location-permissions/api/androidLib/location-permissions.api index a457b760f..6231a107d 100644 --- a/location-permissions/api/androidLib/location-permissions.api +++ b/location-permissions/api/androidLib/location-permissions.api @@ -36,9 +36,13 @@ public final class com/splendo/kaluga/permissions/location/LocationPermissionSta } public final class com/splendo/kaluga/permissions/location/PermissionKt { - public static final fun registerLocationPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; - public static synthetic fun registerLocationPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; - public static final fun registerLocationPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; - public static synthetic fun registerLocationPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; + public static final fun registerLocationPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerLocationPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerLocationPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerLocationPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/location-permissions/api/jvm/location-permissions.api b/location-permissions/api/jvm/location-permissions.api index cadf3f6eb..f336a04df 100644 --- a/location-permissions/api/jvm/location-permissions.api +++ b/location-permissions/api/jvm/location-permissions.api @@ -39,9 +39,13 @@ public final class com/splendo/kaluga/permissions/location/LocationPermissionSta } public final class com/splendo/kaluga/permissions/location/PermissionKt { - public static final fun registerLocationPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; - public static synthetic fun registerLocationPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; - public static final fun registerLocationPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; - public static synthetic fun registerLocationPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/location/BaseLocationPermissionManagerBuilder; + public static final fun registerLocationPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerLocationPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerLocationPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerLocationPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerLocationPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/location/api/androidLib/location.api b/location/api/androidLib/location.api index 9c275cfd5..f7264932f 100644 --- a/location/api/androidLib/location.api +++ b/location/api/androidLib/location.api @@ -443,8 +443,8 @@ public final class com/splendo/kaluga/location/LocationStateImpl$PermittedHandle } public class com/splendo/kaluga/location/LocationStateImplRepo : com/splendo/kaluga/location/BaseLocationStateRepo { - public fun (Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/CoroutineContext;)V - public synthetic fun (Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/CoroutineContext;)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class com/splendo/kaluga/location/LocationStateKt { @@ -453,7 +453,7 @@ public final class com/splendo/kaluga/location/LocationStateKt { } public final class com/splendo/kaluga/location/LocationStateRepo : com/splendo/kaluga/location/LocationStateImplRepo { - public fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Lkotlin/coroutines/CoroutineContext;)V + public fun (Lkotlin/jvm/functions/Function2;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Lkotlin/coroutines/CoroutineContext;)V } public abstract interface class com/splendo/kaluga/location/LocationStateRepo$Builder { @@ -463,8 +463,8 @@ public abstract interface class com/splendo/kaluga/location/LocationStateRepo$Bu public final class com/splendo/kaluga/location/LocationStateRepoBuilder : com/splendo/kaluga/location/LocationStateRepo$Builder { public fun ()V - public fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Landroid/content/Context;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Landroid/content/Context;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/permissions/location/LocationPermission;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/location/LocationStateRepo; } diff --git a/location/api/jvm/location.api b/location/api/jvm/location.api index e1da9fb39..1980faeb4 100644 --- a/location/api/jvm/location.api +++ b/location/api/jvm/location.api @@ -473,8 +473,8 @@ public final class com/splendo/kaluga/location/LocationStateImpl$PermittedHandle } public class com/splendo/kaluga/location/LocationStateImplRepo : com/splendo/kaluga/location/BaseLocationStateRepo { - public fun (Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/CoroutineContext;)V - public synthetic fun (Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/CoroutineContext;)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class com/splendo/kaluga/location/LocationStateKt { @@ -483,7 +483,7 @@ public final class com/splendo/kaluga/location/LocationStateKt { } public final class com/splendo/kaluga/location/LocationStateRepo : com/splendo/kaluga/location/LocationStateImplRepo { - public fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Lkotlin/coroutines/CoroutineContext;)V + public fun (Lkotlin/jvm/functions/Function2;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Lkotlin/coroutines/CoroutineContext;)V } public abstract interface class com/splendo/kaluga/location/LocationStateRepo$Builder { @@ -496,8 +496,8 @@ public final class com/splendo/kaluga/location/LocationStateRepo$Builder$Default public final class com/splendo/kaluga/location/LocationStateRepoBuilder : com/splendo/kaluga/location/LocationStateRepo$Builder { public fun ()V - public fun (Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/permissions/location/LocationPermission;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/location/LocationStateRepo; } diff --git a/microphone-permissions/api/androidLib/microphone-permissions.api b/microphone-permissions/api/androidLib/microphone-permissions.api index 7837168b8..11bfce5a7 100644 --- a/microphone-permissions/api/androidLib/microphone-permissions.api +++ b/microphone-permissions/api/androidLib/microphone-permissions.api @@ -25,9 +25,13 @@ public final class com/splendo/kaluga/permissions/microphone/MicrophonePermissio } public final class com/splendo/kaluga/permissions/microphone/PermissionKt { - public static final fun registerMicrophonePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; - public static synthetic fun registerMicrophonePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; - public static final fun registerMicrophonePermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; - public static synthetic fun registerMicrophonePermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; + public static final fun registerMicrophonePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerMicrophonePermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerMicrophonePermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerMicrophonePermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/microphone-permissions/api/jvm/microphone-permissions.api b/microphone-permissions/api/jvm/microphone-permissions.api index e0c07d1ef..0dee42186 100644 --- a/microphone-permissions/api/jvm/microphone-permissions.api +++ b/microphone-permissions/api/jvm/microphone-permissions.api @@ -28,9 +28,13 @@ public final class com/splendo/kaluga/permissions/microphone/MicrophonePermissio } public final class com/splendo/kaluga/permissions/microphone/PermissionKt { - public static final fun registerMicrophonePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; - public static synthetic fun registerMicrophonePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; - public static final fun registerMicrophonePermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; - public static synthetic fun registerMicrophonePermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/microphone/BaseMicrophonePermissionManagerBuilder; + public static final fun registerMicrophonePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerMicrophonePermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerMicrophonePermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerMicrophonePermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerMicrophonePermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/notifications-permissions/api/androidLib/notifications-permissions.api b/notifications-permissions/api/androidLib/notifications-permissions.api index bb479759b..07871ee96 100644 --- a/notifications-permissions/api/androidLib/notifications-permissions.api +++ b/notifications-permissions/api/androidLib/notifications-permissions.api @@ -38,9 +38,13 @@ public final class com/splendo/kaluga/permissions/notifications/NotificationsPer } public final class com/splendo/kaluga/permissions/notifications/PermissionKt { - public static final fun registerNotificationsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; - public static synthetic fun registerNotificationsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; - public static final fun registerNotificationsPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; - public static synthetic fun registerNotificationsPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; + public static final fun registerNotificationsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerNotificationsPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerNotificationsPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerNotificationsPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/notifications-permissions/api/jvm/notifications-permissions.api b/notifications-permissions/api/jvm/notifications-permissions.api index 38c90684f..1fa58a542 100644 --- a/notifications-permissions/api/jvm/notifications-permissions.api +++ b/notifications-permissions/api/jvm/notifications-permissions.api @@ -41,9 +41,13 @@ public final class com/splendo/kaluga/permissions/notifications/NotificationsPer } public final class com/splendo/kaluga/permissions/notifications/PermissionKt { - public static final fun registerNotificationsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; - public static synthetic fun registerNotificationsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; - public static final fun registerNotificationsPermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; - public static synthetic fun registerNotificationsPermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/notifications/BaseNotificationsPermissionManagerBuilder; + public static final fun registerNotificationsPermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerNotificationsPermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerNotificationsPermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerNotificationsPermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerNotificationsPermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/permissions/api/androidLib/permissions.api b/permissions/api/androidLib/permissions.api index 0aa4131f6..e31d48e29 100644 --- a/permissions/api/androidLib/permissions.api +++ b/permissions/api/androidLib/permissions.api @@ -1,5 +1,5 @@ public final class com/splendo/kaluga/permissions/RegisterAllPermissionsKt { - public static final fun registerAllPermissions-8Mi8wO0 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)V - public static synthetic fun registerAllPermissions-8Mi8wO0$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)V + public static final fun registerAllPermissions-dWUq8MI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerAllPermissions-dWUq8MI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/permissions/api/jvm/permissions.api b/permissions/api/jvm/permissions.api index 0aa4131f6..e31d48e29 100644 --- a/permissions/api/jvm/permissions.api +++ b/permissions/api/jvm/permissions.api @@ -1,5 +1,5 @@ public final class com/splendo/kaluga/permissions/RegisterAllPermissionsKt { - public static final fun registerAllPermissions-8Mi8wO0 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)V - public static synthetic fun registerAllPermissions-8Mi8wO0$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)V + public static final fun registerAllPermissions-dWUq8MI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerAllPermissions-dWUq8MI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } diff --git a/storage-permissions/api/androidLib/storage-permissions.api b/storage-permissions/api/androidLib/storage-permissions.api index 98257be06..92c791b14 100644 --- a/storage-permissions/api/androidLib/storage-permissions.api +++ b/storage-permissions/api/androidLib/storage-permissions.api @@ -8,10 +8,14 @@ public final class com/splendo/kaluga/permissions/storage/DefaultStoragePermissi } public final class com/splendo/kaluga/permissions/storage/PermissionKt { - public static final fun registerStoragePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; - public static synthetic fun registerStoragePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; - public static final fun registerStoragePermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; - public static synthetic fun registerStoragePermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; + public static final fun registerStoragePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerStoragePermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerStoragePermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerStoragePermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class com/splendo/kaluga/permissions/storage/StoragePermission : com/splendo/kaluga/permissions/base/Permission { diff --git a/storage-permissions/api/jvm/storage-permissions.api b/storage-permissions/api/jvm/storage-permissions.api index 98b1dfba8..6b9655f38 100644 --- a/storage-permissions/api/jvm/storage-permissions.api +++ b/storage-permissions/api/jvm/storage-permissions.api @@ -11,10 +11,14 @@ public final class com/splendo/kaluga/permissions/storage/DefaultStoragePermissi } public final class com/splendo/kaluga/permissions/storage/PermissionKt { - public static final fun registerStoragePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; - public static synthetic fun registerStoragePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; - public static final fun registerStoragePermission-exY8QGI (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; - public static synthetic fun registerStoragePermission-exY8QGI$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;ILjava/lang/Object;)Lcom/splendo/kaluga/permissions/storage/BaseStoragePermissionManagerBuilder; + public static final fun registerStoragePermission (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermission$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerStoragePermission-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermission-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerStoragePermissionIfNotRegistered (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermissionIfNotRegistered$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun registerStoragePermissionIfNotRegistered-1Y68eR8 (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun registerStoragePermissionIfNotRegistered-1Y68eR8$default (Lcom/splendo/kaluga/permissions/base/PermissionsBuilder;Lkotlin/jvm/functions/Function1;JLcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class com/splendo/kaluga/permissions/storage/StoragePermission : com/splendo/kaluga/permissions/base/Permission { diff --git a/test-utils-alerts/api/androidLib/test-utils-alerts.api b/test-utils-alerts/api/androidLib/test-utils-alerts.api index 10bb047de..711352b67 100644 --- a/test-utils-alerts/api/androidLib/test-utils-alerts.api +++ b/test-utils-alerts/api/androidLib/test-utils-alerts.api @@ -14,6 +14,7 @@ public final class com/splendo/kaluga/test/alerts/MockAlertPresenter : com/splen public final fun getDismissMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getShowAsyncMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun isPresented ()Z + public final fun setPresented (Z)V public fun showAsync (ZLkotlin/jvm/functions/Function0;)V } @@ -21,8 +22,8 @@ public final class com/splendo/kaluga/test/alerts/MockAlertPresenter$Builder : c public fun ()V public fun (Z)V public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; - public final fun getBuiltAlerts ()Lco/touchlab/stately/collections/IsoMutableList; + public fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; + public final fun getBuiltAlerts ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; } diff --git a/test-utils-alerts/api/jvm/test-utils-alerts.api b/test-utils-alerts/api/jvm/test-utils-alerts.api index a5b683884..ccb45d0cc 100644 --- a/test-utils-alerts/api/jvm/test-utils-alerts.api +++ b/test-utils-alerts/api/jvm/test-utils-alerts.api @@ -7,6 +7,7 @@ public final class com/splendo/kaluga/test/alerts/MockAlertPresenter : com/splen public final fun getDismissMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getShowAsyncMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun isPresented ()Z + public final fun setPresented (Z)V public fun showAsync (ZLkotlin/jvm/functions/Function0;)V } @@ -14,8 +15,8 @@ public final class com/splendo/kaluga/test/alerts/MockAlertPresenter$Builder : c public fun ()V public fun (Z)V public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; - public final fun getBuiltAlerts ()Lco/touchlab/stately/collections/IsoMutableList; + public fun create (Lcom/splendo/kaluga/alerts/Alert;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; + public final fun getBuiltAlerts ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; } diff --git a/test-utils-architecture/api/androidLib/test-utils-architecture.api b/test-utils-architecture/api/androidLib/test-utils-architecture.api index 3259b7a4b..9fa11fed1 100644 --- a/test-utils-architecture/api/androidLib/test-utils-architecture.api +++ b/test-utils-architecture/api/androidLib/test-utils-architecture.api @@ -1,7 +1,5 @@ public abstract class com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest : com/splendo/kaluga/test/base/BaseUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$LazyViewModelTestContext : com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$ViewModelTestContext, kotlinx/coroutines/CoroutineScope { @@ -43,8 +41,6 @@ public abstract class com/splendo/kaluga/test/architecture/SimpleUIThreadViewMod public abstract class com/splendo/kaluga/test/architecture/UIThreadViewModelTest : com/splendo/kaluga/test/base/UIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/architecture/UIThreadViewModelTest$LazyViewModelTestContext : com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$LazyViewModelTestContext, com/splendo/kaluga/test/architecture/UIThreadViewModelTest$ViewModelTestContext { @@ -57,8 +53,6 @@ public abstract interface class com/splendo/kaluga/test/architecture/UIThreadVie public abstract class com/splendo/kaluga/test/architecture/ViewModelTest : com/splendo/kaluga/test/base/BaseTest { public field viewModel Lcom/splendo/kaluga/architecture/viewmodel/LifecycleViewModel; public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun beforeTest ()V protected abstract fun createViewModel ()Lcom/splendo/kaluga/architecture/viewmodel/LifecycleViewModel; public final fun getViewModel ()Lcom/splendo/kaluga/architecture/viewmodel/LifecycleViewModel; diff --git a/test-utils-architecture/api/jvm/test-utils-architecture.api b/test-utils-architecture/api/jvm/test-utils-architecture.api index ba53aa9d8..fc7c6fb24 100644 --- a/test-utils-architecture/api/jvm/test-utils-architecture.api +++ b/test-utils-architecture/api/jvm/test-utils-architecture.api @@ -1,7 +1,5 @@ public abstract class com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest : com/splendo/kaluga/test/base/BaseUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$LazyViewModelTestContext : com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$ViewModelTestContext, kotlinx/coroutines/CoroutineScope { @@ -33,8 +31,6 @@ public abstract class com/splendo/kaluga/test/architecture/SimpleUIThreadViewMod public abstract class com/splendo/kaluga/test/architecture/UIThreadViewModelTest : com/splendo/kaluga/test/base/UIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/architecture/UIThreadViewModelTest$LazyViewModelTestContext : com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$LazyViewModelTestContext, com/splendo/kaluga/test/architecture/UIThreadViewModelTest$ViewModelTestContext { @@ -51,8 +47,6 @@ public final class com/splendo/kaluga/test/architecture/UIThreadViewModelTest$Vi public abstract class com/splendo/kaluga/test/architecture/ViewModelTest : com/splendo/kaluga/test/base/BaseTest { public field viewModel Lcom/splendo/kaluga/architecture/viewmodel/LifecycleViewModel; public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun beforeTest ()V protected abstract fun createViewModel ()Lcom/splendo/kaluga/architecture/viewmodel/LifecycleViewModel; public final fun getViewModel ()Lcom/splendo/kaluga/architecture/viewmodel/LifecycleViewModel; diff --git a/test-utils-base/api/androidLib/test-utils-base.api b/test-utils-base/api/androidLib/test-utils-base.api index bf4c5a3f2..7d4959f6e 100644 --- a/test-utils-base/api/androidLib/test-utils-base.api +++ b/test-utils-base/api/androidLib/test-utils-base.api @@ -1,10 +1,3 @@ -public final class com/splendo/kaluga/test/base/AssertionsKt { - public static final fun assertFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V - public static final fun assertNotFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertNotFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V -} - public final class com/splendo/kaluga/test/base/AwaitAllBlockingKt { public static final fun awaitAllBlocking ([Lkotlinx/coroutines/Deferred;)Ljava/util/List; } @@ -21,7 +14,7 @@ public abstract class com/splendo/kaluga/test/base/BaseFlowTest : com/splendo/ka public abstract fun getFlowFromTestContext ()Lkotlin/jvm/functions/Function2; public final fun getJob ()Lkotlinx/coroutines/Job; public final fun getScope ()Lkotlinx/coroutines/CoroutineScope; - protected final fun getWaitForTestToSucceed ()J + protected final fun getWaitForTestToSucceed-UwyO8pc ()J public final fun mainAction (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun resetFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun setFirstTestBlock (Z)V @@ -41,8 +34,6 @@ public class com/splendo/kaluga/test/base/BaseTest { public abstract class com/splendo/kaluga/test/base/BaseUIThreadTest : com/splendo/kaluga/test/base/BaseTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public abstract fun getCreateTestContextWithConfiguration ()Lkotlin/jvm/functions/Function3; public final fun testOnUIThread (Ljava/lang/Object;ZLkotlin/jvm/functions/Function2;)V public static synthetic fun testOnUIThread$default (Lcom/splendo/kaluga/test/base/BaseUIThreadTest;Ljava/lang/Object;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V @@ -68,7 +59,7 @@ public final class com/splendo/kaluga/test/base/BuildConfig { } public final class com/splendo/kaluga/test/base/DeliberateCancellationException : java/util/concurrent/CancellationException { - public fun (Ljava/lang/Object;Z)V + public fun (Ljava/lang/Object;)V public final fun getResult ()Ljava/lang/Object; } @@ -114,8 +105,6 @@ public final class com/splendo/kaluga/test/base/SimpleUIThreadTest$SimpleTestCon public abstract class com/splendo/kaluga/test/base/UIThreadTest : com/splendo/kaluga/test/base/BaseUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public abstract fun getCreateTestContext ()Lkotlin/jvm/functions/Function2; public fun getCreateTestContextWithConfiguration ()Lkotlin/jvm/functions/Function3; public final fun testOnUIThread (ZLkotlin/jvm/functions/Function2;)V @@ -157,8 +146,9 @@ public abstract class com/splendo/kaluga/test/base/mock/BaseMethodMock$Stub { public final fun doExecute (Lkotlin/jvm/functions/Function1;)V public final fun doReturn (Ljava/lang/Object;)V public final fun doThrow (Ljava/lang/Throwable;)V - protected final fun getAnswer ()Ljava/util/concurrent/atomic/AtomicReference; + protected final fun getAnswer ()Lcom/splendo/kaluga/test/base/mock/answer/BaseAnswer; public abstract fun getMatchers ()Lcom/splendo/kaluga/test/base/mock/parameters/ParametersSpec$Matchers; + protected final fun setAnswer (Lcom/splendo/kaluga/test/base/mock/answer/BaseAnswer;)V } public final class com/splendo/kaluga/test/base/mock/CallKt { diff --git a/test-utils-base/api/jvm/test-utils-base.api b/test-utils-base/api/jvm/test-utils-base.api index 5e5e0fada..a7b7d5bcf 100644 --- a/test-utils-base/api/jvm/test-utils-base.api +++ b/test-utils-base/api/jvm/test-utils-base.api @@ -1,10 +1,3 @@ -public final class com/splendo/kaluga/test/base/AssertionsKt { - public static final fun assertFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V - public static final fun assertNotFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertNotFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V -} - public final class com/splendo/kaluga/test/base/AwaitAllBlockingKt { public static final fun awaitAllBlocking ([Lkotlinx/coroutines/Deferred;)Ljava/util/List; } @@ -21,7 +14,7 @@ public abstract class com/splendo/kaluga/test/base/BaseFlowTest : com/splendo/ka public abstract fun getFlowFromTestContext ()Lkotlin/jvm/functions/Function2; public final fun getJob ()Lkotlinx/coroutines/Job; public final fun getScope ()Lkotlinx/coroutines/CoroutineScope; - protected final fun getWaitForTestToSucceed ()J + protected final fun getWaitForTestToSucceed-UwyO8pc ()J public final fun mainAction (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun resetFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun setFirstTestBlock (Z)V @@ -40,8 +33,6 @@ public class com/splendo/kaluga/test/base/BaseTest { public abstract class com/splendo/kaluga/test/base/BaseUIThreadTest : com/splendo/kaluga/test/base/BaseTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public abstract fun getCreateTestContextWithConfiguration ()Lkotlin/jvm/functions/Function3; public final fun testOnUIThread (Ljava/lang/Object;ZLkotlin/jvm/functions/Function2;)V public static synthetic fun testOnUIThread$default (Lcom/splendo/kaluga/test/base/BaseUIThreadTest;Ljava/lang/Object;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V @@ -65,7 +56,7 @@ public final class com/splendo/kaluga/test/base/BaseUIThreadTest$TestContext$Def } public final class com/splendo/kaluga/test/base/DeliberateCancellationException : java/util/concurrent/CancellationException { - public fun (Ljava/lang/Object;Z)V + public fun (Ljava/lang/Object;)V public final fun getResult ()Ljava/lang/Object; } @@ -112,8 +103,6 @@ public final class com/splendo/kaluga/test/base/SimpleUIThreadTest$SimpleTestCon public abstract class com/splendo/kaluga/test/base/UIThreadTest : com/splendo/kaluga/test/base/BaseUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public abstract fun getCreateTestContext ()Lkotlin/jvm/functions/Function2; public fun getCreateTestContextWithConfiguration ()Lkotlin/jvm/functions/Function3; public final fun testOnUIThread (ZLkotlin/jvm/functions/Function2;)V @@ -160,8 +149,9 @@ public abstract class com/splendo/kaluga/test/base/mock/BaseMethodMock$Stub { public final fun doExecute (Lkotlin/jvm/functions/Function1;)V public final fun doReturn (Ljava/lang/Object;)V public final fun doThrow (Ljava/lang/Throwable;)V - protected final fun getAnswer ()Ljava/util/concurrent/atomic/AtomicReference; + protected final fun getAnswer ()Lcom/splendo/kaluga/test/base/mock/answer/BaseAnswer; public abstract fun getMatchers ()Lcom/splendo/kaluga/test/base/mock/parameters/ParametersSpec$Matchers; + protected final fun setAnswer (Lcom/splendo/kaluga/test/base/mock/answer/BaseAnswer;)V } public final class com/splendo/kaluga/test/base/mock/CallKt { diff --git a/test-utils-bluetooth/api/androidLib/test-utils-bluetooth.api b/test-utils-bluetooth/api/androidLib/test-utils-bluetooth.api index 5fec133eb..a29e51e17 100644 --- a/test-utils-bluetooth/api/androidLib/test-utils-bluetooth.api +++ b/test-utils-bluetooth/api/androidLib/test-utils-bluetooth.api @@ -140,7 +140,7 @@ public final class com/splendo/kaluga/test/bluetooth/MockDeviceWrapper : com/spl public final fun getConnectGattMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getCreateBondMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public fun getDevice ()Landroid/bluetooth/BluetoothDevice; - public final fun getGattWrappers ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getGattWrappers ()Ljava/util/List; public fun getIdentifier ()Ljava/lang/String; public fun getName ()Ljava/lang/String; public final fun getRemoveBondMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; @@ -280,7 +280,7 @@ public final class com/splendo/kaluga/test/bluetooth/device/MockDeviceConnection public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/bluetooth/device/DeviceWrapper;Lcom/splendo/kaluga/bluetooth/device/ConnectionSettings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/bluetooth/device/BaseDeviceConnectionManager; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getCreatedDeviceConnectionManager ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getCreatedDeviceConnectionManager ()Ljava/util/List; } public final class com/splendo/kaluga/test/bluetooth/scanner/MockBaseScanner : com/splendo/kaluga/bluetooth/scanner/BaseScanner { @@ -311,7 +311,7 @@ public final class com/splendo/kaluga/test/bluetooth/scanner/MockBaseScanner$Bui public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Settings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getCreatedScanners ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getCreatedScanners ()Ljava/util/List; } public final class com/splendo/kaluga/test/bluetooth/scanner/MockScanner : com/splendo/kaluga/bluetooth/scanner/Scanner { diff --git a/test-utils-bluetooth/api/jvm/test-utils-bluetooth.api b/test-utils-bluetooth/api/jvm/test-utils-bluetooth.api index 69ec1c9fe..422e2d921 100644 --- a/test-utils-bluetooth/api/jvm/test-utils-bluetooth.api +++ b/test-utils-bluetooth/api/jvm/test-utils-bluetooth.api @@ -188,7 +188,7 @@ public final class com/splendo/kaluga/test/bluetooth/device/MockDeviceConnection public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/bluetooth/device/DeviceWrapper;Lcom/splendo/kaluga/bluetooth/device/ConnectionSettings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/bluetooth/device/BaseDeviceConnectionManager; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getCreatedDeviceConnectionManager ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getCreatedDeviceConnectionManager ()Ljava/util/List; } public final class com/splendo/kaluga/test/bluetooth/scanner/MockBaseScanner : com/splendo/kaluga/bluetooth/scanner/BaseScanner { @@ -219,7 +219,7 @@ public final class com/splendo/kaluga/test/bluetooth/scanner/MockBaseScanner$Bui public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner$Settings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/bluetooth/scanner/BaseScanner; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getCreatedScanners ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getCreatedScanners ()Ljava/util/List; } public final class com/splendo/kaluga/test/bluetooth/scanner/MockScanner : com/splendo/kaluga/bluetooth/scanner/Scanner { diff --git a/test-utils-hud/api/androidLib/test-utils-hud.api b/test-utils-hud/api/androidLib/test-utils-hud.api index dac3a9d29..2dc8365d1 100644 --- a/test-utils-hud/api/androidLib/test-utils-hud.api +++ b/test-utils-hud/api/androidLib/test-utils-hud.api @@ -14,6 +14,7 @@ public final class com/splendo/kaluga/test/hud/MockHUD : com/splendo/kaluga/hud/ public final fun getPresentMock ()Lcom/splendo/kaluga/test/base/mock/SuspendMethodMock; public fun isVisible ()Z public fun present (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setVisible (Z)V } public final class com/splendo/kaluga/test/hud/MockHUD$Builder : com/splendo/kaluga/hud/BaseHUD$Builder { @@ -22,7 +23,7 @@ public final class com/splendo/kaluga/test/hud/MockHUD$Builder : com/splendo/kal public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun create (Lcom/splendo/kaluga/hud/HudConfig;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/hud/BaseHUD; public fun create (Lcom/splendo/kaluga/hud/HudConfig;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/test/hud/MockHUD; - public final fun getBuiltHUDs ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltHUDs ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; } diff --git a/test-utils-hud/api/jvm/test-utils-hud.api b/test-utils-hud/api/jvm/test-utils-hud.api index a728e57cb..0a9134f6c 100644 --- a/test-utils-hud/api/jvm/test-utils-hud.api +++ b/test-utils-hud/api/jvm/test-utils-hud.api @@ -7,6 +7,7 @@ public final class com/splendo/kaluga/test/hud/MockHUD : com/splendo/kaluga/hud/ public final fun getPresentMock ()Lcom/splendo/kaluga/test/base/mock/SuspendMethodMock; public fun isVisible ()Z public fun present (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun setVisible (Z)V } public final class com/splendo/kaluga/test/hud/MockHUD$Builder : com/splendo/kaluga/hud/BaseHUD$Builder { @@ -15,7 +16,7 @@ public final class com/splendo/kaluga/test/hud/MockHUD$Builder : com/splendo/kal public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun create (Lcom/splendo/kaluga/hud/HudConfig;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/hud/BaseHUD; public fun create (Lcom/splendo/kaluga/hud/HudConfig;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/test/hud/MockHUD; - public final fun getBuiltHUDs ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltHUDs ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; } diff --git a/test-utils-keyboard/api/androidLib/test-utils-keyboard.api b/test-utils-keyboard/api/androidLib/test-utils-keyboard.api index 5cec3ad42..6372a48e3 100644 --- a/test-utils-keyboard/api/androidLib/test-utils-keyboard.api +++ b/test-utils-keyboard/api/androidLib/test-utils-keyboard.api @@ -26,8 +26,8 @@ public final class com/splendo/kaluga/test/keyboard/MockKeyboardManager : com/sp public final fun getFocusHandler ()Lcom/splendo/kaluga/keyboard/FocusHandler; public final fun getHideMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getShowMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun get_focusHandler ()Ljava/util/concurrent/atomic/AtomicReference; public fun hide ()V + public final fun setFocusHandler (Lcom/splendo/kaluga/keyboard/FocusHandler;)V public fun show (Lcom/splendo/kaluga/keyboard/FocusHandler;)V } @@ -37,7 +37,7 @@ public final class com/splendo/kaluga/test/keyboard/MockKeyboardManager$Builder public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/BaseKeyboardManager; public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/test/keyboard/MockKeyboardManager; - public final fun getBuiltKeyboardManagers ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltKeyboardManagers ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; } diff --git a/test-utils-keyboard/api/jvm/test-utils-keyboard.api b/test-utils-keyboard/api/jvm/test-utils-keyboard.api index a55dcc374..b14aa4de8 100644 --- a/test-utils-keyboard/api/jvm/test-utils-keyboard.api +++ b/test-utils-keyboard/api/jvm/test-utils-keyboard.api @@ -18,8 +18,8 @@ public final class com/splendo/kaluga/test/keyboard/MockKeyboardManager : com/sp public final fun getFocusHandler ()Lcom/splendo/kaluga/keyboard/FocusHandler; public final fun getHideMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getShowMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun get_focusHandler ()Ljava/util/concurrent/atomic/AtomicReference; public fun hide ()V + public final fun setFocusHandler (Lcom/splendo/kaluga/keyboard/FocusHandler;)V public fun show (Lcom/splendo/kaluga/keyboard/FocusHandler;)V } @@ -29,7 +29,7 @@ public final class com/splendo/kaluga/test/keyboard/MockKeyboardManager$Builder public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/BaseKeyboardManager; public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/test/keyboard/MockKeyboardManager; - public final fun getBuiltKeyboardManagers ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltKeyboardManagers ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; } diff --git a/test-utils-koin/api/androidLib/test-utils-koin.api b/test-utils-koin/api/androidLib/test-utils-koin.api index fae78bb49..91f812a2a 100644 --- a/test-utils-koin/api/androidLib/test-utils-koin.api +++ b/test-utils-koin/api/androidLib/test-utils-koin.api @@ -4,8 +4,6 @@ public abstract class com/splendo/kaluga/test/koin/BaseKoinFlowTest : com/splend public abstract class com/splendo/kaluga/test/koin/BaseKoinUIThreadTest : com/splendo/kaluga/test/base/BaseUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/koin/BaseKoinUIThreadTest$KoinTestContext : com/splendo/kaluga/test/base/BaseUIThreadTest$TestContext, org/koin/core/component/KoinComponent { @@ -19,8 +17,6 @@ public class com/splendo/kaluga/test/koin/BaseKoinUIThreadTest$KoinTestContext : public abstract class com/splendo/kaluga/test/koin/BaseKoinUIThreadViewModelTest : com/splendo/kaluga/test/koin/BaseKoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class com/splendo/kaluga/test/koin/BaseKoinUIThreadViewModelTest$KoinViewModelTestContext : com/splendo/kaluga/test/koin/BaseKoinUIThreadTest$KoinTestContext, com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$ViewModelTestContext { @@ -46,8 +42,6 @@ public abstract class com/splendo/kaluga/test/koin/KoinFlowTest : com/splendo/ka public abstract class com/splendo/kaluga/test/koin/KoinUIThreadTest : com/splendo/kaluga/test/koin/BaseKoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public abstract fun getCreateTestContext ()Lkotlin/jvm/functions/Function2; public fun getCreateTestContextWithConfiguration ()Lkotlin/jvm/functions/Function3; public final fun testOnUIThread (ZLkotlin/jvm/functions/Function2;)V @@ -63,8 +57,6 @@ public class com/splendo/kaluga/test/koin/KoinUIThreadTest$KoinTestContext : com public abstract class com/splendo/kaluga/test/koin/KoinUIThreadViewModelTest : com/splendo/kaluga/test/koin/KoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class com/splendo/kaluga/test/koin/KoinUIThreadViewModelTest$KoinViewModelTestContext : com/splendo/kaluga/test/koin/KoinUIThreadTest$KoinTestContext, com/splendo/kaluga/test/architecture/UIThreadViewModelTest$ViewModelTestContext { diff --git a/test-utils-koin/api/jvm/test-utils-koin.api b/test-utils-koin/api/jvm/test-utils-koin.api index 4af8c3676..049e5a3c3 100644 --- a/test-utils-koin/api/jvm/test-utils-koin.api +++ b/test-utils-koin/api/jvm/test-utils-koin.api @@ -4,8 +4,6 @@ public abstract class com/splendo/kaluga/test/koin/BaseKoinFlowTest : com/splend public abstract class com/splendo/kaluga/test/koin/BaseKoinUIThreadTest : com/splendo/kaluga/test/base/BaseUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/koin/BaseKoinUIThreadTest$KoinTestContext : com/splendo/kaluga/test/base/BaseUIThreadTest$TestContext, org/koin/core/component/KoinComponent { @@ -19,8 +17,6 @@ public class com/splendo/kaluga/test/koin/BaseKoinUIThreadTest$KoinTestContext : public abstract class com/splendo/kaluga/test/koin/BaseKoinUIThreadViewModelTest : com/splendo/kaluga/test/koin/BaseKoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class com/splendo/kaluga/test/koin/BaseKoinUIThreadViewModelTest$KoinViewModelTestContext : com/splendo/kaluga/test/koin/BaseKoinUIThreadTest$KoinTestContext, com/splendo/kaluga/test/architecture/BaseUIThreadViewModelTest$ViewModelTestContext { @@ -39,8 +35,6 @@ public abstract class com/splendo/kaluga/test/koin/KoinFlowTest : com/splendo/ka public abstract class com/splendo/kaluga/test/koin/KoinUIThreadTest : com/splendo/kaluga/test/koin/BaseKoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public abstract fun getCreateTestContext ()Lkotlin/jvm/functions/Function2; public fun getCreateTestContextWithConfiguration ()Lkotlin/jvm/functions/Function3; public final fun testOnUIThread (ZLkotlin/jvm/functions/Function2;)V @@ -56,8 +50,6 @@ public class com/splendo/kaluga/test/koin/KoinUIThreadTest$KoinTestContext : com public abstract class com/splendo/kaluga/test/koin/KoinUIThreadViewModelTest : com/splendo/kaluga/test/koin/KoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class com/splendo/kaluga/test/koin/KoinUIThreadViewModelTest$KoinViewModelTestContext : com/splendo/kaluga/test/koin/KoinUIThreadTest$KoinTestContext, com/splendo/kaluga/test/architecture/UIThreadViewModelTest$ViewModelTestContext { diff --git a/test-utils-location/api/androidLib/test-utils-location.api b/test-utils-location/api/androidLib/test-utils-location.api index 8da3ddcdc..e4e480ee4 100644 --- a/test-utils-location/api/androidLib/test-utils-location.api +++ b/test-utils-location/api/androidLib/test-utils-location.api @@ -30,7 +30,7 @@ public final class com/splendo/kaluga/test/location/MockBaseLocationManager$Buil public fun (ZZ)V public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/location/BaseLocationManager$Settings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/location/BaseLocationManager; - public final fun getBuiltLocationManagers ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltLocationManagers ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getInitialLocationEnabled ()Z } @@ -144,10 +144,10 @@ public final class com/splendo/kaluga/test/location/MockLocationState$PermittedH } public final class com/splendo/kaluga/test/location/MockLocationStateRepoBuilder : com/splendo/kaluga/location/LocationStateRepo$Builder { - public fun (Lcom/splendo/kaluga/permissions/base/Permissions;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Z)V - public synthetic fun (Lcom/splendo/kaluga/permissions/base/Permissions;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Z)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/permissions/location/LocationPermission;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/location/LocationStateRepo; - public final fun getBuiltLocationStateRepo ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltLocationStateRepo ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getLocationManagerBuilder ()Lcom/splendo/kaluga/location/BaseLocationManager$Builder; } diff --git a/test-utils-location/api/jvm/test-utils-location.api b/test-utils-location/api/jvm/test-utils-location.api index a109ee728..32fef185c 100644 --- a/test-utils-location/api/jvm/test-utils-location.api +++ b/test-utils-location/api/jvm/test-utils-location.api @@ -23,7 +23,7 @@ public final class com/splendo/kaluga/test/location/MockBaseLocationManager$Buil public fun (ZZ)V public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/location/BaseLocationManager$Settings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/location/BaseLocationManager; - public final fun getBuiltLocationManagers ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltLocationManagers ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getInitialLocationEnabled ()Z } @@ -155,10 +155,10 @@ public final class com/splendo/kaluga/test/location/MockLocationState$PermittedH } public final class com/splendo/kaluga/test/location/MockLocationStateRepoBuilder : com/splendo/kaluga/location/LocationStateRepo$Builder { - public fun (Lcom/splendo/kaluga/permissions/base/Permissions;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Z)V - public synthetic fun (Lcom/splendo/kaluga/permissions/base/Permissions;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;Z)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lcom/splendo/kaluga/location/BaseLocationManager$Builder;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Lcom/splendo/kaluga/permissions/location/LocationPermission;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;)Lcom/splendo/kaluga/location/LocationStateRepo; - public final fun getBuiltLocationStateRepo ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuiltLocationStateRepo ()Ljava/util/List; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getLocationManagerBuilder ()Lcom/splendo/kaluga/location/BaseLocationManager$Builder; } diff --git a/test-utils-permissions/api/androidLib/test-utils-permissions.api b/test-utils-permissions/api/androidLib/test-utils-permissions.api index 68ef3e17b..6d5d913c4 100644 --- a/test-utils-permissions/api/androidLib/test-utils-permissions.api +++ b/test-utils-permissions/api/androidLib/test-utils-permissions.api @@ -45,7 +45,7 @@ public final class com/splendo/kaluga/test/permissions/MockPermissionManager$Bui public synthetic fun (Lcom/splendo/kaluga/permissions/base/Permission;JLcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun create (Lcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/test/permissions/MockPermissionManager; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getCreatedManagers ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getCreatedManagers ()Ljava/util/List; public final fun getInitialState ()Lcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState; public final fun getMonitoringInterval-UwyO8pc ()J public final fun getPermission ()Lcom/splendo/kaluga/permissions/base/Permission; @@ -114,14 +114,14 @@ public final class com/splendo/kaluga/test/permissions/MockPermissionsBuilder : public fun (Lcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState;Z)V public synthetic fun (Lcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getBluetoothStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getBuildBluetoothStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildCalendarStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildCameraStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildContactsStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildLocationStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildMicrophoneStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildNotificationsStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildStorageStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuildBluetoothStateRepos ()Ljava/util/List; + public final fun getBuildCalendarStateRepos ()Ljava/util/List; + public final fun getBuildCameraStateRepos ()Ljava/util/List; + public final fun getBuildContactsStateRepos ()Ljava/util/List; + public final fun getBuildLocationStateRepos ()Ljava/util/List; + public final fun getBuildMicrophoneStateRepos ()Ljava/util/List; + public final fun getBuildNotificationsStateRepos ()Ljava/util/List; + public final fun getBuildStorageStateRepos ()Ljava/util/List; public final fun getCalendarStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getCameraStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getContactsStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; @@ -129,6 +129,6 @@ public final class com/splendo/kaluga/test/permissions/MockPermissionsBuilder : public final fun getMicrophoneStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getNotificationsStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getStorageStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun registerAllPermissionsBuilders ()V + public final fun registerAllPermissionsBuilders (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/test-utils-permissions/api/jvm/test-utils-permissions.api b/test-utils-permissions/api/jvm/test-utils-permissions.api index 1ca3d0343..b0d79d054 100644 --- a/test-utils-permissions/api/jvm/test-utils-permissions.api +++ b/test-utils-permissions/api/jvm/test-utils-permissions.api @@ -34,7 +34,7 @@ public final class com/splendo/kaluga/test/permissions/MockPermissionManager$Bui public synthetic fun (Lcom/splendo/kaluga/permissions/base/Permission;JLcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun create (Lcom/splendo/kaluga/permissions/base/BasePermissionManager$Settings;Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/test/permissions/MockPermissionManager; public final fun getCreateMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getCreatedManagers ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getCreatedManagers ()Ljava/util/List; public final fun getInitialState ()Lcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState; public final fun getMonitoringInterval-UwyO8pc ()J public final fun getPermission ()Lcom/splendo/kaluga/permissions/base/Permission; @@ -121,14 +121,14 @@ public final class com/splendo/kaluga/test/permissions/MockPermissionsBuilder : public fun (Lcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState;Z)V public synthetic fun (Lcom/splendo/kaluga/test/permissions/MockPermissionState$ActiveState;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getBluetoothStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun getBuildBluetoothStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildCalendarStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildCameraStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildContactsStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildLocationStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildMicrophoneStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildNotificationsStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; - public final fun getBuildStorageStateRepos ()Lco/touchlab/stately/collections/IsoMutableList; + public final fun getBuildBluetoothStateRepos ()Ljava/util/List; + public final fun getBuildCalendarStateRepos ()Ljava/util/List; + public final fun getBuildCameraStateRepos ()Ljava/util/List; + public final fun getBuildContactsStateRepos ()Ljava/util/List; + public final fun getBuildLocationStateRepos ()Ljava/util/List; + public final fun getBuildMicrophoneStateRepos ()Ljava/util/List; + public final fun getBuildNotificationsStateRepos ()Ljava/util/List; + public final fun getBuildStorageStateRepos ()Ljava/util/List; public final fun getCalendarStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getCameraStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getContactsStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; @@ -136,7 +136,7 @@ public final class com/splendo/kaluga/test/permissions/MockPermissionsBuilder : public final fun getMicrophoneStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getNotificationsStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; public final fun getStorageStateRepoBuilderMock ()Lcom/splendo/kaluga/test/base/mock/MethodMock; - public final fun registerAllPermissionsBuilders ()V + public final fun registerAllPermissionsBuilders (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/splendo/kaluga/test/permissions/MockPermissionsContextKt { diff --git a/test-utils/api/androidLib/test-utils.api b/test-utils/api/androidLib/test-utils.api index a29084463..d246a8527 100644 --- a/test-utils/api/androidLib/test-utils.api +++ b/test-utils/api/androidLib/test-utils.api @@ -13,8 +13,6 @@ public abstract class com/splendo/kaluga/test/KoinFlowTest : com/splendo/kaluga/ public abstract class com/splendo/kaluga/test/KoinUIThreadTest : com/splendo/kaluga/test/koin/KoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/KoinUIThreadTest$KoinTestContext : com/splendo/kaluga/test/koin/KoinUIThreadTest$KoinTestContext { @@ -24,8 +22,6 @@ public class com/splendo/kaluga/test/KoinUIThreadTest$KoinTestContext : com/sple public abstract class com/splendo/kaluga/test/KoinUIThreadViewModelTest : com/splendo/kaluga/test/KoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class com/splendo/kaluga/test/KoinUIThreadViewModelTest$KoinViewModelTestContext : com/splendo/kaluga/test/KoinUIThreadTest$KoinTestContext, com/splendo/kaluga/test/architecture/UIThreadViewModelTest$ViewModelTestContext { @@ -36,10 +32,6 @@ public abstract class com/splendo/kaluga/test/KoinUIThreadViewModelTest$KoinView } public final class com/splendo/kaluga/test/LegacyKt { - public static final fun assertFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V - public static final fun assertNotFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertNotFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V public static final fun awaitAllBlocking ([Lkotlinx/coroutines/Deferred;)Ljava/util/List; public static final fun captureFor-8Mi8wO0 (Lkotlinx/coroutines/flow/Flow;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -56,8 +48,6 @@ public final class com/splendo/kaluga/test/SimpleUIThreadTest$SimpleTestContext public abstract class com/splendo/kaluga/test/UIThreadTest : com/splendo/kaluga/test/base/UIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract interface class com/splendo/kaluga/test/UIThreadTest$TestContext : com/splendo/kaluga/test/base/UIThreadTest$TestContext { diff --git a/test-utils/api/jvm/test-utils.api b/test-utils/api/jvm/test-utils.api index 3b75d675e..a8d4922a1 100644 --- a/test-utils/api/jvm/test-utils.api +++ b/test-utils/api/jvm/test-utils.api @@ -13,8 +13,6 @@ public abstract class com/splendo/kaluga/test/KoinFlowTest : com/splendo/kaluga/ public abstract class com/splendo/kaluga/test/KoinUIThreadTest : com/splendo/kaluga/test/koin/KoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class com/splendo/kaluga/test/KoinUIThreadTest$KoinTestContext : com/splendo/kaluga/test/koin/KoinUIThreadTest$KoinTestContext { @@ -24,8 +22,6 @@ public class com/splendo/kaluga/test/KoinUIThreadTest$KoinTestContext : com/sple public abstract class com/splendo/kaluga/test/KoinUIThreadViewModelTest : com/splendo/kaluga/test/KoinUIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class com/splendo/kaluga/test/KoinUIThreadViewModelTest$KoinViewModelTestContext : com/splendo/kaluga/test/KoinUIThreadTest$KoinTestContext, com/splendo/kaluga/test/architecture/UIThreadViewModelTest$ViewModelTestContext { @@ -36,10 +32,6 @@ public abstract class com/splendo/kaluga/test/KoinUIThreadViewModelTest$KoinView } public final class com/splendo/kaluga/test/LegacyKt { - public static final fun assertFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V - public static final fun assertNotFrozen (Ljava/lang/Object;Ljava/lang/String;Z)V - public static synthetic fun assertNotFrozen$default (Ljava/lang/Object;Ljava/lang/String;ZILjava/lang/Object;)V public static final fun awaitAllBlocking ([Lkotlinx/coroutines/Deferred;)Ljava/util/List; public static final fun captureFor-8Mi8wO0 (Lkotlinx/coroutines/flow/Flow;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -57,8 +49,6 @@ public final class com/splendo/kaluga/test/SimpleUIThreadTest$SimpleTestContext public abstract class com/splendo/kaluga/test/UIThreadTest : com/splendo/kaluga/test/base/UIThreadTest { public fun ()V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract interface class com/splendo/kaluga/test/UIThreadTest$TestContext : com/splendo/kaluga/test/base/UIThreadTest$TestContext { From c378a4da2863f0eed41553bc4fbca512f09bb69c Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 4 Nov 2022 11:25:17 +0100 Subject: [PATCH 009/227] Fixes to get compilation working --- .../kotlin/AlertsViewModel.kt | 2 +- .../commonTest/kotlin/AlertPresenterTests.kt | 2 +- alerts/src/commonTest/kotlin/AlertsTest.kt | 18 ++++++++++++++++++ .../kotlin/TestActivity.kt | 2 +- .../androidLibAndroidTest/AndroidManifest.xml | 2 +- .../kotlin/TestActivity.kt | 2 +- .../GetOrPutAndRemoveOnDestroyFromCacheTest.kt | 2 +- .../kotlin/lifecycle/LifecycleManagerTest.kt | 2 +- .../navigation/AndroidNavigationBundleTest.kt | 2 +- .../src/androidLibMain/AndroidManifest.xml | 2 +- .../lifecycle/LifecycleManagerObserver.kt | 2 +- .../kotlin/lifecycle/LifecycleSubscribable.kt | 2 +- .../getOrPutAndRemoveOnDestroyFromCache.kt | 2 +- .../kotlin/navigation/IntentFlag.kt | 2 +- .../kotlin/navigation/NavigationBundle.kt | 2 +- .../kotlin/navigation/NavigationSpec.kt | 2 +- .../kotlin/navigation/Navigator.kt | 2 +- .../kotlin/observable/Disposable.kt | 2 +- .../viewmodel/KalugaViewModelActivity.kt | 2 +- .../viewmodel/KalugaViewModelDialogFragment.kt | 2 +- .../viewmodel/KalugaViewModelFragment.kt | 2 +- .../KalugaViewModelLifecycleObserver.kt | 2 +- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../lifecycle/LifecycleManagerObserverTests.kt | 2 +- .../kotlin/navigation/NavigationBundle.kt | 2 +- .../kotlin/navigation/NavigationBundleSpec.kt | 2 +- .../commonMain/kotlin/navigation/Navigator.kt | 2 +- .../commonMain/kotlin/observable/Disposable.kt | 2 +- .../commonMain/kotlin/observable/Observable.kt | 2 +- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../kotlin/navigation/MockSpecRow.kt | 2 +- .../kotlin/navigation/NavigationBundleTest.kt | 2 +- .../kotlin/observable/ObservableBaseTest.kt | 2 +- .../kotlin/navigation/NavigationSpec.kt | 2 +- .../src/iosMain/kotlin/navigation/Navigator.kt | 2 +- .../kotlin/observable/SimpleDisposable.kt | 2 +- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../viewmodel/ViewModelLifecycleManager.kt | 2 +- .../src/jsMain/kotlin/navigation/Navigator.kt | 2 +- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../src/jvmMain/kotlin/navigation/Navigator.kt | 2 +- .../kotlin/observable/SimpleDisposable.kt | 2 +- .../kotlin/viewmodel/LifecycleViewModel.kt | 2 +- .../kotlin/text/DateFormatterTest.kt | 2 +- .../kotlin/text/KalugaDateFormatter.kt | 2 +- .../kotlin/text/NumberFormatter.kt | 11 +++++------ .../androidLibMain/kotlin/text/StringUtils.kt | 10 +++++----- .../androidLibMain/kotlin/utils/KalugaDate.kt | 2 +- base/src/androidLibMain/kotlin/utils/Locale.kt | 2 +- .../androidLibMain/kotlin/utils/TimeZone.kt | 2 +- .../kotlin/text/DateFormatterTest.kt | 2 +- base/src/commonMain/kotlin/text/DateTime.kt | 10 +++++----- base/src/commonMain/kotlin/text/FixedString.kt | 10 +++++----- base/src/commonMain/kotlin/text/Flags.kt | 2 +- .../commonMain/kotlin/text/FormatSpecifier.kt | 10 +++++----- .../src/commonMain/kotlin/text/FormatString.kt | 10 +++++----- .../kotlin/text/KalugaDateFormatter.kt | 2 +- .../commonMain/kotlin/text/NumberFormatter.kt | 2 +- .../kotlin/text/RegularFormatCharacter.kt | 10 +++++----- .../commonMain/kotlin/text/StringFormatter.kt | 10 +++++----- .../kotlin/text/StringFormatterException.kt | 10 +++++----- base/src/commonMain/kotlin/text/StringUtils.kt | 10 +++++----- base/src/commonMain/kotlin/utils/ByteUtils.kt | 10 +++++----- base/src/commonMain/kotlin/utils/KalugaDate.kt | 2 +- base/src/commonMain/kotlin/utils/Locale.kt | 2 +- base/src/commonMain/kotlin/utils/TimeZone.kt | 2 +- base/src/commonMain/kotlin/utils/UnitSystem.kt | 2 +- .../commonTest/kotlin/flow/ColdFlowableTest.kt | 2 +- .../commonTest/kotlin/flow/HotFlowableTest.kt | 2 +- .../kotlin/text/DateFormatterTest.kt | 2 +- .../kotlin/text/NumberFormatterTest.kt | 7 ++++--- .../kotlin/text/StringFormatterTest.kt | 10 +++++----- .../kotlin/utils/ByOrdinalOrDefaultTest.kt | 10 +++++----- base/src/commonTest/kotlin/utils/DateTest.kt | 10 +++++----- base/src/commonTest/kotlin/utils/LocaleTest.kt | 2 +- .../commonTest/kotlin/utils/UnitSystemTest.kt | 2 +- base/src/iosMain/kotlin/GCScheduler.kt | 2 +- .../iosMain/kotlin/text/KalugaDateFormatter.kt | 2 +- .../src/iosMain/kotlin/text/NumberFormatter.kt | 2 +- base/src/iosMain/kotlin/text/StringUtils.kt | 2 +- base/src/iosMain/kotlin/utils/KalugaDate.kt | 10 +++++----- base/src/iosMain/kotlin/utils/Locale.kt | 2 +- base/src/iosMain/kotlin/utils/TimeZone.kt | 2 +- base/src/iosTest/kotlin/DateFormatterTest.kt | 2 +- base/src/iosTest/kotlin/IOSVersionTest.kt | 2 +- .../jsMain/kotlin/text/KalugaDateFormatter.kt | 2 +- base/src/jsMain/kotlin/text/NumberFormatter.kt | 10 +++++----- base/src/jsMain/kotlin/text/StringUtils.kt | 10 +++++----- base/src/jsMain/kotlin/utils/KalugaDate.kt | 2 +- base/src/jsMain/kotlin/utils/Locale.kt | 2 +- base/src/jsMain/kotlin/utils/TimeZone.kt | 2 +- .../jsTest/kotlin/text/DateFormatterTest.kt | 2 +- .../jvmMain/kotlin/text/KalugaDateFormatter.kt | 2 +- .../src/jvmMain/kotlin/text/NumberFormatter.kt | 10 +++++----- base/src/jvmMain/kotlin/text/StringUtils.kt | 10 +++++----- base/src/jvmMain/kotlin/utils/KalugaDate.kt | 2 +- base/src/jvmMain/kotlin/utils/Locale.kt | 2 +- base/src/jvmMain/kotlin/utils/TimeZone.kt | 2 +- .../jvmTest/kotlin/text/DateFormatterTest.kt | 2 +- beacons/src/commonMain/kotlin/BeaconInfo.kt | 2 +- beacons/src/commonMain/kotlin/Beacons.kt | 2 +- beacons/src/commonMain/kotlin/Eddystone.kt | 2 +- .../src/commonTest/kotlin/BeaconUnpackTest.kt | 2 +- .../src/iosMain/kotlin/device/DeviceWrapper.kt | 2 +- .../kotlin/DateTimePickerViewModel.kt | 2 +- .../kotlin/DateTimePickerPresenter.kt | 2 +- .../src/commonMain/kotlin/DateTimePicker.kt | 2 +- .../kotlin/DateTimePickerPresenterTests.kt | 2 +- .../iosMain/kotlin/DateTimePickerPresenter.kt | 2 +- .../jsMain/kotlin/DateTimePickerPresenter.kt | 2 +- .../jvmMain/kotlin/DateTimePickerPresenter.kt | 2 +- .../kaluga/example/beacons/BeaconsActivity.kt | 2 +- .../kaluga/example/beacons/BeaconsAdapter.kt | 2 +- .../kaluga/example/beacons/BeaconsBinding.kt | 2 +- .../example/bluetooth/BluetoothActivity.kt | 2 +- .../example/bluetooth/BluetoothAdapter.kt | 2 +- .../BluetoothCharacteristicAdapter.kt | 2 +- .../bluetooth/BluetoothDescriptorAdapter.kt | 2 +- .../example/bluetooth/BluetoothMoreActivity.kt | 2 +- .../bluetooth/BluetoothServiceAdapter.kt | 2 +- .../location/LocationBackgroundService.kt | 2 +- .../permissions/PermissionsDemoListActivity.kt | 2 +- .../architecture/KNArchitectureFramework.kt | 2 +- .../kotlin/beacons/KNBeaconsFramework.kt | 2 +- .../kotlin/bluetooth/KNBluetoothFramework.kt | 2 +- .../kotlin/viewmodel/ExampleViewModel.kt | 2 +- .../ArchitectureDetailsViewModel.kt | 2 +- .../architecture/ArchitectureInputViewModel.kt | 2 +- .../beacons/BeaconsListBeaconViewModel.kt | 2 +- .../viewmodel/beacons/BeaconsListViewModel.kt | 2 +- .../BluetoothCharacteristicViewModel.kt | 2 +- .../bluetooth/BluetoothDescriptorViewModel.kt | 2 +- .../BluetoothDeviceDetailViewModel.kt | 2 +- .../bluetooth/BluetoothListDeviceViewModel.kt | 2 +- .../bluetooth/BluetoothListViewModel.kt | 2 +- .../bluetooth/BluetoothServiceViewModel.kt | 2 +- .../featureList/FeatureListViewModel.kt | 2 +- .../kotlin/viewmodel/info/InfoViewModel.kt | 2 +- .../viewmodel/keyboard/KeyboardViewModel.kt | 2 +- .../viewmodel/location/LocationViewModel.kt | 2 +- .../permissions/PermissionViewModel.kt | 2 +- .../permissions/PermissionsListViewModel.kt | 2 +- .../kotlin/HudViewModel.kt | 2 +- hud/src/jsMain/kotlin/HUD.kt | 2 +- hud/src/jvmMain/kotlin/HUD.kt | 2 +- .../ActivityKeyboardManagerBuilderTest.kt | 2 +- .../kotlin/AndroidKeyboardManagerTests.kt | 2 +- .../commonTest/kotlin/KeyboardManagerTests.kt | 2 +- links/build.gradle.kts | 2 +- .../kaluga/links/AndroidLinksHandlerTest.kt | 2 +- links/src/androidLibMain/AndroidManifest.xml | 2 +- .../links/manager/PlatformLinksHandler.kt | 2 +- .../links/manager/DefaultLinksManager.kt | 2 +- .../kaluga/links/manager/LinksHandler.kt | 2 +- .../kaluga/links/manager/LinksManager.kt | 2 +- .../com/splendo/kaluga/links/utils/decoders.kt | 2 +- .../links/manager/DefaultLinksManagerTest.kt | 2 +- .../kaluga/links/manager/MockLinksManager.kt | 2 +- .../kaluga/links/utils/LinksDecoderTest.kt | 2 +- .../links/manager/PlatformLinksHandler.kt | 2 +- .../kaluga/links/IosLinksHandlerTest.kt | 2 +- .../links/manager/PlatformLinksHandler.kt | 2 +- .../kaluga/links/PlatformLinksManagerTest.kt | 2 +- .../links/manager/PlatformLinksHandler.kt | 2 +- .../kaluga/links/PlatformLinksManagerTest.kt | 2 +- .../iosMain/kotlin/location/LocationMonitor.kt | 10 +++++----- logging/src/androidLibMain/kotlin/log.kt | 2 +- logging/src/commonMain/kotlin/NapierLogger.kt | 2 +- logging/src/iosMain/kotlin/log.kt | 2 +- logging/src/jsMain/kotlin/log.kt | 10 +++++----- .../kotlin/TestActivity.kt | 2 +- .../kotlin/AndroidStringsTest.kt | 2 +- .../src/androidLibMain/kotlin/DarkMode.kt | 10 +++++----- resources/src/androidLibMain/kotlin/Font.kt | 10 +++++----- resources/src/androidLibMain/kotlin/Image.kt | 2 +- .../src/androidLibMain/kotlin/KalugaColor.kt | 10 +++++----- .../src/androidLibMain/kotlin/Resources.kt | 10 +++++----- resources/src/commonMain/kotlin/DarkMode.kt | 10 +++++----- resources/src/commonMain/kotlin/Font.kt | 2 +- resources/src/commonMain/kotlin/Image.kt | 10 +++++----- resources/src/commonMain/kotlin/KalugaColor.kt | 10 +++++----- resources/src/commonMain/kotlin/Resources.kt | 10 +++++----- .../src/commonTest/kotlin/StringsTests.kt | 2 +- resources/src/iosMain/kotlin/DarkMode.kt | 10 +++++----- resources/src/iosMain/kotlin/Font.kt | 10 +++++----- resources/src/iosMain/kotlin/Image.kt | 10 +++++----- resources/src/iosMain/kotlin/KalugaColor.kt | 2 +- resources/src/iosMain/kotlin/Resources.kt | 10 +++++----- resources/src/iosTest/kotlin/IOSStringsTest.kt | 2 +- resources/src/jsMain/kotlin/DarkMode.kt | 10 +++++----- resources/src/jsMain/kotlin/Font.kt | 10 +++++----- resources/src/jsMain/kotlin/Image.kt | 2 +- resources/src/jsMain/kotlin/KalugaColor.kt | 10 +++++----- resources/src/jsMain/kotlin/Resources.kt | 10 +++++----- resources/src/jvmMain/kotlin/DarkMode.kt | 10 +++++----- resources/src/jvmMain/kotlin/Font.kt | 11 ++++++----- resources/src/jvmMain/kotlin/Image.kt | 10 +++++----- resources/src/jvmMain/kotlin/KalugaColor.kt | 10 +++++----- resources/src/jvmMain/kotlin/Resources.kt | 10 +++++----- .../src/commonMain/kotlin/ScientificArray.kt | 10 +++++----- .../kotlin/converter/area/convertToVolume.kt | 10 +++++----- .../convertFromSpecificEnergy.kt | 10 +++++----- .../convertToSpecificEnergy.kt | 10 +++++----- .../kotlin/converter/length/convertToVolume.kt | 10 +++++----- ...nvertFromIonizingRadiationEquivalentDose.kt | 10 +++++----- ...convertToIonizingRadiationEquivalentDose.kt | 10 +++++----- .../volume/convertToVolumetricFlow.kt | 10 +++++----- .../volumetricFlow/convertToVolume.kt | 10 +++++----- .../src/commonMain/kotlin/unit/ActionUnit.kt | 10 +++++----- .../kotlin/unit/AmountOfSubstanceUnit.kt | 10 +++++----- .../src/commonMain/kotlin/unit/AngleUnit.kt | 10 +++++----- .../kotlin/unit/AngularAcceleration.kt | 10 +++++----- .../kotlin/unit/CatalysticActivityUnit.kt | 10 +++++----- .../kotlin/unit/ElectricCapacitanceUnit.kt | 10 +++++----- .../kotlin/unit/ElectricConductanceUnit.kt | 10 +++++----- .../kotlin/unit/ElectricCurrentUnit.kt | 10 +++++----- .../kotlin/unit/ElectricResistanceUnit.kt | 10 +++++----- .../commonMain/kotlin/unit/HeatCapacityUnit.kt | 10 +++++----- .../commonMain/kotlin/unit/IlluminanceUnit.kt | 10 +++++----- .../kotlin/unit/LinearMassDensityUnit.kt | 10 +++++----- .../kotlin/unit/LuminousExposureUnits.kt | 10 +++++----- .../commonMain/kotlin/unit/LuminousFluxUnit.kt | 10 +++++----- .../commonMain/kotlin/unit/MagneticFluxUnit.kt | 10 +++++----- .../kotlin/unit/MagneticInductionUnit.kt | 10 +++++----- .../commonMain/kotlin/unit/MassFlowRateUnit.kt | 10 +++++----- .../commonMain/kotlin/unit/MolarEnergyUnit.kt | 10 +++++----- .../commonMain/kotlin/unit/MolarVolumeUnit.kt | 10 +++++----- .../src/commonMain/kotlin/unit/MolarityUnit.kt | 10 +++++----- .../kotlin/unit/SpecificVolumeUnit.kt | 10 +++++----- .../kotlin/unit/ThermalResistanceUnit.kt | 10 +++++----- .../src/commonMain/kotlin/unit/VoltageUnit.kt | 10 +++++----- .../kotlin/unit/VolumetricFluxUnit.kt | 10 +++++----- .../src/commonMain/kotlin/unit/YankUnit.kt | 10 +++++----- .../kaluga/system/network/NetworkManager.kt | 2 +- .../system/network/NetworkManagerBuilder.kt | 2 +- .../network/state/NetworkStateRepoBuilder.kt | 2 +- .../system/network/BaseNetworkManager.kt | 2 +- .../splendo/kaluga/system/network/Network.kt | 2 +- .../system/network/state/NetworkState.kt | 2 +- .../system/network/state/NetworkStateRepo.kt | 2 +- .../network/state/NetworkStateRepoBuilder.kt | 2 +- .../system/network/MockNetworkManager.kt | 2 +- .../network/MockNetworkStateRepoBuilder.kt | 2 +- .../kaluga/system/network/NetworkStateTest.kt | 2 +- .../system/network/NetworkManager.kt | 2 +- .../system/network/NetworkManagerBuilder.kt | 2 +- .../network/state/NetworkStateRepoBuilder.kt | 2 +- .../system/network/NetworkManagerBuilder.kt | 2 +- .../network/state/NetworkStateRepoBuilder.kt | 2 +- .../system/network/NetworkManagerBuilder.kt | 2 +- .../network/state/NetworkStateRepoBuilder.kt | 2 +- .../kotlin/TestBaseInstrumentationTest.kt | 2 +- .../kotlin/mock/android/MockContext.kt | 10 +++++----- .../kotlin/TestBaseUnitTest.kt | 2 +- .../src/commonMain/kotlin/awaitAllBlocking.kt | 2 +- .../src/commonMain/kotlin/ui_thread_testing.kt | 2 +- .../src/commonTest/kotlin/FlowTestTest.kt | 2 +- .../src/commonTest/kotlin/UIThreadTestTest.kt | 2 +- .../src/iosMain/kotlin/mainBackground.kt | 2 +- .../src/commonMain/kotlin/MockLocationState.kt | 8 ++++---- 260 files changed, 592 insertions(+), 573 deletions(-) diff --git a/alerts/src/androidLibAndroidTest/kotlin/AlertsViewModel.kt b/alerts/src/androidLibAndroidTest/kotlin/AlertsViewModel.kt index a34ce1ae4..bc82afe34 100644 --- a/alerts/src/androidLibAndroidTest/kotlin/AlertsViewModel.kt +++ b/alerts/src/androidLibAndroidTest/kotlin/AlertsViewModel.kt @@ -1,5 +1,5 @@ /* - Copyright 2020 Splendo Consulting B.V. The Netherlands + Copyright 2022 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/alerts/src/commonTest/kotlin/AlertPresenterTests.kt b/alerts/src/commonTest/kotlin/AlertPresenterTests.kt index bd9815028..ebc75e5d3 100644 --- a/alerts/src/commonTest/kotlin/AlertPresenterTests.kt +++ b/alerts/src/commonTest/kotlin/AlertPresenterTests.kt @@ -1,6 +1,6 @@ /* -Copyright 2020 Splendo Consulting B.V. The Netherlands +Copyright 2022 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/alerts/src/commonTest/kotlin/AlertsTest.kt b/alerts/src/commonTest/kotlin/AlertsTest.kt index f05a1a40b..2b8c96f61 100644 --- a/alerts/src/commonTest/kotlin/AlertsTest.kt +++ b/alerts/src/commonTest/kotlin/AlertsTest.kt @@ -1,3 +1,21 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.test import com.splendo.kaluga.alerts.Alert diff --git a/architecture-compose/src/androidLibAndroidTest/kotlin/TestActivity.kt b/architecture-compose/src/androidLibAndroidTest/kotlin/TestActivity.kt index ffc2f7078..5ceb48b68 100644 --- a/architecture-compose/src/androidLibAndroidTest/kotlin/TestActivity.kt +++ b/architecture-compose/src/androidLibAndroidTest/kotlin/TestActivity.kt @@ -1,5 +1,5 @@ /* - Copyright 2020 Splendo Consulting B.V. The Netherlands + Copyright 2022 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/architecture/src/androidLibAndroidTest/AndroidManifest.xml b/architecture/src/androidLibAndroidTest/AndroidManifest.xml index 6df5694e1..b2367acb9 100644 --- a/architecture/src/androidLibAndroidTest/AndroidManifest.xml +++ b/architecture/src/androidLibAndroidTest/AndroidManifest.xml @@ -1,5 +1,5 @@ + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt new file mode 100644 index 000000000..2145a4a03 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt @@ -0,0 +1,76 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example + +import android.os.Bundle +import com.google.android.material.tabs.TabLayout +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation +import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class ExampleActivity : KalugaViewModelActivity(R.layout.activity_example) { + + override val viewModel: ExampleViewModel by viewModel { + parametersOf( + ActivityNavigator { action -> + when (action) { + ExampleTabNavigation.FeatureList -> NavigationSpec.Fragment( + R.id.example_fragment, + createFragment = { FeaturesListFragment() } + ) + ExampleTabNavigation.Info -> NavigationSpec.Fragment( + R.id.example_fragment, + createFragment = { InfoFragment() } + ) + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val tabs: TabLayout = findViewById(R.id.tabs) + + viewModel.tabs.observeInitialized { exampleTabs -> + tabs.removeAllTabs() + exampleTabs.forEach { tab -> + tabs.addTab(tabs.newTab().setText(tab.title).setTag(tab)) + } + } + + tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabReselected(tab: TabLayout.Tab?) { + val exampleTab = tab?.tag as? ExampleViewModel.Tab ?: return + viewModel.tab.post(exampleTab) + } + + override fun onTabSelected(tab: TabLayout.Tab?) { + val exampleTab = tab?.tag as? ExampleViewModel.Tab ?: return + viewModel.tab.post(exampleTab) + } + + override fun onTabUnselected(tab: TabLayout.Tab?) { } + }) + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt new file mode 100644 index 000000000..4b3b334fe --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt @@ -0,0 +1,39 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example + +import android.app.Application +import com.splendo.kaluga.base.ApplicationHolder +import com.splendo.kaluga.example.di.utilitiesModule +import com.splendo.kaluga.example.di.viewModelModule +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin + +class ExampleApplication : Application() { + + override fun onCreate() { + super.onCreate() + ApplicationHolder.application = this + + startKoin { + androidContext(this@ExampleApplication) + modules(utilitiesModule, viewModelModule) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt new file mode 100644 index 000000000..9132c61e1 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt @@ -0,0 +1,112 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment +import com.splendo.kaluga.example.alerts.AlertsActivity +import com.splendo.kaluga.example.architecture.ArchitectureInputActivity +import com.splendo.kaluga.example.beacons.BeaconsActivity +import com.splendo.kaluga.example.bluetooth.BluetoothActivity +import com.splendo.kaluga.example.databinding.ViewListButtonBinding +import com.splendo.kaluga.example.datetimepicker.DateTimePickerActivity +import com.splendo.kaluga.example.keyboard.KeyboardManagerActivity +import com.splendo.kaluga.example.link.LinksActivity +import com.splendo.kaluga.example.loading.LoadingActivity +import com.splendo.kaluga.example.location.LocationActivity +import com.splendo.kaluga.example.permissions.PermissionsDemoListActivity +import com.splendo.kaluga.example.platformspecific.PlatformSpecificActivity +import com.splendo.kaluga.example.resources.ResourcesActivity +import com.splendo.kaluga.example.shared.viewmodel.featureList.Feature +import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel +import com.splendo.kaluga.example.system.SystemActivity +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class FeaturesListFragment : KalugaViewModelFragment(R.layout.fragment_features_list) { + + override val viewModel: FeatureListViewModel by viewModel { + parametersOf( + ActivityNavigator { action -> + when (action) { + FeatureListNavigationAction.Location -> NavigationSpec.Activity() + FeatureListNavigationAction.Permissions -> NavigationSpec.Activity() + FeatureListNavigationAction.Alerts -> NavigationSpec.Activity() + FeatureListNavigationAction.DateTimePicker -> NavigationSpec.Activity() + FeatureListNavigationAction.LoadingIndicator -> NavigationSpec.Activity() + FeatureListNavigationAction.Architecture -> NavigationSpec.Activity() + FeatureListNavigationAction.Keyboard -> NavigationSpec.Activity() + FeatureListNavigationAction.Links -> NavigationSpec.Activity() + FeatureListNavigationAction.System -> NavigationSpec.Activity() + FeatureListNavigationAction.Bluetooth -> NavigationSpec.Activity() + FeatureListNavigationAction.Beacons -> NavigationSpec.Activity() + FeatureListNavigationAction.Resources -> NavigationSpec.Activity() + FeatureListNavigationAction.PlatformSpecific -> NavigationSpec.Activity() + } + } + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val adapter = FeaturesAdapter(viewModel).apply { + + view.findViewById(R.id.features_list).adapter = this + } + viewModel.feature.observeInitialized { adapter.features = it } + } +} + +class FeaturesAdapter(private val viewModel: FeatureListViewModel) : RecyclerView.Adapter() { + + class FeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { + val button = binding.button + } + + var features: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeatureViewHolder { + val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return FeatureViewHolder(binding) + } + + override fun getItemCount(): Int = features.size + + override fun onBindViewHolder(holder: FeatureViewHolder, position: Int) { + features.getOrNull(position)?.let { feature -> + holder.button.text = feature.title + holder.button.setOnClickListener { viewModel.onFeaturePressed(feature) } + } ?: run { + holder.button.text = null + holder.button.setOnClickListener(null) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt new file mode 100644 index 000000000..0520e4b2f --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt @@ -0,0 +1,125 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.widget.AppCompatButton +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment +import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpecRow +import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation +import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel +import com.splendo.kaluga.example.shared.viewmodel.info.LinkSpecRow +import com.splendo.kaluga.example.shared.viewmodel.info.MailSpecRow +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import java.net.URL + +class InfoFragment : KalugaViewModelFragment(R.layout.fragment_info) { + + override val viewModel: InfoViewModel by viewModel { + parametersOf( + ActivityNavigator> { action -> + when (action) { + is InfoNavigation.Dialog -> { + val title = action.bundle?.get(DialogSpecRow.TitleRow) ?: "" + val message = action.bundle?.get(DialogSpecRow.MessageRow) ?: "" + NavigationSpec.Dialog( + createDialog = { + InfoDialog(title, message) + } + ) + } + is InfoNavigation.Link -> NavigationSpec.Browser( + URL(action.bundle!!.get(LinkSpecRow.LinkRow)), + NavigationSpec.Browser.Type.Normal + ) + is InfoNavigation.Mail -> NavigationSpec.Email( + NavigationSpec.Email.EmailSettings( + to = action.bundle?.get(MailSpecRow.ToRow) ?: emptyList(), + subject = action.bundle?.get(MailSpecRow.SubjectRow) + ) + ) + } + } + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + + super.onViewCreated(view, savedInstanceState) + + val adapter = InfoAdapter(viewModel).apply { + view.findViewById(R.id.info_buttons) + .adapter = this + } + viewModel.buttons.observeInitialized { adapter.buttons = it } + } +} + +class InfoAdapter(private val viewModel: InfoViewModel) : RecyclerView.Adapter() { + + class InfoViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button) + + var buttons: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InfoViewHolder { + val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton + return InfoViewHolder(button) + } + + override fun getItemCount(): Int = buttons.size + + override fun onBindViewHolder(holder: InfoViewHolder, position: Int) { + buttons.getOrNull(position)?.let { button -> + holder.button.text = button.title + holder.button.setOnClickListener { viewModel.onButtonPressed(button) } + } ?: run { + holder.button.text = null + holder.button.setOnClickListener(null) + } + } +} + +class InfoDialog(val title: String, val message: String) : DialogFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val v = inflater.inflate(R.layout.dialog_info, container, false) + + v.findViewById(R.id.title).text = title + v.findViewById(R.id.message).text = message + + return v + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt new file mode 100644 index 000000000..5fa52e39d --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt @@ -0,0 +1,42 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.alerts + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.appcompat.widget.AppCompatButton +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +@SuppressLint("SetTextI18n") +class AlertsActivity : KalugaViewModelActivity(R.layout.activity_alerts) { + + override val viewModel: AlertViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + findViewById(R.id.btn_simple_alert).setOnClickListener { viewModel.showAlert() } + findViewById(R.id.btn_dismissible_alert).setOnClickListener { viewModel.showAndDismissAfter(3) } + findViewById(R.id.btn_alert_list).setOnClickListener { viewModel.showAlertWithList() } + findViewById(R.id.btn_alert_input_field).setOnClickListener { viewModel.showAlertWithInput() } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/android/MainActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/android/MainActivity.kt deleted file mode 100644 index be6b7c113..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/android/MainActivity.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.splendo.kaluga.example.android - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.splendo.kaluga.example.Greeting - -@Composable -fun MyApplicationTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val colors = if (darkTheme) { - darkColors( - primary = Color(0xFFBB86FC), - primaryVariant = Color(0xFF3700B3), - secondary = Color(0xFF03DAC5) - ) - } else { - lightColors( - primary = Color(0xFF6200EE), - primaryVariant = Color(0xFF3700B3), - secondary = Color(0xFF03DAC5) - ) - } - val typography = Typography( - body1 = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp - ) - ) - val shapes = Shapes( - small = RoundedCornerShape(4.dp), - medium = RoundedCornerShape(4.dp), - large = RoundedCornerShape(0.dp) - ) - - MaterialTheme( - colors = colors, - typography = typography, - shapes = shapes, - content = content - ) -} - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MyApplicationTheme { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colors.background - ) { - Greeting(Greeting().greeting()) - } - } - } - } -} - -@Composable -fun Greeting(text: String) { - Text(text = text) -} - -@Preview -@Composable -fun DefaultPreview() { - MyApplicationTheme { - Greeting("Hello, Android!") - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt new file mode 100644 index 000000000..879360e98 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt @@ -0,0 +1,56 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.architecture + +import android.os.Bundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.toTypedProperty +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityArchitectureDetailsBinding +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class ArchitectureDetailsActivity : KalugaViewModelActivity() { + + companion object { + val resultCode = 1 + } + + override val viewModel: ArchitectureDetailsViewModel by viewModel { + val type = NavigationBundleSpecType.SerializedType(InputDetails.serializer()) + intent.extras?.toTypedProperty(type)?.let { details -> + parametersOf(details) + } ?: parametersOf("", 0) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityArchitectureDetailsBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + binding.viewModel = viewModel + binding.lifecycleOwner = this + } + + override fun onBackPressed() { + viewModel.onClosePressed() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt new file mode 100644 index 000000000..b4e3f51fa --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt @@ -0,0 +1,74 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.architecture + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.result.contract.ActivityResultContract +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction +import com.splendo.kaluga.architecture.navigation.toTypedProperty +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityArchitectureInputBinding +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class ArchitectureInputActivity : KalugaViewModelActivity() { + + inner class Contract : ActivityResultContract() { + + override fun createIntent(context: Context, input: Intent): Intent = input + + override fun parseResult( + resultCode: Int, + intent: Intent? + ): InputDetails? = intent?.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(InputDetails.serializer())) + } + + override val viewModel: ArchitectureInputViewModel by viewModel { + parametersOf( + ActivityNavigator> { + NavigationSpec.Activity( + launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } + ) + } + ) + } + + private val contract = registerForActivityResult(Contract()) { inputDetails -> + inputDetails?.let { + viewModel.nameInput.post(it.name) + viewModel.numberInput.post(it.number.toString()) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityArchitectureInputBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + binding.viewModel = viewModel + binding.lifecycleOwner = this + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt new file mode 100644 index 000000000..b1d59b78b --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt @@ -0,0 +1,81 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.beacons + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.core.view.forEach +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivityBeaconsBinding +import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel +import kotlinx.coroutines.runBlocking +import org.koin.androidx.viewmodel.ext.android.viewModel + +class BeaconsActivity : KalugaViewModelActivity() { + + override val viewModel: BeaconsListViewModel by viewModel() + + private lateinit var adapter: BeaconsAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityBeaconsBinding.inflate(layoutInflater, null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + adapter = BeaconsAdapter(this) + binding.beaconsList.adapter = adapter + binding.beaconsList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) + + viewModel.isScanning.observe { + invalidateOptionsMenu() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.bluetooth_menu, menu) + return true + } + + override fun onPrepareOptionsMenu(menu: Menu?): Boolean = runBlocking { + val scanning = viewModel.isScanning.current + menu?.forEach { item -> + when (item.itemId) { + R.id.start_scanning -> item.isVisible = !scanning + R.id.stop_scanning -> item.isVisible = scanning + } + } + super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.start_scanning, + R.id.stop_scanning -> { + viewModel.onScanPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt new file mode 100644 index 000000000..1c76de087 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt @@ -0,0 +1,64 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.beacons + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.example.databinding.BeaconItemBinding +import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconViewModel + +class BeaconsAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + + class BeaconItemViewHolder(val binding: BeaconItemBinding) : RecyclerView.ViewHolder(binding.root) + + internal var beacons: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BeaconItemViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = BeaconItemBinding.inflate(inflater, parent, false) + binding.lifecycleOwner = lifecycleOwner + return BeaconItemViewHolder(binding) + } + + override fun getItemCount(): Int { + return beacons.size + } + + override fun onBindViewHolder(holder: BeaconItemViewHolder, position: Int) { + val viewModel = beacons[position] + holder.binding.viewModel = viewModel + } + + override fun onViewAttachedToWindow(holder: BeaconItemViewHolder) { + super.onViewAttachedToWindow(holder) + + holder.binding.viewModel?.didResume() + } + + override fun onViewDetachedFromWindow(holder: BeaconItemViewHolder) { + super.onViewDetachedFromWindow(holder) + + holder.binding.viewModel?.didPause() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt new file mode 100644 index 000000000..74e5ad859 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt @@ -0,0 +1,31 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.beacons + +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconViewModel + +object BeaconsBinding { + @BindingAdapter("beacons") + @JvmStatic + fun bindBeacons(view: RecyclerView, beacons: List?) { + val beaconsAdapter = view.adapter as? BeaconsAdapter ?: return + beaconsAdapter.beacons = beacons ?: emptyList() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt new file mode 100644 index 000000000..d07256ba2 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt @@ -0,0 +1,93 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bluetooth + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.core.view.forEach +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivityBluetoothBinding +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListNavigation +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel +import kotlinx.coroutines.runBlocking +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class BluetoothActivity : KalugaViewModelActivity() { + + override val viewModel: BluetoothListViewModel by viewModel { + parametersOf( + ActivityNavigator { + NavigationSpec.Activity() + } + ) + } + + private lateinit var adapter: BluetoothAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityBluetoothBinding.inflate(layoutInflater, null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + adapter = BluetoothAdapter(this) + binding.devicesList.adapter = adapter + binding.devicesList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) + + viewModel.isScanning.observe { + invalidateOptionsMenu() + } + + viewModel.title.observe(::setTitle) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.bluetooth_menu, menu) + return true + } + + override fun onPrepareOptionsMenu(menu: Menu?): Boolean = runBlocking { + val scanning = viewModel.isScanning.current + menu?.forEach { item -> + when (item.itemId) { + R.id.start_scanning -> item.isVisible = !scanning + R.id.stop_scanning -> item.isVisible = scanning + } + } + super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.start_scanning, + R.id.stop_scanning -> { + viewModel.onScanPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt new file mode 100644 index 000000000..5010367be --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt @@ -0,0 +1,76 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bluetooth + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.BindingAdapter +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.example.databinding.BluetoothItemBinding +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListDeviceViewModel + +object DevicesBinding { + + @BindingAdapter("devices") + @JvmStatic + fun bindDevices(view: RecyclerView, devices: List?) { + val bluetoothAdapter = view.adapter as? BluetoothAdapter + ?: return + bluetoothAdapter.bluetoothDevices = devices ?: emptyList() + } +} + +class BluetoothAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + + class BluetoothItemViewHolder(val binding: BluetoothItemBinding) : RecyclerView.ViewHolder(binding.root) + + internal var bluetoothDevices: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BluetoothItemViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = BluetoothItemBinding.inflate(inflater, parent, false) + binding.lifecycleOwner = lifecycleOwner + return BluetoothItemViewHolder(binding) + } + + override fun getItemCount(): Int { + return bluetoothDevices.size + } + + override fun onBindViewHolder(holder: BluetoothItemViewHolder, position: Int) { + val viewModel = bluetoothDevices[position] + holder.binding.viewModel = viewModel + } + + override fun onViewAttachedToWindow(holder: BluetoothItemViewHolder) { + super.onViewAttachedToWindow(holder) + + holder.binding.viewModel?.didResume() + } + + override fun onViewDetachedFromWindow(holder: BluetoothItemViewHolder) { + super.onViewDetachedFromWindow(holder) + + holder.binding.viewModel?.didPause() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt new file mode 100644 index 000000000..a0b9e6322 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt @@ -0,0 +1,78 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bluetooth + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.BindingAdapter +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.example.databinding.BluetoothCharacteristicItemBinding +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothCharacteristicViewModel + +object CharacteristicsBinding { + + @BindingAdapter("characteristics") + @JvmStatic + fun bindCharacteristics(view: RecyclerView, characteristics: List?) { + val characteristicAdapter = view.adapter as? BluetoothCharacteristicAdapter + ?: return + characteristicAdapter.characteristics = characteristics ?: emptyList() + } +} + +class BluetoothCharacteristicAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + + class BluetoothCharacteristicItemViewHolder(val characteristicItem: BluetoothCharacteristicItemBinding) : RecyclerView.ViewHolder(characteristicItem.root) + + internal var characteristics = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BluetoothCharacteristicItemViewHolder { + val binding = BluetoothCharacteristicItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + binding.lifecycleOwner = lifecycleOwner + binding.descriptors.adapter = BluetoothDescriptorAdapter(lifecycleOwner) + return BluetoothCharacteristicItemViewHolder(binding) + } + + override fun getItemCount(): Int { + return characteristics.size + } + + override fun onBindViewHolder(holder: BluetoothCharacteristicItemViewHolder, position: Int) { + holder.characteristicItem.viewModel = characteristics[position] + } + + override fun onViewAttachedToWindow(holder: BluetoothCharacteristicItemViewHolder) { + super.onViewAttachedToWindow(holder) + + holder.characteristicItem.viewModel?.didResume() + } + + override fun onViewDetachedFromWindow(holder: BluetoothCharacteristicItemViewHolder) { + super.onViewDetachedFromWindow(holder) + + holder.characteristicItem.viewModel?.didPause() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt new file mode 100644 index 000000000..e59ee3b49 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt @@ -0,0 +1,77 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bluetooth + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.BindingAdapter +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.example.databinding.BluetoothDescriptorItemBinding +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDescriptorViewModel + +object DescriptorsBinding { + + @BindingAdapter("descriptors") + @JvmStatic + fun bindDescriptors(view: RecyclerView, descriptors: List?) { + val descriptorAdapter = view.adapter as? BluetoothDescriptorAdapter + ?: return + descriptorAdapter.descriptors = descriptors ?: emptyList() + } +} + +class BluetoothDescriptorAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + + class BluetoothDescriptorItemViewHolder(val descriptorItem: BluetoothDescriptorItemBinding) : RecyclerView.ViewHolder(descriptorItem.root) + + var descriptors = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BluetoothDescriptorItemViewHolder { + val binding = BluetoothDescriptorItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + binding.lifecycleOwner = lifecycleOwner + return BluetoothDescriptorItemViewHolder(binding) + } + + override fun getItemCount(): Int { + return descriptors.size + } + + override fun onBindViewHolder(holder: BluetoothDescriptorItemViewHolder, position: Int) { + holder.descriptorItem.viewModel = descriptors[position] + } + + override fun onViewAttachedToWindow(holder: BluetoothDescriptorItemViewHolder) { + super.onViewAttachedToWindow(holder) + + holder.descriptorItem.viewModel?.didResume() + } + + override fun onViewDetachedFromWindow(holder: BluetoothDescriptorItemViewHolder) { + super.onViewDetachedFromWindow(holder) + + holder.descriptorItem.viewModel?.didPause() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt new file mode 100644 index 000000000..22b422dfd --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt @@ -0,0 +1,49 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bluetooth + +import android.os.Bundle +import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityBluetoothMoreBinding +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpec +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpecRow +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class BluetoothMoreActivity : KalugaViewModelActivity() { + + override val viewModel: BluetoothDeviceDetailViewModel by viewModel { + val deviceDetailsSpec = DeviceDetailsSpec() + intent.extras?.toNavigationBundle(deviceDetailsSpec)?.let { bundle -> + parametersOf(bundle.get(DeviceDetailsSpecRow.UUIDRow)) + } ?: parametersOf("") + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityBluetoothMoreBinding.inflate(layoutInflater, null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + + binding.serviceList.adapter = BluetoothServiceAdapter(this) + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt new file mode 100644 index 000000000..46711f70e --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt @@ -0,0 +1,78 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bluetooth + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.BindingAdapter +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.example.databinding.BluetoothServiceItemBinding +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothServiceViewModel + +object ServicesBinding { + + @BindingAdapter("services") + @JvmStatic + fun bindServices(view: RecyclerView, services: List?) { + val serviceAdapter = view.adapter as? BluetoothServiceAdapter + ?: return + serviceAdapter.services = services ?: emptyList() + } +} + +class BluetoothServiceAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + + class BluetoothServiceItemViewHolder(val serviceItem: BluetoothServiceItemBinding) : RecyclerView.ViewHolder(serviceItem.root) + + internal var services: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BluetoothServiceItemViewHolder { + val binding = BluetoothServiceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + binding.lifecycleOwner = lifecycleOwner + binding.characteristicsList.adapter = BluetoothCharacteristicAdapter(lifecycleOwner) + return BluetoothServiceItemViewHolder(binding) + } + + override fun getItemCount(): Int { + return services.size + } + + override fun onBindViewHolder(holder: BluetoothServiceItemViewHolder, position: Int) { + holder.serviceItem.viewModel = services[position] + } + + override fun onViewAttachedToWindow(holder: BluetoothServiceItemViewHolder) { + super.onViewAttachedToWindow(holder) + + holder.serviceItem.viewModel?.didResume() + } + + override fun onViewDetachedFromWindow(holder: BluetoothServiceItemViewHolder) { + super.onViewDetachedFromWindow(holder) + + holder.serviceItem.viewModel?.didPause() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt new file mode 100644 index 000000000..298917892 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt @@ -0,0 +1,32 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bottomSheet + +import androidx.compose.runtime.Composable +import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.example.bottomSheet.ui.BottomSheetParentLayout + +class BottomSheetActivity : KalugaViewModelComposeActivity() { + override val viewModel = BaseLifecycleViewModel() + + @Composable + override fun Layout(viewModel: BaseLifecycleViewModel) { + BottomSheetParentLayout() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt new file mode 100644 index 000000000..7b77eb5f9 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt @@ -0,0 +1,73 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bottomSheet.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation +import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.store +import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetNavigationRouteMapper +import com.splendo.kaluga.example.contacts.ui.Padding +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetViewModel + +@Composable +fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { + val navigator = ModalBottomSheetNavigator( + contentNavHostController, + sheetContentNavHostController, + sheetState, + rememberCoroutineScope(), + ::bottomSheetNavigationRouteMapper, + ) + + val viewModel = store { + remember { + BottomSheetViewModel(navigator) + } + } + + ViewModelComposable(viewModel) { + HardwareBackButtonNavigation(onBackButtonClickHandler = { onClosePressed() }) + Column( + Modifier + .fillMaxWidth() + .padding(Padding.default) + ) { + Text(text) + Button( + modifier = Modifier + .fillMaxWidth() + .padding(Padding.default), + onClick = { onSubPagePressed() } + ) { + Text(buttonText) + } + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt new file mode 100644 index 000000000..63e4549ab --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt @@ -0,0 +1,124 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bottomSheet.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Text +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRouteController +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetSheetContentRouteController +import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator +import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteController +import com.splendo.kaluga.architecture.compose.navigation.NavigatingModalBottomSheetLayout +import com.splendo.kaluga.architecture.compose.navigation.route +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.store +import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetParentNavigationRouteMapper +import com.splendo.kaluga.example.contacts.ui.Padding +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetNavigation +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentNavigation +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentViewModel + +@Composable +fun BottomSheetParentLayout() { + MdcTheme { + val bottomSheetRouteController = BottomSheetRouteController( + NavHostRouteController(rememberNavController()), + BottomSheetSheetContentRouteController( + rememberNavController(), + rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), + rememberCoroutineScope() + ) + ) + + bottomSheetRouteController.NavigatingModalBottomSheetLayout( + sheetContent = { contentNavHostController, sheetContentNavHostController, sheetState -> + composable(BottomSheetParentNavigation.ShowSheet.route()) { + BottomSheetLayout(contentNavHostController, sheetContentNavHostController, sheetState) + } + composable(BottomSheetNavigation.SubPage.route()) { + BottomSheetSubPageLayout( + contentNavHostController, sheetContentNavHostController, sheetState + ) + } + }, + contentRoot = { contentNavHostController, sheetContentNavHostController, sheetState -> + BottomSheetParentLayoutContent(contentNavHostController, sheetContentNavHostController, sheetState) + }, + content = { contentNavHostController, _, _ -> + composable(BottomSheetParentNavigation.SubPage.route()) { + BottomSheetParentSubPageLayout(contentNavHostController) + } + } + ) + } +} + +@Composable +fun BottomSheetParentLayoutContent(contentNavHostController: NavHostController, sheetNavHostController: NavHostController, sheetState: ModalBottomSheetState) { + + val navigator = ModalBottomSheetNavigator( + NavHostRouteController(contentNavHostController), + BottomSheetSheetContentRouteController( + sheetNavHostController, + sheetState, + rememberCoroutineScope() + ), + ::bottomSheetParentNavigationRouteMapper + ) + + val viewModel = store { + remember { + BottomSheetParentViewModel(navigator) + } + } + + ViewModelComposable(viewModel) { + Column(Modifier.fillMaxWidth()) { + Button( + modifier = Modifier + .fillMaxWidth() + .padding(Padding.default), + onClick = { onShowSheetPressed() } + ) { + Text(sheetText) + } + Button( + modifier = Modifier + .fillMaxWidth() + .padding(Padding.default), + onClick = { onSubPagePressed() } + ) { + Text(subPageText) + } + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt new file mode 100644 index 000000000..20f93e69c --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt @@ -0,0 +1,53 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bottomSheet.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation +import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.store +import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetParentSubPageNavigationRouteMapper +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentSubPageViewModel + +@Composable +fun BottomSheetParentSubPageLayout(navHostController: NavHostController) { + val navigator = RouteNavigator( + navHostController, + ::bottomSheetParentSubPageNavigationRouteMapper + ) + + val viewModel = store { + remember { + BottomSheetParentSubPageViewModel(navigator) + } + } + + ViewModelComposable(viewModel) { + HardwareBackButtonNavigation(onBackButtonClickHandler = { viewModel.onBackPressed() }) + Column(Modifier.fillMaxWidth()) { + Text(text) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt new file mode 100644 index 000000000..76fce4ae4 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt @@ -0,0 +1,79 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bottomSheet.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation +import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.store +import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetSubPageNavigationRouteMapper +import com.splendo.kaluga.example.contacts.ui.Padding +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetSubPageViewModel + +@Composable +fun BottomSheetSubPageLayout( + contentNavHostController: NavHostController, + sheetContentNavHostController: NavHostController, + sheetState: ModalBottomSheetState +) { + val navigator = ModalBottomSheetNavigator( + contentNavHostController, + sheetContentNavHostController, + sheetState, + rememberCoroutineScope(), + ::bottomSheetSubPageNavigationRouteMapper + ) + + val viewModel = store { + remember { + BottomSheetSubPageViewModel(navigator) + } + } + + ViewModelComposable(viewModel) { + HardwareBackButtonNavigation(onBackButtonClickHandler = { viewModel.onBackPressed() }) + Column( + Modifier + .fillMaxWidth() + .padding(Padding.default) + ) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + Button( + modifier = Modifier.padding(Padding.default), + onClick = { onClosePressed() } + ) { + Text("X") + } + } + Text(text) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt new file mode 100644 index 000000000..616631770 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt @@ -0,0 +1,59 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.bottomSheet.viewModel + +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRoute +import com.splendo.kaluga.architecture.compose.navigation.Route +import com.splendo.kaluga.architecture.compose.navigation.bottomSheetContent +import com.splendo.kaluga.architecture.compose.navigation.bottomSheetSheetContent +import com.splendo.kaluga.architecture.compose.navigation.next +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetNavigation +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentNavigation +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentSubPageNavigation +import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetSubPageNavigation + +/** Maps a navigation action to a route string. */ +internal fun bottomSheetParentNavigationRouteMapper(action: BottomSheetParentNavigation): BottomSheetRoute { + return when (action) { + is BottomSheetParentNavigation.SubPage -> action.next.bottomSheetContent + is BottomSheetParentNavigation.ShowSheet -> action.next.bottomSheetSheetContent + } +} + +/** Maps a navigation action to a route string. */ +internal fun bottomSheetParentSubPageNavigationRouteMapper(action: BottomSheetParentSubPageNavigation): Route { + return when (action) { + is BottomSheetParentSubPageNavigation.Back -> Route.Back + } +} + +/** Maps a navigation action to a route string. */ +internal fun bottomSheetNavigationRouteMapper(action: BottomSheetNavigation): BottomSheetRoute { + return when (action) { + is BottomSheetNavigation.Close -> Route.Close.bottomSheetSheetContent + is BottomSheetNavigation.SubPage -> action.next.bottomSheetSheetContent + } +} + +/** Maps a navigation action to a route string. */ +internal fun bottomSheetSubPageNavigationRouteMapper(action: BottomSheetSubPageNavigation): BottomSheetRoute { + return when (action) { + is BottomSheetSubPageNavigation.Close -> Route.Close.bottomSheetSheetContent + is BottomSheetSubPageNavigation.Back -> Route.Back.bottomSheetSheetContent + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt new file mode 100644 index 000000000..01d6d45f2 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt @@ -0,0 +1,32 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.contacts + +import androidx.compose.runtime.Composable +import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.example.contacts.ui.ContactsLayout + +class ContactsActivity : KalugaViewModelComposeActivity() { + override val viewModel = BaseLifecycleViewModel() + + @Composable + override fun Layout(viewModel: BaseLifecycleViewModel) { + ContactsLayout() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt new file mode 100644 index 000000000..c5b96f85c --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt @@ -0,0 +1,97 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.contacts.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation +import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator +import com.splendo.kaluga.architecture.compose.navigation.rememberCombinedNavigator +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.store +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.contacts.viewModel.contactDetailsNavigationActivityMapper +import com.splendo.kaluga.example.contacts.viewModel.contactDetailsNavigationRouteMapper +import com.splendo.kaluga.example.shared.model.contacts.ContactDetails +import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsNavigation +import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsViewModel + +@Composable +fun ContactDetailsLayout(contactDetails: ContactDetails, navHostController: NavHostController) { + val routeNavigator = RouteNavigator( + navHostController, + ::contactDetailsNavigationRouteMapper + ) + + val navigator = rememberCombinedNavigator { action: ContactDetailsNavigation<*> -> + when (action) { + is ContactDetailsNavigation.Close -> routeNavigator + is ContactDetailsNavigation.SendEmail -> ::contactDetailsNavigationActivityMapper.toActivityNavigator() + } + } + + val viewModel = store { + remember { + ContactDetailsViewModel(contactDetails, navigator) + } + } + ViewModelComposable(viewModel) { + HardwareBackButtonNavigation(::back) + + Column { + val image = painterResource(id = R.drawable.ic_account) + Image( + modifier = Modifier + .size(320.dp) + .align(CenterHorizontally), + painter = image, + contentDescription = "" + ) + + Column( + modifier = Modifier.padding(horizontal = Padding.x2) + .fillMaxWidth() + ) { + Text(text = "Name:", style = MaterialTheme.typography.h6) + Text(text = contactDetails.name, style = MaterialTheme.typography.body1) + DefaultSpacer() + + Text(text = "Email:", style = MaterialTheme.typography.h6) + Text(text = contactDetails.email, style = MaterialTheme.typography.body1) + DefaultSpacer() + + Button(onClick = ::sendEmail) { + Text(text = sendEmailButtonText, style = MaterialTheme.typography.button) + } + } + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt new file mode 100644 index 000000000..a5c221458 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt @@ -0,0 +1,53 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.contacts.ui + +import androidx.compose.runtime.Composable +import androidx.navigation.compose.rememberNavController +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator +import com.splendo.kaluga.architecture.compose.navigation.SetupNavHost +import com.splendo.kaluga.architecture.compose.navigation.composable +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.example.contacts.viewModel.contactListNavigationRouteMapper +import com.splendo.kaluga.example.shared.model.contacts.ContactDetails +import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation + +@Composable +fun ContactsLayout() { + MdcTheme { + val navigator = RouteNavigator( + rememberNavController(), + ::contactListNavigationRouteMapper + ) + + navigator.SetupNavHost( + rootView = { + ContactsListLayout(navigator = navigator) + } + ) { navHostController -> + composable( + type = NavigationBundleSpecType.SerializedType( + ContactDetails.serializer() + ) + ) { details -> + ContactDetailsLayout(details, navHostController) + } + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt new file mode 100644 index 000000000..2e72cd4e3 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt @@ -0,0 +1,100 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.contacts.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.store +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.model.contacts.ContactDetails +import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation +import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListViewModel + +@Composable +fun ContactsListLayout(navigator: Navigator>) { + val viewModel = store { + remember { + ContactsListViewModel(navigator) + } + } + + ViewModelComposable(viewModel) { + val items by contacts.state() + LazyColumn(modifier = Modifier.padding(Padding.default)) { + items(items) { contactDetails -> + ListItem(contactDetails) { + onContactClick(contactDetails) + } + + DefaultSpacer() + } + } + } +} + +@Composable +private fun ListItem( + contactDetails: ContactDetails, + onClickHandler: () -> Unit +) { + Row( + modifier = Modifier + .clickable(onClick = onClickHandler) + .fillMaxWidth() + ) { + val image = painterResource(id = R.drawable.ic_account) + Image( + modifier = Modifier.size(48.dp), + painter = image, + contentDescription = "" + ) + + DefaultSpacer() + + Text( + modifier = Modifier.align(CenterVertically), + text = contactDetails.name, + style = MaterialTheme.typography.h5 + ) + } +} + +@Composable +@Preview +private fun ListItemPreview() { + ListItem(ContactDetails("name", "email")) { } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt new file mode 100644 index 000000000..4bac3e533 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt @@ -0,0 +1,17 @@ +package com.splendo.kaluga.example.contacts.ui + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +object Padding { + val default = 8.dp + val x2 = 16.dp +} + +@Composable +fun DefaultSpacer() { + Spacer(modifier = Modifier.size(Padding.default)) +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt new file mode 100644 index 000000000..09e1f9b2d --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt @@ -0,0 +1,52 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.contacts.viewModel + +import com.splendo.kaluga.architecture.compose.navigation.Route +import com.splendo.kaluga.architecture.compose.navigation.next +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsNavigation +import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation + +/** Maps a navigation action to a route string. */ +internal fun contactListNavigationRouteMapper(action: ContactsListNavigation<*>): Route { + return when (action) { + is ContactsListNavigation.ShowContactDetails -> action.next + } +} + +/** Maps a navigation action to a route string. */ +internal fun contactDetailsNavigationRouteMapper(action: ContactDetailsNavigation<*>): Route { + return when (action) { + is ContactDetailsNavigation.Close -> Route.Back + else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}") + } +} + +/** Maps a navigation action to a NavigationSpec. */ +internal fun contactDetailsNavigationActivityMapper(action: ContactDetailsNavigation<*>): NavigationSpec { + return when (action) { + is ContactDetailsNavigation.SendEmail -> NavigationSpec.Email( + emailSettings = NavigationSpec.Email.EmailSettings( + type = NavigationSpec.Email.Type.Plain, + to = listOf(action.bundle!!.get(action.type)) + ) + ) + else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}") + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt new file mode 100644 index 000000000..b766243e4 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt @@ -0,0 +1,21 @@ +package com.splendo.kaluga.example.datetimepicker + +import android.os.Bundle +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityDateTimePickerBinding +import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel +import org.koin.android.ext.android.inject + +class DateTimePickerActivity : KalugaViewModelActivity() { + + override val viewModel: DateTimePickerViewModel by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityDateTimePickerBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + binding.viewModel = viewModel + binding.lifecycleOwner = this + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt new file mode 100644 index 000000000..4e652e223 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt @@ -0,0 +1,221 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.di + +/* ktlint-disable no-wildcard-imports */ +import com.splendo.kaluga.alerts.AlertPresenter +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction +import com.splendo.kaluga.base.singleThreadDispatcher +import com.splendo.kaluga.bluetooth.Bluetooth +import com.splendo.kaluga.bluetooth.BluetoothBuilder +import com.splendo.kaluga.bluetooth.beacons.Beacons +import com.splendo.kaluga.bluetooth.scanner.BaseScanner +import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter +import com.splendo.kaluga.example.architecture.ArchitectureDetailsActivity +import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation +import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel +import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails +import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListNavigation +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel +import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel +import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel +import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel +import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation +import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel +import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel +import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions +import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow +import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel +import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel +import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel +import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel +import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel +import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions +import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel +import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel +import com.splendo.kaluga.hud.HUD +import com.splendo.kaluga.keyboard.FocusHandler +import com.splendo.kaluga.keyboard.KeyboardManager +import com.splendo.kaluga.links.LinksBuilder +import com.splendo.kaluga.location.LocationStateRepoBuilder +import com.splendo.kaluga.logging.RestrictedLogLevel +import com.splendo.kaluga.logging.RestrictedLogger +import com.splendo.kaluga.permissions.base.BasePermissionManager +import com.splendo.kaluga.permissions.base.Permission +import com.splendo.kaluga.permissions.base.Permissions +import com.splendo.kaluga.permissions.base.PermissionsBuilder +import com.splendo.kaluga.permissions.location.LocationPermission +import com.splendo.kaluga.permissions.registerAllPermissions +import com.splendo.kaluga.resources.StyledStringBuilder +import com.splendo.kaluga.review.ReviewManager +import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module +import kotlin.time.Duration.Companion.minutes + +val utilitiesModule = module { + single { + Permissions( + PermissionsBuilder().apply { + MainScope().launch { + registerAllPermissions( + settings = BasePermissionManager.Settings( + logger = RestrictedLogger(RestrictedLogLevel.Verbose) + ) + ) + } + }, + coroutineContext = singleThreadDispatcher("Permissions") + ) + } + single { LocationStateRepoBuilder() } + single { + BluetoothBuilder().create({ BaseScanner.Settings(get()) }) + } + single { Beacons(get(), timeout = 1.minutes) } +} + +val viewModelModule = module { + viewModel { (navigator: Navigator) -> + ExampleViewModel( + navigator + ) + } + + viewModel { (navigator: Navigator) -> + FeatureListViewModel( + navigator + ) + } + + viewModel { (navigator: Navigator>) -> + InfoViewModel( + ReviewManager.Builder(), + navigator + ) + } + + viewModel { (navigator: Navigator) -> + PermissionsListViewModel( + navigator + ) + } + + viewModel { (permission: Permission) -> PermissionViewModel(get(), permission) } + + viewModel { (permission: LocationPermission) -> LocationViewModel(permission, get()) } + + viewModel { (navigator: Navigator>) -> + ArchitectureInputViewModel( + navigator + ) + } + + viewModel { (initialDetail: InputDetails) -> + ArchitectureDetailsViewModel( + initialDetail, + ActivityNavigator { + NavigationSpec.Close(ArchitectureDetailsActivity.resultCode) + } + ) + } + + viewModel { + AlertViewModel(AlertPresenter.Builder()) + } + + viewModel { + DateTimePickerViewModel(DateTimePickerPresenter.Builder()) + } + + viewModel { + HudViewModel(HUD.Builder()) + } + + viewModel { (keyboardBuilder: KeyboardManager.Builder, focusHandler: FocusHandler) -> + KeyboardViewModel(keyboardBuilder, focusHandler) + } + + viewModel { (navigator: Navigator>) -> + LinksViewModel( + LinksBuilder(), + AlertPresenter.Builder(), + navigator + ) + } + + viewModel { (navigator: Navigator>) -> + SystemViewModel( + navigator + ) + } + + viewModel { + NetworkViewModel(NetworkStateRepoBuilder(get())) + } + + viewModel { (navigator: Navigator) -> + BluetoothListViewModel( + get(), + navigator + ) + } + + viewModel { (identifier: com.splendo.kaluga.bluetooth.device.Identifier) -> + BluetoothDeviceDetailViewModel(get(), identifier) + } + + viewModel { + BeaconsListViewModel(get()) + } + + viewModel { (navigator: Navigator) -> + ResourcesListViewModel( + navigator + ) + } + + viewModel { + ColorViewModel(AlertPresenter.Builder()) + } + + viewModel { + LabelViewModel(StyledStringBuilder.Provider()) + } + + viewModel { + ButtonViewModel(StyledStringBuilder.Provider(), AlertPresenter.Builder()) + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt new file mode 100644 index 000000000..15ffc09e7 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt @@ -0,0 +1,46 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.keyboard + +import android.os.Bundle +import androidx.appcompat.widget.AppCompatButton +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel +import com.splendo.kaluga.keyboard.ViewFocusHandler +import com.splendo.kaluga.keyboard.keyboardManagerBuilder +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class KeyboardManagerActivity : KalugaViewModelActivity(R.layout.activity_keyboard_manager) { + + override val viewModel: KeyboardViewModel by viewModel { + parametersOf( + keyboardManagerBuilder(), + ViewFocusHandler(R.id.edit_field) + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + findViewById(R.id.btn_show_keyboard).setOnClickListener { viewModel.onShowPressed() } + findViewById(R.id.btn_hide_keyboard).setOnClickListener { viewModel.onHidePressed() } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt new file mode 100644 index 000000000..485777505 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt @@ -0,0 +1,79 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.link + +import android.content.Intent +import android.os.Bundle +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivityLinkBinding +import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions +import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow +import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import java.net.URL + +class LinksActivity : KalugaViewModelActivity(R.layout.activity_link) { + + override val viewModel: LinksViewModel by viewModel { + parametersOf( + ActivityNavigator> { + when (it) { + is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser( + URL(it.bundle!!.get(BrowserSpecRow.UrlSpecRow)), + NavigationSpec.Browser.Type.Normal + ) + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setupBindings() + + handleAppLinks(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { + handleAppLinks(it) + } + } + + private fun handleAppLinks(intent: Intent) { + intent.let { i -> + i.data?.let { uri -> + val url = URL(uri.scheme, uri.host, uri.path) + viewModel.handleIncomingLink("$url?${uri.encodedQuery}") + } + } + } + + private fun setupBindings() { + val binding = ActivityLinkBinding.inflate(layoutInflater, null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt new file mode 100644 index 000000000..11468040e --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt @@ -0,0 +1,45 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.loading + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.appcompat.widget.AppCompatButton +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +@SuppressLint("SetTextI18n") +class LoadingActivity : KalugaViewModelActivity(R.layout.activity_loading) { + + override val viewModel: HudViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + findViewById(R.id.btn_show_loading_indicator_system).setOnClickListener { + viewModel.onShowSystemPressed() + } + + findViewById(R.id.btn_show_loading_indicator_custom).setOnClickListener { + viewModel.onShowCustomPressed() + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt new file mode 100644 index 000000000..0ef45e6d2 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt @@ -0,0 +1,61 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.location + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.AppCompatTextView +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel +import com.splendo.kaluga.permissions.location.LocationPermission +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class LocationActivity : KalugaViewModelActivity(R.layout.activity_location) { + + companion object { + private val permission = LocationPermission(background = false, precise = true) + } + + override val viewModel: LocationViewModel by viewModel { + parametersOf(permission) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + findViewById(R.id.enable_background).setOnClickListener { + startService(Intent(applicationContext, LocationBackgroundService::class.java)) + } + + findViewById(R.id.disable_background).setOnClickListener { + stopService(Intent(applicationContext, LocationBackgroundService::class.java)) + } + + viewModel.location.observeInitialized { + val info = findViewById(R.id.info) + info.text = it + info.animate().withEndAction { + info.animate().setDuration(10000).alpha(0.12f).start() + }.alpha(1f).setDuration(100).start() + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt new file mode 100644 index 000000000..d7cd3d96e --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt @@ -0,0 +1,105 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.location + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel +import com.splendo.kaluga.permissions.location.LocationPermission +import org.koin.core.component.KoinComponent +import org.koin.core.component.get + +class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinComponent { + + companion object { + const val notificationId = 1 + const val channelId = "location_channel" + const val channelName = "Kaluga Location" + + private val permission = LocationPermission(background = true, precise = true) + } + + private val notificationService by lazy { applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } + + private val viewModel = LocationViewModel(permission, get()) + + override fun onCreate() { + super.onCreate() + + viewModel.location.observeInitialized { message -> + NotificationManagerCompat.from(applicationContext).notify(notificationId, getNotification(message)) + } + + startForeground(notificationId, getNotification("")) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + super.onStartCommand(intent, flags, startId) + viewModel.didResume() + return START_NOT_STICKY + } + + override fun onDestroy() { + super.onDestroy() + viewModel.didPause() + viewModel.onCleared() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + stopForeground(STOP_FOREGROUND_REMOVE) + } else { + stopForeground(true) + } + NotificationManagerCompat.from(applicationContext).cancel(notificationId) + } + + private fun createChannelIfNeeded() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notificationService.getNotificationChannel( + channelId + ) == null + ) { + val importance = NotificationManager.IMPORTANCE_DEFAULT + val channel = NotificationChannel(channelId, channelName, importance) + channel.setSound(null, null) + channel.enableVibration(false) + channel.setShowBadge(false) + notificationService.createNotificationChannel(channel) + } + } + + private fun getNotification(message: String): Notification { + createChannelIfNeeded() + val builder = NotificationCompat.Builder(applicationContext, channelId) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentTitle(applicationContext.getString(R.string.location_background)) + .setContentText(message) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setSound(null) + .setVibrate(null) + .setBadgeIconType(NotificationCompat.BADGE_ICON_NONE) + val notification = builder.build() + + notification.flags = Notification.FLAG_NO_CLEAR or Notification.FLAG_ONGOING_EVENT + return notification + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt new file mode 100644 index 000000000..ca92bc4cd --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt @@ -0,0 +1,84 @@ +/* + Copyright (c) 2020. Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.permissions + +import android.os.Bundle +import android.view.View +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.widget.AppCompatButton +import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpec +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpecRow +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel +import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission +import com.splendo.kaluga.permissions.calendar.CalendarPermission +import com.splendo.kaluga.permissions.camera.CameraPermission +import com.splendo.kaluga.permissions.contacts.ContactsPermission +import com.splendo.kaluga.permissions.location.LocationPermission +import com.splendo.kaluga.permissions.microphone.MicrophonePermission +import com.splendo.kaluga.permissions.notifications.NotificationsPermission +import com.splendo.kaluga.permissions.storage.StoragePermission +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class PermissionsDemoActivity : KalugaViewModelActivity(R.layout.activity_permissions_demo) { + + override val viewModel: PermissionViewModel by viewModel { + val permissionNavSpec = PermissionNavigationBundleSpec() + intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle -> + val permission = when (bundle.get(PermissionNavigationBundleSpecRow)) { + PermissionView.Bluetooth -> BluetoothPermission + PermissionView.Calendar -> CalendarPermission(allowWrite = true) + PermissionView.Camera -> CameraPermission + PermissionView.Contacts -> ContactsPermission(allowWrite = true) + PermissionView.Location -> LocationPermission(background = true, precise = true) + PermissionView.Microphone -> MicrophonePermission + PermissionView.Notifications -> NotificationsPermission() + PermissionView.Storage -> StoragePermission(allowWrite = true) + } + parametersOf(permission) + } ?: parametersOf() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val permissionNavSpec = PermissionNavigationBundleSpec() + intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle -> + supportActionBar?.title = bundle.get(PermissionNavigationBundleSpecRow).title + } + + viewModel.permissionStateMessage.observe { + findViewById(R.id.permissions_message).text = it + } + + viewModel.showPermissionButton.observe { + findViewById(R.id.btn_permissions_bluetooth_request_permissions).visibility = if (it == true) View.VISIBLE else View.GONE + } + + findViewById(R.id.btn_permissions_bluetooth_request_permissions).setOnClickListener { viewModel.requestPermission() } + + viewModel.requestMessage.observeInitialized { message -> + if (message != null) Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt new file mode 100644 index 000000000..48fdd892b --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt @@ -0,0 +1,81 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.permissions + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.widget.AppCompatButton +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class PermissionsDemoListActivity : KalugaViewModelActivity(R.layout.activity_permissions_list) { + + override val viewModel: PermissionsListViewModel by viewModel { + parametersOf( + ActivityNavigator { + NavigationSpec.Activity() + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val adapter = PermissionsAdapter(viewModel).apply { + findViewById(R.id.permissions_list).adapter = this + } + viewModel.permissions.observeInitialized { adapter.permissions = it } + } +} + +class PermissionsAdapter(private val viewModel: PermissionsListViewModel) : RecyclerView.Adapter() { + + class PermissionsViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button) + + var permissions: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionsViewHolder { + val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton + return PermissionsViewHolder(button) + } + + override fun getItemCount(): Int = permissions.size + + override fun onBindViewHolder(holder: PermissionsViewHolder, position: Int) { + permissions.getOrNull(position)?.let { permission -> + holder.button.text = permission.title + holder.button.setOnClickListener { viewModel.onPermissionPressed(permission) } + } ?: run { + holder.button.text = null + holder.button.setOnClickListener(null) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt new file mode 100644 index 000000000..e47f80400 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt @@ -0,0 +1,90 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.platformspecific + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.example.bottomSheet.BottomSheetActivity +import com.splendo.kaluga.example.contacts.ContactsActivity +import com.splendo.kaluga.example.contacts.ui.Padding +import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformFeatureListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformSpecificFeaturesViewModel + +class PlatformSpecificActivity : KalugaViewModelComposeActivity() { + override val viewModel = PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper)) + + @Composable + override fun Layout(viewModel: PlatformSpecificFeaturesViewModel) { + PlatformSpecificFeaturesLayout(viewModel) + } +} + +private fun navigationMapper(action: PlatformFeatureListNavigationAction): NavigationSpec = + when (action) { + is PlatformFeatureListNavigationAction.ComposeNavigation -> NavigationSpec.Activity( + ContactsActivity::class.java) + is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity( + BottomSheetActivity::class.java) + } + +@Composable +private fun PlatformSpecificFeaturesLayout(viewModel: PlatformSpecificFeaturesViewModel) { + MdcTheme { + ViewModelComposable(viewModel) { + val features by feature.state() + LazyColumn { + items(features) { item -> + Button( + modifier = Modifier + .fillMaxWidth() + .padding(Padding.default), + onClick = { onFeaturePressed(item) }, + ) { + Text( + text = item.title.uppercase(), + style = MaterialTheme.typography.button, + ) + } + } + } + } + } +} + +@Composable +@Preview +private fun PlatformSpecificFeaturesLayoutPreview() { + PlatformSpecificFeaturesLayout( + PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper)) + ) +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt new file mode 100644 index 000000000..9a38060f6 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.resources + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityResourcesBinding +import com.splendo.kaluga.example.databinding.ViewListButtonBinding +import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel +import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration +import com.splendo.kaluga.resources.dpToPixel +import com.splendo.kaluga.resources.view.KalugaButton +import com.splendo.kaluga.resources.view.bindButton +import org.koin.androidx.viewmodel.ext.android.viewModel + +class ButtonActivity : KalugaViewModelActivity() { + + override val viewModel: ButtonViewModel by viewModel() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + + val adapter = ButtonAdapter().apply { + + binding.resourcesList.adapter = this + } + viewModel.buttons.observeInitialized { adapter.buttons = it } + binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) + } +} + +class ButtonAdapter : RecyclerView.Adapter() { + + class ButtonViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { + val button = binding.button + } + + var buttons: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ButtonViewHolder { + val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ButtonViewHolder(binding) + } + + override fun getItemCount(): Int = buttons.size + + override fun onBindViewHolder(holder: ButtonViewHolder, position: Int) { + buttons.getOrNull(position)?.let { button -> + holder.button.bindButton(button) + } ?: run { + holder.button.text = null + holder.button.background = null + holder.button.setOnClickListener(null) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt new file mode 100644 index 000000000..105ffca09 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.resources + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.Button +import android.widget.TextView +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityResourcesColorBinding +import com.splendo.kaluga.example.databinding.ViewResourceListBackgroundBinding +import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel +import com.splendo.kaluga.resources.DefaultColors +import com.splendo.kaluga.resources.stylable.BackgroundStyle +import com.splendo.kaluga.resources.view.KalugaButton +import com.splendo.kaluga.resources.view.KalugaLabel +import com.splendo.kaluga.resources.view.applyBackgroundStyle +import com.splendo.kaluga.resources.view.bindButton +import com.splendo.kaluga.resources.view.bindLabel +import org.koin.androidx.viewmodel.ext.android.viewModel + +class ColorActivity : KalugaViewModelActivity() { + + override val viewModel: ColorViewModel by viewModel() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityResourcesColorBinding.inflate(layoutInflater, null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + + binding.backdropEdit.setOnEditorActionListener { v, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + viewModel.submitBackdropText(v.text.toString()) + true + } else { + false + } + } + + binding.sourceEdit.setOnEditorActionListener { v, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + viewModel.submitSourceText(v.text.toString()) + true + } else { + false + } + } + + binding.backdropLighten.adapter = BackgroundAdapter() + binding.backdropDarken.adapter = BackgroundAdapter() + binding.blendedLighten.adapter = BackgroundAdapter() + binding.blendedDarken.adapter = BackgroundAdapter() + binding.sourceLighten.adapter = BackgroundAdapter() + binding.sourceDarken.adapter = BackgroundAdapter() + } +} + +class BackgroundAdapter : RecyclerView.Adapter() { + + class BackgroundViewHolder(val binding: ViewResourceListBackgroundBinding) : RecyclerView.ViewHolder(binding.root) { + val view = binding.root + } + + var backgrounds: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackgroundViewHolder { + val binding = ViewResourceListBackgroundBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return BackgroundViewHolder(binding) + } + + override fun getItemCount(): Int = backgrounds.size + + override fun onBindViewHolder(holder: BackgroundViewHolder, position: Int) { + backgrounds.getOrNull(position)?.let { background -> + holder.view.applyBackgroundStyle(background) + } ?: run { + holder.view.setBackgroundColor(DefaultColors.clear) + } + } +} + +object ResourcesBinding { + + @BindingAdapter("button") + @JvmStatic + fun bindButton(view: Button, button: KalugaButton?) { + button?.let { + view.bindButton(it) + } + } + + @BindingAdapter("label") + @JvmStatic + fun bindLabel(view: TextView, label: KalugaLabel?) { + label?.let { + view.bindLabel(it) + } + } + + @BindingAdapter("backgroundStyle") + @JvmStatic + fun bindBackgroundStyle(view: View, backgroundStyle: BackgroundStyle?) { + backgroundStyle?.let { + view.applyBackgroundStyle(it) + } + } + + @BindingAdapter("backgrounds") + @JvmStatic + fun bindBackgrounds(recyclerView: RecyclerView, backgrounds: List?) { + val adapter = recyclerView.adapter as? BackgroundAdapter ?: return + adapter.backgrounds = backgrounds ?: emptyList() + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt new file mode 100644 index 000000000..9addfb939 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.resources + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityResourcesBinding +import com.splendo.kaluga.example.databinding.ViewListTextViewBinding +import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel +import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration +import com.splendo.kaluga.resources.dpToPixel +import com.splendo.kaluga.resources.view.KalugaLabel +import com.splendo.kaluga.resources.view.bindLabel +import org.koin.androidx.viewmodel.ext.android.viewModel + +class LabelActivity : KalugaViewModelActivity() { + + override val viewModel: LabelViewModel by viewModel() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + + val adapter = LabelAdapter().apply { + + binding.resourcesList.adapter = this + } + binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) + viewModel.labels.observeInitialized { adapter.labels = it } + } +} + +class LabelAdapter : RecyclerView.Adapter() { + + class LabelViewHolder(val binding: ViewListTextViewBinding) : RecyclerView.ViewHolder(binding.root) { + val label = binding.label + } + + var labels: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LabelViewHolder { + val binding = ViewListTextViewBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return LabelViewHolder(binding) + } + + override fun getItemCount(): Int = labels.size + + override fun onBindViewHolder(holder: LabelViewHolder, position: Int) { + labels.getOrNull(position)?.let { label -> + holder.label.bindLabel(label) + } ?: run { + holder.label.text = null + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt new file mode 100644 index 000000000..a587071fb --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt @@ -0,0 +1,75 @@ +package com.splendo.kaluga.example.resources + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityResourcesBinding +import com.splendo.kaluga.example.databinding.ViewListButtonBinding +import com.splendo.kaluga.example.shared.viewmodel.resources.Resource +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class ResourcesActivity : KalugaViewModelActivity() { + override val viewModel: ResourcesListViewModel by viewModel { + parametersOf( + ActivityNavigator { action -> + when (action) { + is ResourcesListNavigationAction.Label -> NavigationSpec.Activity() + is ResourcesListNavigationAction.Color -> NavigationSpec.Activity() + is ResourcesListNavigationAction.Button -> NavigationSpec.Activity() + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + + val adapter = ResourcesAdapter(viewModel).apply { + + binding.resourcesList.adapter = this + } + viewModel.resources.observeInitialized { adapter.resources = it } + } +} + +class ResourcesAdapter(private val viewModel: ResourcesListViewModel) : + RecyclerView.Adapter() { + + class ResourceViewHolder(val binding: ViewListButtonBinding) : + RecyclerView.ViewHolder(binding.root) { + val button = binding.button + } + + var resources: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResourceViewHolder { + val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ResourceViewHolder(binding) + } + + override fun getItemCount(): Int = resources.size + + override fun onBindViewHolder(holder: ResourceViewHolder, position: Int) { + resources.getOrNull(position)?.let { resource -> + holder.button.text = resource.title + holder.button.setOnClickListener { viewModel.onResourceSelected(resource) } + } ?: run { + holder.button.text = null + holder.button.setOnClickListener(null) + } + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt new file mode 100644 index 000000000..293f385b3 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt @@ -0,0 +1,92 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.system + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Button +import androidx.appcompat.widget.AppCompatButton +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.shared.viewmodel.system.SystemFeatures +import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions +import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel +import com.splendo.kaluga.example.system.fragments.NetworkFragment +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class SystemActivity : KalugaViewModelActivity(R.layout.activity_system) { + override val viewModel: SystemViewModel by viewModel { + parametersOf( + ActivityNavigator> { action -> + when (action) { + SystemNavigationActions.Network -> NavigationSpec.Fragment( + R.id.system_features_fragment, + createFragment = { NetworkFragment() } + ) + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val adapter = SystemFeatureAdapter(viewModel).apply { + findViewById(R.id.system_features_list).adapter = this + } + + viewModel.modules.observeInitialized { + adapter.modules = it + } + } +} + +class SystemFeatureAdapter( + private val viewModel: SystemViewModel +) : RecyclerView.Adapter() { + + inner class SystemFeatureViewHolder(val button: Button) : RecyclerView.ViewHolder(button) + + var modules: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SystemFeatureViewHolder { + val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton + return SystemFeatureViewHolder(button) + } + + override fun onBindViewHolder(holder: SystemFeatureViewHolder, position: Int) { + modules.getOrNull(position)?.let { feature -> + holder.button.text = feature.name + holder.button.setOnClickListener { viewModel.onButtonTapped(feature) } + } ?: run { + holder.button.text = null + holder.button.setOnClickListener(null) + } + } + + override fun getItemCount(): Int = modules.size +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt new file mode 100644 index 000000000..3535caea9 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt @@ -0,0 +1,44 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.system.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.FragmentNetworkBinding +import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +class NetworkFragment : KalugaViewModelFragment(R.layout.fragment_network) { + + override val viewModel: NetworkViewModel by viewModel() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding = FragmentNetworkBinding.inflate(inflater) + binding.lifecycleOwner = this + binding.viewModel = viewModel + return binding.root + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt new file mode 100644 index 000000000..b7398e774 --- /dev/null +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.view + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class VerticalSpaceItemDecoration(private val verticalSpaceHeight: Int) : RecyclerView.ItemDecoration() { + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + outRect.bottom = verticalSpaceHeight + } +} + +class HorizontalSpaceItemDecoration(private val horizontalSpaceWidth: Int) : RecyclerView.ItemDecoration() { + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + outRect.right = horizontalSpaceWidth + } +} diff --git a/newexample/android/src/main/res/drawable/ic_account.xml b/newexample/android/src/main/res/drawable/ic_account.xml new file mode 100644 index 000000000..0e510741e --- /dev/null +++ b/newexample/android/src/main/res/drawable/ic_account.xml @@ -0,0 +1,5 @@ + + + diff --git a/newexample/android/src/main/res/drawable/ic_launcher_foreground.xml b/newexample/android/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..fffaf19df --- /dev/null +++ b/newexample/android/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + diff --git a/newexample/android/src/main/res/drawable/ic_refresh_circle.xml b/newexample/android/src/main/res/drawable/ic_refresh_circle.xml new file mode 100644 index 000000000..43bf87fcd --- /dev/null +++ b/newexample/android/src/main/res/drawable/ic_refresh_circle.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/drawable/ic_stop_circle.xml b/newexample/android/src/main/res/drawable/ic_stop_circle.xml new file mode 100644 index 000000000..a0770a6eb --- /dev/null +++ b/newexample/android/src/main/res/drawable/ic_stop_circle.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_alerts.xml b/newexample/android/src/main/res/layout/activity_alerts.xml new file mode 100644 index 000000000..74ea5dea3 --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_alerts.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + diff --git a/newexample/android/src/main/res/layout/activity_architecture_details.xml b/newexample/android/src/main/res/layout/activity_architecture_details.xml new file mode 100644 index 000000000..926bd42b8 --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_architecture_details.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_architecture_input.xml b/newexample/android/src/main/res/layout/activity_architecture_input.xml new file mode 100644 index 000000000..95c28e0ac --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_architecture_input.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_beacons.xml b/newexample/android/src/main/res/layout/activity_beacons.xml new file mode 100644 index 000000000..30abccc0a --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_beacons.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/newexample/android/src/main/res/layout/activity_bluetooth.xml b/newexample/android/src/main/res/layout/activity_bluetooth.xml new file mode 100644 index 000000000..59d229ef1 --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_bluetooth.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_bluetooth_more.xml b/newexample/android/src/main/res/layout/activity_bluetooth_more.xml new file mode 100644 index 000000000..9a917b91e --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_bluetooth_more.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_date_time_picker.xml b/newexample/android/src/main/res/layout/activity_date_time_picker.xml new file mode 100644 index 000000000..f92a40694 --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_date_time_picker.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_example.xml b/newexample/android/src/main/res/layout/activity_example.xml new file mode 100644 index 000000000..fecd0859d --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_example.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_keyboard_manager.xml b/newexample/android/src/main/res/layout/activity_keyboard_manager.xml new file mode 100644 index 000000000..364a8b6c4 --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_keyboard_manager.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/newexample/android/src/main/res/layout/activity_link.xml b/newexample/android/src/main/res/layout/activity_link.xml new file mode 100644 index 000000000..99c88fc0b --- /dev/null +++ b/newexample/android/src/main/res/layout/activity_link.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/newexample/ios/Demo/Beacons/BeaconsViewCell.swift b/newexample/ios/Demo/Beacons/BeaconsViewCell.swift new file mode 100644 index 000000000..829f8565d --- /dev/null +++ b/newexample/ios/Demo/Beacons/BeaconsViewCell.swift @@ -0,0 +1,52 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// 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. +// + +import UIKit +import KotlinNativeFramework + +class BeaconsViewCell: UICollectionViewCell { + + static var identifier: String { String(describing: Self.self) } + + @IBOutlet private var namespaceLabel: UILabel! + @IBOutlet private var instanceLabel: UILabel! + @IBOutlet private var txPowerLabel: UILabel! + + private let disposeBag = DisposeBag() + private var viewModel: BeaconsListBeaconViewModel? + + func configure(with viewModel: BeaconsListBeaconViewModel) { + self.viewModel = viewModel + } + + func startMonitoring() { + viewModel?.namespace_.observe { [weak self] namespace in + self?.namespaceLabel.text = namespace as String? + }.addTo(disposeBag: disposeBag) + + viewModel?.instance.observe { [weak self] instance in + self?.instanceLabel.text = instance as String? + }.addTo(disposeBag: disposeBag) + + viewModel?.txPower.observe { [weak self] txPower in + self?.txPowerLabel.text = txPower as String? + }.addTo(disposeBag: disposeBag) + } + + func stopMonitoring() { + disposeBag.dispose() + } +} diff --git a/newexample/ios/Demo/Beacons/BeaconsViewController.swift b/newexample/ios/Demo/Beacons/BeaconsViewController.swift new file mode 100644 index 000000000..678e5786a --- /dev/null +++ b/newexample/ios/Demo/Beacons/BeaconsViewController.swift @@ -0,0 +1,115 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// 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. +// + +import UIKit +import KotlinNativeFramework + +class BeaconsViewController: UICollectionViewController { + + private var beacons = [BeaconsListBeaconViewModel]() { + didSet { + collectionView.reloadData() + } + } + + private var lifecycleManager: LifecycleManager! + + private lazy var flowLayout: UICollectionViewFlowLayout = { + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() + flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) + flowLayout.minimumLineSpacing = 4 + return flowLayout + }() + + private lazy var viewModel = KNArchitectureFramework() + .createBeaconsListViewModel( + parent: self, + service: KNBeaconsFramework().service + ) + + deinit { + lifecycleManager.unbind() + } + + override func awakeFromNib() { + super.awakeFromNib() + + collectionView.collectionViewLayout = flowLayout + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + + return [ + viewModel.isScanning.observe { isScanning in + self?.updateNavigationItem(isScanning: isScanning as? Bool ?? false) + }, + viewModel.beacons.observe { devices in + self?.beacons = devices as? [BeaconsListBeaconViewModel] ?? [] + } + ] + } + } + + override func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + beacons.count + } + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let beaconCell = collectionView.dequeueReusableCell(withReuseIdentifier: BeaconsViewCell.identifier, for: indexPath) as! BeaconsViewCell + beaconCell.configure(with: beacons[indexPath.row]) + return beaconCell + } + + override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let cell = cell as? BeaconsViewCell else { + return + } + + cell.startMonitoring() + } + + override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let cell = cell as? BeaconsViewCell else { + return + } + + cell.stopMonitoring() + } + + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + collectionView.deselectItem(at: indexPath, animated: true) + } + + private func updateNavigationItem(isScanning: Bool) { + let title = isScanning ? "beacons_stop_monitoring" : "beacons_start_monitoring" + let item = UIBarButtonItem( + title: NSLocalizedString(title, comment: ""), + style: .plain, + target: viewModel, + action: #selector(viewModel.onScanPressed) + ) + navigationItem.setRightBarButton(item, animated: true) + } +} diff --git a/newexample/ios/Demo/Bluetooth/BluetoothCharacteristicCell.xib b/newexample/ios/Demo/Bluetooth/BluetoothCharacteristicCell.xib new file mode 100644 index 000000000..4708c258e --- /dev/null +++ b/newexample/ios/Demo/Bluetooth/BluetoothCharacteristicCell.xib @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/newexample/ios/Demo/Bluetooth/BluetoothDescriptorCell.xib b/newexample/ios/Demo/Bluetooth/BluetoothDescriptorCell.xib new file mode 100644 index 000000000..13eae4024 --- /dev/null +++ b/newexample/ios/Demo/Bluetooth/BluetoothDescriptorCell.xib @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift b/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift new file mode 100644 index 000000000..e6b178457 --- /dev/null +++ b/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift @@ -0,0 +1,364 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { + + struct Const { + static let storyboard = UIStoryboard(name: "Main", bundle: nil) + static let storyboardId = "BluetoothDeviceDetails" + } + + static func create(deviceUuid: UUID, bluetooth: Bluetooth) -> BluetoothDeviceDetailsViewController { + let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! BluetoothDeviceDetailsViewController + if #available(iOS 13.0, *) { + vc.isModalInPresentation = true + } + vc.viewModel = KNArchitectureFramework().createBluetoothDeviceDetailsViewModel(identifier: deviceUuid, bluetooth: bluetooth) + return vc + } + + var viewModel: BluetoothDeviceDetailViewModel! + private var lifecycleManager: LifecycleManager! + + @IBOutlet var deviceName: UILabel! + @IBOutlet var deviceIdentifier: UILabel! + @IBOutlet var rssi: UILabel! + @IBOutlet var distance: UILabel! + @IBOutlet var connectionStatus: UILabel! + @IBOutlet var servicesHeader: UILabel! + @IBOutlet var servicesList: UICollectionView! + + private var services: [BluetoothServiceViewModel] = [] + + private var isInvalidating: Bool = false + + deinit { + lifecycleManager.unbind() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + servicesHeader.text = NSLocalizedString("bluetooth_services_header", comment: "") + deviceIdentifier.text = viewModel.identifierString + + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() + flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) + flowLayout.minimumLineSpacing = 4 + servicesList.collectionViewLayout = flowLayout + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + + guard let viewModel = self?.viewModel else { return []} + + return [ + viewModel.name.observe { name in + self?.deviceName.text = name as String? + } + , + viewModel.rssi.observe { rssiValue in + self?.rssi.text = rssiValue as String? + } + , + viewModel.distance.observe { distanceValue in + self?.distance.text = distanceValue as String? + } + , + viewModel.state.observe { state in + self?.connectionStatus.text = state as String? + } + , + viewModel.services.observe { servicesList in + self?.services = servicesList as? [BluetoothServiceViewModel] ?? [] + self?.servicesList.reloadData() + self?.servicesList.layoutIfNeeded() + } + ] + }) + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return services.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let serviceCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothServiceView.Companion.identifier, for: indexPath) as! BluetoothServiceView + serviceCell.parent = self + serviceCell.service = services[indexPath.row] + return serviceCell + } + + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if (!isInvalidating) { + (cell as? BluetoothServiceView)?.startMonitoring() + } + } + + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if (!isInvalidating) { + (cell as? BluetoothServiceView)?.stopMonitoring() + } + } + + fileprivate func updateListSize() { + isInvalidating = true + servicesList.collectionViewLayout.invalidateLayout() + servicesList.layoutIfNeeded() + isInvalidating = false + } + +} + +class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource { + + fileprivate struct Companion { + static let identifier = "BluetoothServiceView" + } + + fileprivate weak var parent: BluetoothDeviceDetailsViewController? + fileprivate var service: BluetoothServiceViewModel? + private let disposeBag = DisposeBag() + + private var isInvalidating: Bool = false + + @IBOutlet var serviceHeader: UILabel! + @IBOutlet var serviceIdentifier: UILabel! + @IBOutlet var characteristicsHeader: UILabel! + @IBOutlet var characteristicsList: UICollectionView! + @IBOutlet var characteristicsListHeight: NSLayoutConstraint! + + private var characteristics: [BluetoothCharacteristicViewModel] = [] + + override func awakeFromNib() { + super.awakeFromNib() + + serviceHeader.text = NSLocalizedString("bluetooth_service", comment: "") + characteristicsHeader.text = NSLocalizedString("bluetooth_characteristics", comment: "") + + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() + flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) + flowLayout.minimumLineSpacing = 4 + characteristicsList.collectionViewLayout = flowLayout + characteristicsList.dataSource = self + characteristicsList.delegate = self + characteristicsList.register(UINib(nibName: "BluetoothCharacteristicCell", bundle: nil), forCellWithReuseIdentifier: BluetoothCharacteristicView.Companion.identifier) + } + + fileprivate func startMonitoring() { + disposeBag.dispose() + guard let service = self.service else { + return + } + service.didResume() + + serviceIdentifier.text = service.uuid + service.characteristics.observe { [weak self] characteristics in + self?.characteristics = characteristics as? [BluetoothCharacteristicViewModel] ?? [] + self?.characteristicsList.reloadData() + self?.updateListSize(isInvalidating: false) + }.addTo(disposeBag: disposeBag) + } + + fileprivate func stopMonitoring() { + service?.didPause() + disposeBag.dispose() + } + + fileprivate func updateListSize(isInvalidating: Bool) { + self.isInvalidating = isInvalidating + characteristicsList.collectionViewLayout.invalidateLayout() + characteristicsList.layoutIfNeeded() + let height = characteristicsList.contentSize.height + characteristicsListHeight.constant = height + parent?.updateListSize() + self.isInvalidating = false + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return characteristics.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let characteristicCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCharacteristicView.Companion.identifier, for: indexPath) as! BluetoothCharacteristicView + characteristicCell.parent = self + characteristicCell.characteristic = characteristics[indexPath.row] + return characteristicCell + } + + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if (!isInvalidating) { + (cell as? BluetoothCharacteristicView)?.startMonitoring() + } + } + + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if (!isInvalidating) { + (cell as? BluetoothCharacteristicView)?.stopMonitoring() + } + } + + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) + layoutIfNeeded() + layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + return layoutAttributes + } + +} + +class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource { + + fileprivate struct Companion { + static let identifier = "BluetoothCharacteristicView" + } + + fileprivate weak var parent: BluetoothServiceView? + fileprivate var characteristic: BluetoothCharacteristicViewModel? + private let disposeBag = DisposeBag() + + private var isInvalidating: Bool = false + + @IBOutlet var characteristicIdentifier: UILabel! + @IBOutlet var characteristicValue: UILabel! + @IBOutlet var descriptorsHeader: UILabel! + @IBOutlet var descriptorsList: UICollectionView! + + @IBOutlet var descriptorsListHeight: NSLayoutConstraint! + + private var descriptors: [BluetoothDescriptorViewModel] = [] + + override func awakeFromNib() { + super.awakeFromNib() + + descriptorsHeader.text = NSLocalizedString("bluetooth_descriptors", comment: "") + + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() + flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) + flowLayout.minimumLineSpacing = 4 + descriptorsList.collectionViewLayout = flowLayout + descriptorsList.register(UINib(nibName: "BluetoothDescriptorCell", bundle: nil), forCellWithReuseIdentifier: BluetoothDescriptorView.Companion.identifier) + } + + fileprivate func startMonitoring() { + disposeBag.dispose() + guard let characteristic = self.characteristic else { + return + } + characteristic.didResume() + + characteristicIdentifier.text = characteristic.uuid + characteristic.descriptors.observe { [weak self] descriptors in + self?.descriptors = descriptors as? [BluetoothDescriptorViewModel] ?? [] + self?.descriptorsList.reloadData() + self?.updateListSize(isInvalidating: false) + }.addTo(disposeBag: disposeBag) + + characteristic.value.observe { [weak self] value in + self?.characteristicValue.text = value as String? + }.addTo(disposeBag: disposeBag) + } + + fileprivate func stopMonitoring() { + disposeBag.dispose() + characteristic?.didPause() + } + + private func updateListSize(isInvalidating: Bool) { + self.isInvalidating = isInvalidating + descriptorsList.collectionViewLayout.invalidateLayout() + descriptorsList.layoutIfNeeded() + let height = descriptorsList.contentSize.height + descriptorsListHeight.constant = height + parent?.updateListSize(isInvalidating: true) + self.isInvalidating = false + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return descriptors.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let descriptorCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothDescriptorView.Companion.identifier, for: indexPath) as! BluetoothDescriptorView + descriptorCell.descriptor = descriptors[indexPath.row] + return descriptorCell + } + + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if (!isInvalidating) { + (cell as? BluetoothDescriptorView)?.startMonitoring() + } + } + + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if (!isInvalidating) { + (cell as? BluetoothDescriptorView)?.stopMonitoring() + } + } + + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) + layoutIfNeeded() + layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + return layoutAttributes + } + +} + +class BluetoothDescriptorView : UICollectionViewCell { + + fileprivate struct Companion { + static let identifier = "BluetoothDescriptorView" + } + + fileprivate var descriptor: BluetoothDescriptorViewModel? + private let disposeBag = DisposeBag() + + @IBOutlet var descriptorIdentifier: UILabel! + @IBOutlet var descriptorValue: UILabel! + + fileprivate func startMonitoring() { + disposeBag.dispose() + guard let descriptor = self.descriptor else { + return + } + descriptor.didResume() + + descriptorIdentifier.text = descriptor.uuid + + descriptor.value.observe { [weak self] value in + self?.descriptorValue.text = value as String? + }.addTo(disposeBag: disposeBag) + } + + fileprivate func stopMonitoring() { + disposeBag.dispose() + descriptor?.didResume() + } + + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) + layoutIfNeeded() + layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + return layoutAttributes + } +} diff --git a/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift b/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift new file mode 100644 index 000000000..1e60a04b4 --- /dev/null +++ b/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift @@ -0,0 +1,230 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class BluetoothViewController : UICollectionViewController { + + lazy var viewModel = KNArchitectureFramework().createBluetoothListViewModel(parent: self, bluetooth: KNBluetoothFramework().bluetooth) { uuid, bluetooth in + return BluetoothDeviceDetailsViewController.create(deviceUuid: uuid, bluetooth: bluetooth) + } + + private var devices: [BluetoothListDeviceViewModel] = [] + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func awakeFromNib() { + super.awakeFromNib() + + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() + flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) + flowLayout.minimumLineSpacing = 4 + collectionView.collectionViewLayout = flowLayout + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + + return [ + viewModel.isScanning.observe { isScanning in + self?.updateNavigationItem(isScanning: isScanning as? Bool ?? false) + }, + viewModel.devices.observe { devices in + self?.devices = devices as? [BluetoothListDeviceViewModel] ?? [] + self?.collectionView?.reloadData() + self?.collectionView.layoutIfNeeded() + }, + viewModel.title.observe { title in + self?.updateTitle(title: title as String?) + } + ] + }) + } + + private func updateNavigationItem(isScanning: Bool) { + if (isScanning) { + self.navigationItem.setRightBarButton(UIBarButtonItem(title: NSLocalizedString("bluetooth_stop_scanning", comment: ""), style: .plain, target: self, action: #selector(self.toggleScanning)), animated: true) + } else { + self.navigationItem.setRightBarButton(UIBarButtonItem(title: NSLocalizedString("bluetooth_start_scanning", comment: ""), style: .plain, target: self, action: #selector(self.toggleScanning)), animated: true) + } + } + + private func updateTitle(title: String?) { + self.title = title + } + + @objc private func toggleScanning() { + viewModel.onScanPressed() + } + + override func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + devices.count + } + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let bluetoothCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCell.Companion.identifier, for: indexPath) as! BluetoothCell + bluetoothCell.device = devices[indexPath.row] + return bluetoothCell + } + + override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let btCell = cell as? BluetoothCell else { + return + } + + btCell.startMonitoring() + } + + override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let btCell = cell as? BluetoothCell else { + return + } + + btCell.stopMonitoring() + } + + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + devices[indexPath.row].toggleFoldOut() + let context = UICollectionViewFlowLayoutInvalidationContext() + context.invalidateItems(at: [indexPath]) + collectionView.collectionViewLayout.invalidateLayout() + } +} + +class BluetoothCell: UICollectionViewCell { + + fileprivate struct Companion { + static let identifier = "BluetoothCell" + } + + private let disposeBag = DisposeBag() + + @IBOutlet var deviceName: UILabel! + @IBOutlet var deviceIdentifier: UILabel! + @IBOutlet var rssi: UILabel! + @IBOutlet var txPower: UILabel! + @IBOutlet var buttonContainer: UIStackView! + @IBOutlet var connectButton: UIButton! + @IBOutlet var disconnectButton: UIButton! + + @IBOutlet var connectionStatus: UILabel! + @IBOutlet var foldOutMenu: UIView! + @IBOutlet var serviceId: UILabel! + @IBOutlet var serviceData: UILabel! + @IBOutlet var manufacturerId: UILabel! + @IBOutlet var manufacturerData: UILabel! + @IBOutlet var moreButtonContainer: UIView! + @IBOutlet var moreButton: UIButton! + + fileprivate var device: BluetoothListDeviceViewModel? = nil + + @IBAction func onConnectPressed() { + device?.onConnectPressed() + } + + @IBAction func onDisonnectPressed() { + device?.onDisconnectPressed() + } + + @IBAction func onMorePressed() { + device?.onMorePressed() + } + + func startMonitoring() { + disposeBag.dispose() + guard let device = device else { + return + } + device.didResume() + + deviceIdentifier.text = device.identifierString + + device.name.observe { [weak self] name in + self?.deviceName.text = name as? String + }.addTo(disposeBag: disposeBag) + + device.rssi.observe { [weak self] rssiValue in + self?.rssi.text = rssiValue as? String + }.addTo(disposeBag: disposeBag) + + device.txPower.observe { [weak self] txPowerValue in + self?.txPower.text = txPowerValue as? String + }.addTo(disposeBag: disposeBag) + + device.status.observe { [weak self] status in + self?.connectionStatus.text = status as? String + }.addTo(disposeBag: disposeBag) + + device.isConnectButtonVisible.observe { [weak self] isVisible in + self?.buttonContainer.alpha = (isVisible as? Bool ?? false) ? 1.0 : 0.0 + }.addTo(disposeBag: disposeBag) + + device.connectButtonState.observe { [weak self] connectButtonState in + let state: BluetoothListDeviceViewModel.ConnectButtonState = connectButtonState as? BluetoothListDeviceViewModel.ConnectButtonState ?? BluetoothListDeviceViewModel.ConnectButtonState.disconnect + switch state { + case BluetoothListDeviceViewModel.ConnectButtonState.connect: + self?.connectButton.isHidden = false + self?.disconnectButton.isHidden = true + case BluetoothListDeviceViewModel.ConnectButtonState.disconnect: + self?.connectButton.isHidden = true + self?.disconnectButton.isHidden = false + default: () + } + }.addTo(disposeBag: disposeBag) + + device.isFoldedOut.observe { [weak self] isFoldedOut in + self?.foldOutMenu.isHidden = !((isFoldedOut as? Bool) ?? false) + } + + device.isMoreButtonVisible.observe { [weak self] isMoreVisible in + self?.moreButtonContainer.isHidden = !(isMoreVisible as? Bool ?? false) + } + + device.serviceUUIDs.observe { [weak self] serviceUUIDS in + self?.serviceId.text = serviceUUIDS as? String + } + + device.serviceData.observe { [weak self] serviceData in + self?.serviceData.text = serviceData as? String + } + + device.manufacturerId.observe { [weak self] manufacturerId in + self?.manufacturerId.text = manufacturerId as? String + } + + device.manufacturerData.observe { [weak self] manufacturerData in + self?.manufacturerData.text = manufacturerData as? String + } + } + + func stopMonitoring() { + disposeBag.dispose() + device?.didPause() + } +} diff --git a/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift b/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift new file mode 100644 index 000000000..d32da17ed --- /dev/null +++ b/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift @@ -0,0 +1,58 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class DateTimePickerViewController : UIViewController { + + @IBOutlet private var timeLabel: UILabel! + + lazy var viewModel = DateTimePickerViewModel(dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder(viewController: self)) + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + guard let viewModel = self?.viewModel else { + return [] + } + return [ + viewModel.dateLabel.observe(onNext: { (time) in + if let timeString = (time as? String) { + self?.timeLabel.text = String(timeString) + } + }) + ] + }) + } + + @IBAction + func selectDatePressed() { + viewModel.onSelectDatePressed() + } + + @IBAction + func selectTimePressed() { + viewModel.onSelectTimePressed() + } +} diff --git a/newexample/ios/Demo/Demo.entitlements b/newexample/ios/Demo/Demo.entitlements new file mode 100644 index 000000000..97405eaca --- /dev/null +++ b/newexample/ios/Demo/Demo.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + applinks:kaluga-links.web.app + + + diff --git a/newexample/ios/Demo/ExampleViewController.swift b/newexample/ios/Demo/ExampleViewController.swift new file mode 100644 index 000000000..6b0be8f80 --- /dev/null +++ b/newexample/ios/Demo/ExampleViewController.swift @@ -0,0 +1,57 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class ExampleViewController : UIViewController { + + struct Const { + static let storyboard = UIStoryboard(name: "Main", bundle: nil) + static let featuresList = "FeaturesList" + static let infoView = "InfoViewController" + } + + @IBOutlet weak var containerView: UIView! + @IBOutlet weak var bottomView: UIStackView! + + lazy var featuresListController = Const.storyboard.instantiateViewController(withIdentifier: Const.featuresList) as! FeaturesListViewController + lazy var infoViewController = Const.storyboard.instantiateViewController(withIdentifier: Const.infoView) as! InfoViewController + + lazy var viewModel: ExampleViewModel = KNArchitectureFramework().createExampleViewModel(parent: self, + containerView: containerView, + featuresList: { self.featuresListController }, + info: { self.infoViewController }) + + var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel, let bottomView = self?.bottomView else { return [] } + return viewModel.observeTabs(stackView: bottomView) { (button: UIButton, action: @escaping () -> KotlinUnit) in + button.addAction { let _ = action() } + } + } + } + +} diff --git a/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift b/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift new file mode 100644 index 000000000..1f6f19df9 --- /dev/null +++ b/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift @@ -0,0 +1,75 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class FeaturesListViewController : UITableViewController { + + private lazy var viewModel: FeatureListViewModel = KNArchitectureFramework().createFeatureListViewModel(parent: self) + private var lifecycleManager: LifecycleManager! + + private var features = [String]() + private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + return [viewModel.observeFeatures() { (features: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in + self?.features = features + self?.onSelected = onSelected + self?.tableView.reloadData() + }] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return features.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: FeaturesListCell.Const.identifier, for: indexPath) as! FeaturesListCell + cell.label.text = features[indexPath.row] + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + tableView.deselectRow(at: indexPath, animated: true) + } + +} + +class FeaturesListCell : UITableViewCell { + + struct Const { + static let identifier = "FeaturesListCell" + } + + @IBOutlet weak var label: UILabel! + +} diff --git a/newexample/ios/Demo/Helpers/UIControl+Closure.swift b/newexample/ios/Demo/Helpers/UIControl+Closure.swift new file mode 100644 index 000000000..de3718ea9 --- /dev/null +++ b/newexample/ios/Demo/Helpers/UIControl+Closure.swift @@ -0,0 +1,38 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit + +@objc class UIControlClosure: NSObject { + let closure: ()->() + + init (_ closure: @escaping ()->()) { + self.closure = closure + } + + @objc func invoke () { + closure() + } +} + +extension UIControl { + func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping ()->()) { + let sleeve = UIControlClosure(closure) + addTarget(sleeve, action: #selector(UIControlClosure.invoke), for: controlEvents) + objc_setAssociatedObject(self, "[\(arc4random())]", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + } +} diff --git a/newexample/ios/Demo/Info.plist b/newexample/ios/Demo/Info.plist new file mode 100644 index 000000000..51a9d709a --- /dev/null +++ b/newexample/ios/Demo/Info.plist @@ -0,0 +1,82 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.splendo.kaluga.example + CFBundleURLSchemes + + kalugaexample + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSBluetoothAlwaysUsageDescription + Demonstrate usage of Kaluga library + NSBluetoothPeripheralUsageDescription + Demonstrate usage of Kaluga library + NSCalendarsUsageDescription + Demonstrate usage of Kaluga library + NSBluetoothAlwaysUsageDescription + Demonstrate usage of Kaluga library + NSBluetoothPeripheralUsageDescription + Demonstrate usage of Kaluga library + NSCameraUsageDescription + Demonstrate usage of Kaluga library + NSContactsUsageDescription + Demonstrate usage of Kaluga library + NSLocationAlwaysAndWhenInUseUsageDescription + Demonstrate usage of Kaluga library + NSLocationAlwaysUsageDescription + Demonstrate usage of Kaluga library + NSLocationWhenInUseUsageDescription + Demonstrate usage of Kaluga library + NSMicrophoneUsageDescription + Demonstrate usage of Kaluga library + NSPhotoLibraryUsageDescription + Demonstrate usage of Kaluga library + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/newexample/ios/Demo/Info/InfoViewController.swift b/newexample/ios/Demo/Info/InfoViewController.swift new file mode 100644 index 000000000..e0f6825a8 --- /dev/null +++ b/newexample/ios/Demo/Info/InfoViewController.swift @@ -0,0 +1,79 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class InfoViewController : UITableViewController { + + private lazy var viewModel: InfoViewModel = KNArchitectureFramework().createInfoViewModel(parent: self) + + private var buttons = [String]() + private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + guard let viewModel = self?.viewModel else { + return [] + } + + return [viewModel.observeButtons() { (buttons: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in + self?.buttons = buttons + self?.onSelected = onSelected + self?.tableView.reloadData() + }] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return buttons.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: InfoButtonCell.Const.identifier, for: indexPath) as! InfoButtonCell + cell.label.text = buttons[indexPath.row] + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + tableView.deselectRow(at: indexPath, animated: true) + } + +} + +class InfoButtonCell : UITableViewCell { + + struct Const { + static let identifier = "InfoButtonCell" + } + + @IBOutlet weak var label: UILabel! + +} diff --git a/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift b/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift new file mode 100644 index 000000000..3b973ab9b --- /dev/null +++ b/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift @@ -0,0 +1,50 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class KeyboardManagerViewController : UIViewController { + + @IBOutlet private var editField: UITextField! + + private lazy var editFieldFocusHandler = { + return UIKitFocusHandler(view: self.editField) + }() + lazy var viewModel = KNArchitectureFramework().createKeyboardViewModel(focusHandler: self.editFieldFocusHandler) + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { return [] }) + } + + @IBAction + func showButtonPressed() { + viewModel.onShowPressed() + } + + @IBAction + func hideButtonPressed() { + viewModel.onHidePressed() + } +} diff --git a/newexample/ios/Demo/Links/LinksViewController.swift b/newexample/ios/Demo/Links/LinksViewController.swift new file mode 100644 index 000000000..9f14309e3 --- /dev/null +++ b/newexample/ios/Demo/Links/LinksViewController.swift @@ -0,0 +1,59 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import Foundation +import UIKit +import KotlinNativeFramework + +class LinksViewController : UIViewController { + + @IBOutlet weak var browserButton: UIButton! + @IBOutlet weak var instructionsText: UILabel! + + private let knArchitectureFramework = KNArchitectureFramework() + private lazy var viewModel: LinksViewModel = { + return knArchitectureFramework.createLinksViewModel(parent: self, animated: true, completion: nil) + }() + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { + [ + self.viewModel.browserButtonText.observe { buttonText in + self.browserButton.setTitle(buttonText as String?, for: .normal) + }, + self.viewModel.linksInstructions.observe { text in + self.instructionsText.text = text as String? + } + ] + } + } + + @IBAction func onBrowserButtonTapped(_ sender: UIButton) { + self.viewModel.openWebPage() + } + + func handleIncomingLink(url: String) { + self.viewModel.handleIncomingLink(url: url) + } +} diff --git a/newexample/ios/Demo/LoadingIndicator/ActivityViewController.swift b/newexample/ios/Demo/LoadingIndicator/ActivityViewController.swift new file mode 100644 index 000000000..045eea92b --- /dev/null +++ b/newexample/ios/Demo/LoadingIndicator/ActivityViewController.swift @@ -0,0 +1,20 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit + +class ActivityViewController: UIViewController { } diff --git a/newexample/ios/Demo/LoadingIndicator/ActivityViewController.xib b/newexample/ios/Demo/LoadingIndicator/ActivityViewController.xib new file mode 100644 index 000000000..0b785d2bd --- /dev/null +++ b/newexample/ios/Demo/LoadingIndicator/ActivityViewController.xib @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift b/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift new file mode 100644 index 000000000..8c2412492 --- /dev/null +++ b/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift @@ -0,0 +1,44 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class LoadingViewController: UITableViewController { + + private lazy var viewModel = HudViewModel(builder: HUD.Builder(viewController: self)) + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { return [] } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + switch (indexPath.row) { + case 0: viewModel.onShowSystemPressed() + case 1: viewModel.onShowCustomPressed() + default: () + } + } +} diff --git a/newexample/ios/Demo/Location/LocationViewController.swift b/newexample/ios/Demo/Location/LocationViewController.swift new file mode 100644 index 000000000..a2632b8af --- /dev/null +++ b/newexample/ios/Demo/Location/LocationViewController.swift @@ -0,0 +1,52 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + +*/ + +import UIKit +import CoreLocation +import KotlinNativeFramework + +class LocationViewController: UIViewController { + + struct Const { + static let permission = LocationPermission(background: false, precise: true) + } + + //MARK: Properties + + @IBOutlet weak var label: UILabel! + + lazy var viewModel = KNArchitectureFramework().createLocationViewModel(permission: Const.permission, repoBuilder: KNLocationFramework().getPermissionRepoBuilder()) + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = viewModel.addLifecycleManager(parent: self, onLifecycle: { [weak self] in + guard let viewModel = self?.viewModel else { + return [] + } + return [viewModel.location.observe(onNext: { (location) in + self?.label.text = location as? String ?? "" + })] + }) + } +} diff --git a/newexample/ios/Demo/Permissions/PermissionListViewController.swift b/newexample/ios/Demo/Permissions/PermissionListViewController.swift new file mode 100644 index 000000000..08b634940 --- /dev/null +++ b/newexample/ios/Demo/Permissions/PermissionListViewController.swift @@ -0,0 +1,77 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KotlinNativeFramework + +class PermissionListViewController : UITableViewController { + + private lazy var viewModel: PermissionsListViewModel = KNArchitectureFramework().createPermissionListViewModel(parent: self) { (permission) -> UIViewController in + return PermissionViewController.create(permission: permission) + } + private var lifecycleManager: LifecycleManager! + + private var permissions = [PermissionView]() + private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + return [viewModel.observePermissions { (permissionViews: [PermissionView], onSelected: @escaping (KotlinInt) -> KotlinUnit) in + self?.permissions = permissionViews + self?.onSelected = onSelected + self?.tableView.reloadData() + }] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return permissions.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: PermissionsListCell.Const.identifier, for: indexPath) as! PermissionsListCell + cell.label.text = permissions[indexPath.row].title + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + tableView.deselectRow(at: indexPath, animated: true) + } + +} + +class PermissionsListCell : UITableViewCell { + + struct Const { + static let identifier = "PermissionsListCell" + } + + @IBOutlet weak var label: UILabel! + +} diff --git a/newexample/ios/Demo/Permissions/PermissionViewController.swift b/newexample/ios/Demo/Permissions/PermissionViewController.swift new file mode 100644 index 000000000..29ce5c847 --- /dev/null +++ b/newexample/ios/Demo/Permissions/PermissionViewController.swift @@ -0,0 +1,87 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import Foundation +import KotlinNativeFramework + +class PermissionViewController: UIViewController { + + private struct Const { + static let storyboard = UIStoryboard(name: "Main", bundle: nil) + static let permissionVc = "Permission" + + static let permissions = KNPermissionsFramework().getPermissions() + } + + static func create(permission: Permission) -> PermissionViewController { + let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.permissionVc) as! PermissionViewController + vc.viewModel = KNArchitectureFramework().createPermissionViewModel(permissions: Const.permissions, permission: permission) + return vc + } + + @IBOutlet weak var permissionStateLabel: UILabel! + @IBOutlet weak var requestPermissionButton: UIButton! + + var viewModel: PermissionViewModel! + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + requestPermissionButton.setTitle(NSLocalizedString("permission_request", comment: ""), for: .normal) + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + + guard let viewModel = self?.viewModel else { + return [] + } + + return [ + viewModel.permissionStateMessage.observe(onNext: { (message) in + self?.permissionStateLabel.text = NSLocalizedString(message as? String ?? "", comment: "") + + }), + + viewModel.requestMessage.observe(onNext: { (optionalMessage) in + guard let message = optionalMessage as? String else { + return + } + + let alert = UIAlertController(title: NSLocalizedString("permission_request", comment: ""), message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) + self?.present(alert, animated: true, completion: nil) + + }), + + viewModel.showPermissionButton.observe(onNext: { (show) in + self?.requestPermissionButton.isHidden = !(show as? Bool ?? false) + }) + ] + }) + } + + + @IBAction func requestPermission(sender: Any?) { + viewModel.requestPermission() + } + +} diff --git a/newexample/ios/Demo/Resources/ButtonViewController.swift b/newexample/ios/Demo/Resources/ButtonViewController.swift new file mode 100644 index 000000000..68871ce6f --- /dev/null +++ b/newexample/ios/Demo/Resources/ButtonViewController.swift @@ -0,0 +1,68 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// 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. +// + +import UIKit +import KotlinNativeFramework + +class ButtonViewController : UITableViewController { + + private lazy var viewModel: ButtonViewModel = KNArchitectureFramework().createButtonViewModel(parent: self) + private var lifecycleManager: LifecycleManager! + + private var buttons = [KalugaButton]() + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + tableView.allowsSelection = false + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + return [ + viewModel.buttons.observe { labels in + self?.buttons = labels?.compactMap { $0 as? KalugaButton } ?? [] + self?.tableView.reloadData() + } + ] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return buttons.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: ButtonListCell.Const.identifier, for: indexPath) as! ButtonListCell + ButtonStyleKt.bindButton(cell.button, button: buttons[indexPath.row]) + return cell + } +} + +class ButtonListCell : UITableViewCell { + + struct Const { + static let identifier = "ButtonListCell" + } + + @IBOutlet weak var button: UIButton! + +} diff --git a/newexample/ios/Demo/Resources/ColorViewController.swift b/newexample/ios/Demo/Resources/ColorViewController.swift new file mode 100644 index 000000000..4f2ce7a2f --- /dev/null +++ b/newexample/ios/Demo/Resources/ColorViewController.swift @@ -0,0 +1,202 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// 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. +// + +import UIKit +import KotlinNativeFramework + +class ColorViewController : UIViewController { + + @IBOutlet var backdropColorBackground: UIView! + @IBOutlet var sourceColorBackground: UIView! + @IBOutlet var blendedColorBackground: UIView! + + @IBOutlet var backdropInputField: UITextField! + @IBOutlet var sourceInputField: UITextField! + + @IBOutlet var blendModeButton: UIButton! + @IBOutlet var flipButton: UIButton! + + @IBOutlet var backdropLightenedColorsCollectionView: UICollectionView! + @IBOutlet var backdropDarkenedColorsCollectionView: UICollectionView! + @IBOutlet var sourceLightenedColorsCollectionView: UICollectionView! + @IBOutlet var sourceDarkenedColorsCollectionView: UICollectionView! + @IBOutlet var blendedLightenedColorsCollectionView: UICollectionView! + @IBOutlet var blendedDarkenedColorsCollectionView: UICollectionView! + + private var backdropLightenedColors: [BackgroundStyle] = [] + private var backdropDarkenedColors: [BackgroundStyle] = [] + private var sourceLightenedColors: [BackgroundStyle] = [] + private var sourceDarkenedColors: [BackgroundStyle] = [] + private var blendedLightenedColors: [BackgroundStyle] = [] + private var blendedDarkenedColors: [BackgroundStyle] = [] + + private lazy var viewModel: ColorViewModel = KNArchitectureFramework().createColorViewModel(parent: self) + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + backdropLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) + backdropDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) + sourceLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) + sourceDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) + blendedLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) + blendedDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + + return [ + viewModel.backdropColorBackground.observe { next in + guard let backdropColorBackground = self?.backdropColorBackground, let backdropBackgroundStyle = next as? BackgroundStyle else { + return + } + BackgroundStyleKt.applyBackgroundStyle(backdropColorBackground, style: backdropBackgroundStyle) + }, + viewModel.sourceColorBackground.observe { next in + guard let sourceColorBackground = self?.sourceColorBackground, let sourceBackgroundStyle = next as? BackgroundStyle else { + return + } + BackgroundStyleKt.applyBackgroundStyle(sourceColorBackground, style: sourceBackgroundStyle) + }, + viewModel.blendedColorBackground.observe { next in + guard let blendedColorBackground = self?.blendedColorBackground, let blendedBackgroundStyle = next as? BackgroundStyle else { + return + } + BackgroundStyleKt.applyBackgroundStyle(blendedColorBackground, style: blendedBackgroundStyle) + }, + viewModel.backdropText.observe { text in + self?.backdropInputField.text = text as String? + }, + viewModel.sourceText.observe { text in + self?.sourceInputField.text = text as String? + }, + viewModel.blendModeButton.observe { next in + guard let blendModeButton = self?.blendModeButton, let buttonStyle = next else { + return + } + ButtonStyleKt.bindButton(blendModeButton, button: buttonStyle) + }, + viewModel.lightenBackdrops.observe { next in + self?.backdropLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.backdropLightenedColorsCollectionView?.reloadData() + }, + viewModel.darkenBackdrops.observe { next in + self?.backdropDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.backdropDarkenedColorsCollectionView?.reloadData() + }, + viewModel.lightenSource.observe { next in + self?.sourceLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.sourceLightenedColorsCollectionView?.reloadData() + }, + viewModel.darkenSource.observe { next in + self?.sourceDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.sourceDarkenedColorsCollectionView?.reloadData() + }, + viewModel.lightenBlended.observe { next in + self?.blendedLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.blendedLightenedColorsCollectionView?.reloadData() + }, + viewModel.darkenBlended.observe { next in + self?.blendedDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.blendedDarkenedColorsCollectionView?.reloadData() + }, + ] + } + + ButtonStyleKt.bindButton(flipButton, button: viewModel.flipButton) + } +} + +extension ColorViewController : UITextFieldDelegate { + func textFieldDidEndEditing(_ textField: UITextField) { + if textField == backdropInputField { + viewModel.submitBackdropText(backdropText: textField.text ?? "") + } + if textField == sourceInputField { + viewModel.submitSourceText(sourceText: textField.text ?? "") + } + } +} + +extension ColorViewController : UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if collectionView == backdropLightenedColorsCollectionView { + return backdropLightenedColors.count + } + if collectionView == backdropDarkenedColorsCollectionView { + return backdropDarkenedColors.count + } + if collectionView == sourceLightenedColorsCollectionView { + return sourceLightenedColors.count + } + if collectionView == sourceDarkenedColorsCollectionView { + return sourceDarkenedColors.count + } + if collectionView == blendedLightenedColorsCollectionView { + return blendedLightenedColors.count + } + if collectionView == blendedDarkenedColorsCollectionView { + return blendedDarkenedColors.count + } + return 0 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize.init(width: collectionView.frame.height, height: collectionView.frame.height) + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ColorCollectionCellView.Const.identifier, for: indexPath) as! ColorCollectionCellView + if collectionView == backdropLightenedColorsCollectionView { + BackgroundStyleKt.applyBackgroundStyle(cell, style: backdropLightenedColors[indexPath.row]) + } + if collectionView == backdropDarkenedColorsCollectionView { + BackgroundStyleKt.applyBackgroundStyle(cell, style: backdropDarkenedColors[indexPath.row]) + } + if collectionView == sourceLightenedColorsCollectionView { + BackgroundStyleKt.applyBackgroundStyle(cell, style: sourceLightenedColors[indexPath.row]) + } + if collectionView == sourceDarkenedColorsCollectionView { + BackgroundStyleKt.applyBackgroundStyle(cell, style: sourceDarkenedColors[indexPath.row]) + } + if collectionView == blendedLightenedColorsCollectionView { + BackgroundStyleKt.applyBackgroundStyle(cell, style: blendedLightenedColors[indexPath.row]) + } + if collectionView == blendedDarkenedColorsCollectionView { + BackgroundStyleKt.applyBackgroundStyle(cell, style: blendedDarkenedColors[indexPath.row]) + } + return cell + } +} + +class ColorCollectionCellView : UICollectionViewCell { + + struct Const { + static let identifier = "ColorCollectionCellView" + } + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/newexample/ios/Demo/Resources/LabelViewController.swift b/newexample/ios/Demo/Resources/LabelViewController.swift new file mode 100644 index 000000000..055c3ba09 --- /dev/null +++ b/newexample/ios/Demo/Resources/LabelViewController.swift @@ -0,0 +1,69 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// 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. +// + +import UIKit +import KotlinNativeFramework + +class LabelViewController : UITableViewController { + + private lazy var viewModel: LabelViewModel = KNArchitectureFramework().createLabelViewModel() + private var lifecycleManager: LifecycleManager! + + private var labels = [KalugaLabel]() + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.allowsSelection = false + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + return [ + viewModel.labels.observe { labels in + self?.labels = labels?.compactMap { $0 as? KalugaLabel } ?? [] + self?.tableView.reloadData() + } + ] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return labels.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: LabelListCell.Const.identifier, for: indexPath) as! LabelListCell + TextStyleKt.bindLabel(cell.label, label: labels[indexPath.row]) + return cell + } +} + +class LabelListCell : UITableViewCell { + + struct Const { + static let identifier = "LabelListCell" + } + + @IBOutlet weak var label: UILabel! + +} diff --git a/newexample/ios/Demo/Resources/ResourcesListViewController.swift b/newexample/ios/Demo/Resources/ResourcesListViewController.swift new file mode 100644 index 000000000..906c7c6e7 --- /dev/null +++ b/newexample/ios/Demo/Resources/ResourcesListViewController.swift @@ -0,0 +1,74 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// 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. +// + +import UIKit +import KotlinNativeFramework + +class ResourcesListViewController : UITableViewController { + + private lazy var viewModel: ResourcesListViewModel = KNArchitectureFramework().createResourcesViewModel(parent: self) + private var lifecycleManager: LifecycleManager! + + private var resources = [String]() + private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + return [viewModel.observeResources() { (resources: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in + self?.resources = resources + self?.onSelected = onSelected + self?.tableView.reloadData() + }] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return resources.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: ResourcesListCell.Const.identifier, for: indexPath) as! ResourcesListCell + cell.label.text = resources[indexPath.row] + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + tableView.deselectRow(at: indexPath, animated: true) + } + +} + +class ResourcesListCell : UITableViewCell { + + struct Const { + static let identifier = "ResourcesListCell" + } + + @IBOutlet weak var label: UILabel! + +} diff --git a/newexample/ios/Demo/System/Network/NetworkViewController.swift b/newexample/ios/Demo/System/Network/NetworkViewController.swift new file mode 100644 index 000000000..a3c6c7cef --- /dev/null +++ b/newexample/ios/Demo/System/Network/NetworkViewController.swift @@ -0,0 +1,48 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import Foundation +import UIKit +import KotlinNativeFramework + +class NetworkViewController : UIViewController { + + private let knArchitectureFramework = KNArchitectureFramework() + @IBOutlet weak var networkStateText: UILabel! + private var lifecycleManager: LifecycleManager! + + private lazy var viewModel: NetworkViewModel = NetworkViewModel(networkStateRepoBuilder: NetworkStateRepoBuilder()) + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + + return [ + viewModel.networkState.observe { value in + self?.networkStateText.text = value as? String + } + ] + } + } + +} diff --git a/newexample/ios/Demo/System/SystemViewController.swift b/newexample/ios/Demo/System/SystemViewController.swift new file mode 100644 index 000000000..9945dfd0d --- /dev/null +++ b/newexample/ios/Demo/System/SystemViewController.swift @@ -0,0 +1,83 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import Foundation +import UIKit +import KotlinNativeFramework + +class SystemViewController : UITableViewController { + + private let knArchitectureFramework = KNArchitectureFramework() + private lazy var viewModel: SystemViewModel = { + return self.knArchitectureFramework.createSystemViewModel(parent: self) + }() + + private var modules = [String]() + private var onModuleTapped: ((KotlinInt) -> KotlinUnit)? = nil + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { [weak self] in + guard let viewModel = self?.viewModel else { + return [] + } + return [ + viewModel.observeModules { (modules: [String], onButtonTapped: @escaping (KotlinInt) -> KotlinUnit) in + self?.modules = modules + self?.onModuleTapped = onButtonTapped + self?.tableView.reloadData() + } + ] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return modules.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: SystemListCell.Const.identifier, for: indexPath) as! SystemListCell + cell.label.text = modules[indexPath.row] + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let _ = onModuleTapped?(KotlinInt.init(int: Int32(indexPath.row))) + tableView.deselectRow(at: indexPath, animated: true) + } + +} + +class SystemListCell : UITableViewCell { + + struct Const { + static let identifier = "SystemListCell" + } + + @IBOutlet weak var label: UILabel! + +} diff --git a/newexample/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift b/newexample/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift new file mode 100644 index 000000000..81fa29aa4 --- /dev/null +++ b/newexample/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift @@ -0,0 +1,51 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit + +final class FittingWidthAutomaticHeightCollectionViewFlowLayout: UICollectionViewFlowLayout { + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes] + layoutAttributesObjects?.forEach({ layoutAttributes in + if layoutAttributes.representedElementCategory == .cell { + if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame { + layoutAttributes.frame = newFrame + } + } + }) + return layoutAttributesObjects + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + guard let collectionView = collectionView else { + fatalError() + } + guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else { + return nil + } + + layoutAttributes.frame.origin.x = sectionInset.left + layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right + return layoutAttributes + } + + override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, + withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool { + return true + } +} diff --git a/newexample/ios/DemoTests/DemoTests.swift b/newexample/ios/DemoTests/DemoTests.swift new file mode 100644 index 000000000..2aa6b9b30 --- /dev/null +++ b/newexample/ios/DemoTests/DemoTests.swift @@ -0,0 +1,46 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + +*/ + +import XCTest +@testable import Demo +import KotlinNativeFramework + +class DemoTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + assert(KotlinNativeFramework().hello() == "Hello from the shared module common source") + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // 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/newexample/ios/DemoTests/Info.plist b/newexample/ios/DemoTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/newexample/ios/DemoTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/newexample/ios/DemoUITests/DemoUITests.swift b/newexample/ios/DemoUITests/DemoUITests.swift new file mode 100644 index 000000000..85a5ce551 --- /dev/null +++ b/newexample/ios/DemoUITests/DemoUITests.swift @@ -0,0 +1,44 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + +*/ + +import XCTest + +class DemoUITests: XCTestCase { + + override func setUp() { + // 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 + + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() + + // 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 tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + +} diff --git a/newexample/ios/DemoUITests/Info.plist b/newexample/ios/DemoUITests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/newexample/ios/DemoUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/newexample/ios/ios.xcodeproj/project.pbxproj b/newexample/ios/ios.xcodeproj/project.pbxproj deleted file mode 100644 index d6c8d7d77..000000000 --- a/newexample/ios/ios.xcodeproj/project.pbxproj +++ /dev/null @@ -1,383 +0,0 @@ - // !$*UTF8*$! - { - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - - /* Begin PBXBuildFile section */ -058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; -058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; - 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; - 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; - /* End PBXBuildFile section */ - - /* Begin PBXCopyFilesBuildPhase section */ - 7555FFB4242A642300829871 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - /* End PBXCopyFilesBuildPhase section */ - - /* Begin PBXFileReference section */ - 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; -058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 7555FF7B242A565900829871 /* ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ios.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - /* End PBXFileReference section */ - - /* Begin PBXFrameworksBuildPhase section */ - 7555FF78242A565900829871 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - /* End PBXFrameworksBuildPhase section */ - - /* Begin PBXGroup section */ - 058557D7273AAEEB004C7B11 /* Preview Content */ = { - isa = PBXGroup; - children = ( - 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; -}; - 7555FF72242A565900829871 = { - isa = PBXGroup; - children = ( - 7555FF7D242A565900829871 /* ios */, - 7555FF7C242A565900829871 /* Products */, - 7555FFB0242A642200829871 /* Frameworks */, - ); - sourceTree = ""; - }; - 7555FF7C242A565900829871 /* Products */ = { - isa = PBXGroup; - children = ( - 7555FF7B242A565900829871 /* ios.app */, - ); - name = Products; - sourceTree = ""; - }; - 7555FF7D242A565900829871 /* ios */ = { - isa = PBXGroup; - children = ( - 058557BA273AAA24004C7B11 /* Assets.xcassets */, - 7555FF82242A565900829871 /* ContentView.swift */, - 7555FF8C242A565B00829871 /* Info.plist */, - 2152FB032600AC8F00CF470E /* iOSApp.swift */, - 058557D7273AAEEB004C7B11 /* Preview Content */, - ); - path = ios; - sourceTree = ""; - }; - 7555FFB0242A642200829871 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; - /* End PBXGroup section */ - - /* Begin PBXNativeTarget section */ - 7555FF7A242A565900829871 /* ios */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "ios" */; - buildPhases = ( - 7555FFB5242A651A00829871 /* ShellScript */, - 7555FF77242A565900829871 /* Sources */, - 7555FF78242A565900829871 /* Frameworks */, - 7555FF79242A565900829871 /* Resources */, - 7555FFB4242A642300829871 /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = ios; - productName = ios; - productReference = 7555FF7B242A565900829871 /* ios.app */; - productType = "com.apple.product-type.application"; - }; - /* End PBXNativeTarget section */ - - /* Begin PBXProject section */ - 7555FF73242A565900829871 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1130; - LastUpgradeCheck = 1130; - ORGANIZATIONNAME = orgName; - TargetAttributes = { - 7555FF7A242A565900829871 = { - CreatedOnToolsVersion = 11.3.1; - }; - }; - }; - buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "ios" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 7555FF72242A565900829871; - productRefGroup = 7555FF7C242A565900829871 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 7555FF7A242A565900829871 /* ios */, - ); - }; - /* End PBXProject section */ - - /* Begin PBXResourcesBuildPhase section */ - 7555FF79242A565900829871 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, - 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - /* End PBXResourcesBuildPhase section */ - - /* Begin PBXShellScriptBuildPhase section */ - 7555FFB5242A651A00829871 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; - }; - /* End PBXShellScriptBuildPhase section */ - - /* Begin PBXSourcesBuildPhase section */ - 7555FF77242A565900829871 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, - 7555FF83242A565900829871 /* ContentView.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - /* End PBXSourcesBuildPhase section */ - - /* Begin XCBuildConfiguration section */ - 7555FFA3242A565B00829871 /* 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-with-dsym"; - 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.1; - 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; - }; - 7555FFA4242A565B00829871 /* 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.1; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 7555FFA6242A565B00829871 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_ASSET_PATHS = "\"ios/Preview Content\""; - ENABLE_PREVIEWS = YES; - FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; - INFOPLIST_FILE = ios/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - shared, - ); - PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.ios; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 7555FFA7242A565B00829871 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_ASSET_PATHS = "\"ios/Preview Content\""; - ENABLE_PREVIEWS = YES; - FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; - INFOPLIST_FILE = ios/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - shared, - ); - PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.ios; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - /* End XCBuildConfiguration section */ - - /* Begin XCConfigurationList section */ - 7555FF76242A565900829871 /* Build configuration list for PBXProject "ios" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7555FFA3242A565B00829871 /* Debug */, - 7555FFA4242A565B00829871 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "ios" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7555FFA6242A565B00829871 /* Debug */, - 7555FFA7242A565B00829871 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - /* End XCConfigurationList section */ - }; - rootObject = 7555FF73242A565900829871 /* Project object */; - } \ No newline at end of file diff --git a/newexample/ios/ios/Assets.xcassets/AccentColor.colorset/Contents.json b/newexample/ios/ios/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index ee7e3ca03..000000000 --- a/newexample/ios/ios/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} \ No newline at end of file diff --git a/newexample/ios/ios/Assets.xcassets/AppIcon.appiconset/Contents.json b/newexample/ios/ios/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index fb88a396b..000000000 --- a/newexample/ios/ios/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} \ No newline at end of file diff --git a/newexample/ios/ios/Assets.xcassets/Contents.json b/newexample/ios/ios/Assets.xcassets/Contents.json deleted file mode 100644 index 4aa7c5350..000000000 --- a/newexample/ios/ios/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} \ No newline at end of file diff --git a/newexample/ios/ios/ContentView.swift b/newexample/ios/ios/ContentView.swift deleted file mode 100644 index 4c626ac08..000000000 --- a/newexample/ios/ios/ContentView.swift +++ /dev/null @@ -1,16 +0,0 @@ -import SwiftUI -import shared - -struct ContentView: View { - let greet = Greeting().greeting() - - var body: some View { - Text(greet) - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} \ No newline at end of file diff --git a/newexample/ios/ios/Info.plist b/newexample/ios/ios/Info.plist deleted file mode 100644 index 8044709cf..000000000 --- a/newexample/ios/ios/Info.plist +++ /dev/null @@ -1,48 +0,0 @@ - - - - - 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 - - - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UILaunchScreen - - - \ No newline at end of file diff --git a/newexample/ios/ios/Preview Content/Preview Assets.xcassets/Contents.json b/newexample/ios/ios/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 4aa7c5350..000000000 --- a/newexample/ios/ios/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} \ No newline at end of file diff --git a/newexample/ios/ios/iOSApp.swift b/newexample/ios/ios/iOSApp.swift deleted file mode 100644 index 0648e8602..000000000 --- a/newexample/ios/ios/iOSApp.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI - -@main -struct iOSApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} \ No newline at end of file diff --git a/newexample/shared/build.gradle.kts b/newexample/shared/build.gradle.kts index 39dafc51e..01ad3d30b 100644 --- a/newexample/shared/build.gradle.kts +++ b/newexample/shared/build.gradle.kts @@ -6,29 +6,51 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } -commonComponent() +val libraryVersion = Library.version +val modules = listOf( + "alerts" to true, + "architecture" to true, + "base" to false, + "bluetooth" to false, + "beacons" to false, + "date-time-picker" to true, + "hud" to true, + "keyboard" to true, + "links" to false, + "location" to false, + "logging" to false, + "resources" to true, + "review" to true, + "system" to false, + "permissions" to false +) + +commonComponent { + baseName = "KalugaExampleShared" + isStatic = false + transitiveExport = true + modules.forEach { (module, isExportable) -> + if (isExportable) { + export("com.splendo.kaluga:$module:$libraryVersion") + } + } +} kotlin { sourceSets { getByName("commonMain") { dependencies { - val libraryVersion = Library.version - api("com.splendo.kaluga:alerts:$libraryVersion") - api("com.splendo.kaluga:architecture:$libraryVersion") - api("com.splendo.kaluga:base:$libraryVersion") - api("com.splendo.kaluga:bluetooth:$libraryVersion") - api("com.splendo.kaluga:beacons:$libraryVersion") - api("com.splendo.kaluga:date-time-picker:$libraryVersion") - api("com.splendo.kaluga:hud:$libraryVersion") - api("com.splendo.kaluga:keyboard:$libraryVersion") - api("com.splendo.kaluga:links:$libraryVersion") - api("com.splendo.kaluga:location:$libraryVersion") - api("com.splendo.kaluga:logging:$libraryVersion") - api("com.splendo.kaluga:resources:$libraryVersion") - api("com.splendo.kaluga:review:$libraryVersion") - api("com.splendo.kaluga:system:$libraryVersion") - api("com.splendo.kaluga:permissions:$libraryVersion") + modules.forEach { (module, _) -> + api("com.splendo.kaluga:$module:$libraryVersion") + } + expose(Dependencies.Koin.Core) } } } -} \ No newline at end of file +} + +android { + dependencies { + expose(Dependencies.Koin.Android) + } +} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt b/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt similarity index 60% rename from newexample/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt rename to newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt index a2825a380..972f1dfce 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt +++ b/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -1,42 +1,35 @@ /* + Copyright 2022 Splendo Consulting B.V. The Netherlands -Copyright 2022 Splendo Consulting B.V. The Netherlands + 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 - 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 - 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. - 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. + */ -*/ +@file:JvmName("AndroidDependencyInjection") +package com.splendo.kaluga.example.shared.di -package com.splendo.kaluga.example.di - -/* ktlint-disable no-wildcard-imports */ import com.splendo.kaluga.alerts.AlertPresenter -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.base.singleThreadDispatcher -import com.splendo.kaluga.bluetooth.Bluetooth +import com.splendo.kaluga.base.ApplicationHolder import com.splendo.kaluga.bluetooth.BluetoothBuilder -import com.splendo.kaluga.bluetooth.beacons.Beacons -import com.splendo.kaluga.bluetooth.device.ConnectionSettings -import com.splendo.kaluga.bluetooth.scanner.BaseScanner import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter -import com.splendo.kaluga.example.architecture.ArchitectureDetailsActivity import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.CloseDetailsNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel @@ -69,66 +62,24 @@ import com.splendo.kaluga.keyboard.FocusHandler import com.splendo.kaluga.keyboard.KeyboardManager import com.splendo.kaluga.links.LinksBuilder import com.splendo.kaluga.location.LocationStateRepoBuilder -import com.splendo.kaluga.logging.Logger -import com.splendo.kaluga.logging.RestrictedLogLevel -import com.splendo.kaluga.logging.RestrictedLogger -import com.splendo.kaluga.permissions.base.BasePermissionManager import com.splendo.kaluga.permissions.base.Permission -import com.splendo.kaluga.permissions.base.Permissions -import com.splendo.kaluga.permissions.base.PermissionsBuilder -import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermissionIfNotRegistered import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.location.registerLocationPermissionIfNotRegistered -import com.splendo.kaluga.permissions.registerAllPermissions import com.splendo.kaluga.resources.StyledStringBuilder import com.splendo.kaluga.review.ReviewManager import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch +import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.module.Module +import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.module -import kotlin.time.Duration.Companion.minutes - -val utilitiesModule = module { - single { RestrictedLogger(RestrictedLogLevel.None) } - single { PermissionsBuilder() } - single { LocationStateRepoBuilder( - permissionsBuilder = { - val builder = get() - builder.registerLocationPermissionIfNotRegistered( - settings = BasePermissionManager.Settings(logger = get()) - ) - Permissions(builder, it) - } - ) } - single { - BluetoothBuilder( - permissionsBuilder = { - val builder = get() - val settings = BasePermissionManager.Settings(logger = get()) - builder.registerBluetoothPermissionIfNotRegistered(settings = settings) - builder.registerLocationPermissionIfNotRegistered(settings = settings) - Permissions(builder, it) - } - ).create( - scannerSettingsBuilder = { BaseScanner.Settings(it, logger = get()) }, - connectionSettings = ConnectionSettings(logger = get()) - ) - } - single { Beacons(get(), timeout = 1.minutes) } -} -val viewModelModule = module { +internal val androidModule = module { viewModel { (navigator: Navigator) -> - ExampleViewModel( - navigator - ) + ExampleViewModel(navigator) } viewModel { (navigator: Navigator) -> - FeatureListViewModel( - navigator - ) + FeatureListViewModel(navigator) } viewModel { (navigator: Navigator>) -> @@ -139,10 +90,7 @@ val viewModelModule = module { } viewModel { (navigator: Navigator) -> - PermissionsListViewModel( - get(), - navigator - ) + PermissionsListViewModel(navigator) } viewModel { (permission: Permission) -> PermissionViewModel(get(), permission) } @@ -150,17 +98,13 @@ val viewModelModule = module { viewModel { (permission: LocationPermission) -> LocationViewModel(permission, get()) } viewModel { (navigator: Navigator>) -> - ArchitectureInputViewModel( - navigator - ) + ArchitectureInputViewModel(navigator) } - viewModel { (initialDetail: InputDetails) -> + viewModel { (initialDetail: InputDetails, navigator: Navigator) -> ArchitectureDetailsViewModel( initialDetail, - ActivityNavigator { - NavigationSpec.Close(ArchitectureDetailsActivity.resultCode) - } + navigator ) } @@ -199,24 +143,19 @@ val viewModelModule = module { } viewModel { (navigator: Navigator) -> - BluetoothListViewModel( - get(), - navigator - ) + BluetoothListViewModel(navigator) } viewModel { (identifier: com.splendo.kaluga.bluetooth.device.Identifier) -> - BluetoothDeviceDetailViewModel(get(), identifier) + BluetoothDeviceDetailViewModel(identifier) } viewModel { - BeaconsListViewModel(get()) + BeaconsListViewModel() } viewModel { (navigator: Navigator) -> - ResourcesListViewModel( - navigator - ) + ResourcesListViewModel(navigator) } viewModel { @@ -231,3 +170,14 @@ val viewModelModule = module { ButtonViewModel(StyledStringBuilder.Provider(), AlertPresenter.Builder()) } } + +fun initKoin(customModules: List = emptyList()) = initKoin( + androidModule, + { LocationStateRepoBuilder(permissionsBuilder = it) }, + { BluetoothBuilder(permissionsBuilder = it) }, + customModules +) + +internal actual val appDeclaration: KoinAppDeclaration = { + androidContext(ApplicationHolder.applicationContext) +} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt new file mode 100644 index 000000000..13c160b79 --- /dev/null +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -0,0 +1,84 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.di + +import com.splendo.kaluga.bluetooth.Bluetooth +import com.splendo.kaluga.bluetooth.BluetoothBuilder +import com.splendo.kaluga.bluetooth.beacons.Beacons +import com.splendo.kaluga.bluetooth.device.ConnectionSettings +import com.splendo.kaluga.bluetooth.scanner.BaseScanner +import com.splendo.kaluga.location.LocationStateRepoBuilder +import com.splendo.kaluga.logging.Logger +import com.splendo.kaluga.logging.RestrictedLogLevel +import com.splendo.kaluga.logging.RestrictedLogger +import com.splendo.kaluga.permissions.base.BasePermissionManager +import com.splendo.kaluga.permissions.base.Permissions +import com.splendo.kaluga.permissions.base.PermissionsBuilder +import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermissionIfNotRegistered +import com.splendo.kaluga.permissions.location.registerLocationPermissionIfNotRegistered +import org.koin.core.context.startKoin +import org.koin.core.module.Module +import org.koin.dsl.KoinAppDeclaration +import org.koin.dsl.module +import kotlin.coroutines.CoroutineContext +import kotlin.time.Duration.Companion.minutes + +typealias LocationStateRepoBuilderBuilder = (suspend (CoroutineContext) -> Permissions) -> LocationStateRepoBuilder +typealias BluetoothBuilderBuilder = (suspend (CoroutineContext) -> Permissions) -> BluetoothBuilder + +private fun sharedModule( + locationStateRepoBuilderBuilder: LocationStateRepoBuilderBuilder, + bluetoothBuilderBuilder: BluetoothBuilderBuilder +) = module { + single { RestrictedLogger(RestrictedLogLevel.None) } + single { PermissionsBuilder() } + single { locationStateRepoBuilderBuilder { + val builder = get() + builder.registerLocationPermissionIfNotRegistered( + settings = BasePermissionManager.Settings(logger = get()) + ) + Permissions(builder, it) + } + } + single { + bluetoothBuilderBuilder { + val builder = get() + val settings = BasePermissionManager.Settings(logger = get()) + builder.registerBluetoothPermissionIfNotRegistered(settings = settings) + builder.registerLocationPermissionIfNotRegistered(settings = settings) + Permissions(builder, it) + } + .create( + scannerSettingsBuilder = { BaseScanner.Settings(it, logger = get()) }, + connectionSettings = ConnectionSettings(logger = get()) + ) + } + single { Beacons(get(), timeout = 1.minutes) } +} + +internal fun initKoin( + platformModule: Module, + locationStateRepoBuilderBuilder: LocationStateRepoBuilderBuilder, + bluetoothBuilderBuilder: BluetoothBuilderBuilder, + customModules: List = emptyList() +) = startKoin { + appDeclaration() + modules(platformModule, sharedModule(locationStateRepoBuilderBuilder, bluetoothBuilderBuilder), *customModules.toTypedArray()) +} + +internal expect val appDeclaration: KoinAppDeclaration diff --git a/newexample/shared/src/commonMain/kotlin/model/contacts/ContactDetails.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/model/contacts/ContactDetails.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt diff --git a/newexample/shared/src/commonMain/kotlin/stylable/ButtonStyles.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt similarity index 94% rename from newexample/shared/src/commonMain/kotlin/stylable/ButtonStyles.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt index 07eba41b7..2a0489501 100644 --- a/newexample/shared/src/commonMain/kotlin/stylable/ButtonStyles.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt @@ -1,3 +1,20 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.stylable import com.splendo.kaluga.resources.DefaultColors diff --git a/newexample/shared/src/commonMain/kotlin/stylable/TextStyles.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt similarity index 67% rename from newexample/shared/src/commonMain/kotlin/stylable/TextStyles.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt index b9d04f117..34faa615a 100644 --- a/newexample/shared/src/commonMain/kotlin/stylable/TextStyles.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt @@ -1,3 +1,20 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.stylable import com.splendo.kaluga.resources.DefaultColors diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/ExampleViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/ExampleViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/alert/AlertViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/alert/AlertViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureDetailsViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureDetailsViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListBeaconViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListBeaconViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt similarity index 92% rename from newexample/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt index 4f752265f..2a626d61c 100644 --- a/newexample/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt @@ -25,8 +25,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class BeaconsListViewModel(private val service: Beacons) : BaseLifecycleViewModel() { +class BeaconsListViewModel : BaseLifecycleViewModel(), KoinComponent { + + private val service: Beacons by inject() private val _isScanning = MutableStateFlow(false) val isScanning = _isScanning.toInitializedObservable(coroutineScope) diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt similarity index 94% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt index 68a08b032..522e649e2 100644 --- a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt @@ -43,6 +43,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class DeviceDetailsSpec : NavigationBundleSpec>(setOf(DeviceDetailsSpecRow.UUIDRow)) @@ -50,12 +52,13 @@ sealed class DeviceDetailsSpecRow(associatedType: NavigationBundleSpecType object UUIDRow : DeviceDetailsSpecRow(NavigationBundleSpecType.StringType) } -class BluetoothDeviceDetailViewModel(private val bluetooth: Bluetooth, private val identifier: Identifier) : BaseLifecycleViewModel() { +class BluetoothDeviceDetailViewModel(private val identifier: Identifier) : BaseLifecycleViewModel(), KoinComponent { companion object { private const val rssi_frequency = 1000L } + private val bluetooth: Bluetooth by inject() private val device = bluetooth.devices()[identifier] val name = device.info().map { it.name ?: "bluetooth_no_name".localized() }.toUninitializedObservable(coroutineScope) diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt similarity index 90% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt index 3f87cdc8c..8c3988189 100644 --- a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt @@ -29,11 +29,14 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class BluetoothListNavigation(bundle: NavigationBundle>) : NavigationAction>(bundle) -class BluetoothListViewModel(private val bluetooth: Bluetooth, navigator: Navigator) : NavigatingViewModel(navigator) { +class BluetoothListViewModel(navigator: Navigator) : NavigatingViewModel(navigator), KoinComponent { + private val bluetooth: Bluetooth by inject() private val _isScanning = MutableStateFlow(false) val isScanning = _isScanning.toInitializedObservable(coroutineScope) diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothServiceViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothServiceViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetParentViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetParentViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/bottomsheet/BottomSheetViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/contacts/ContactDetailsViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/contacts/ContactDetailsViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/contacts/ContactsListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/contacts/ContactsListViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/datetimepicker/DateTimePickerViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/datetimepicker/DateTimePickerViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/featureList/FeatureListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/featureList/FeatureListViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/featureList/PlatformSpecific.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/featureList/PlatformSpecific.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/hud/HudViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/hud/HudViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/keyboard/KeyboardViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/keyboard/KeyboardViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt similarity index 91% rename from newexample/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt index fe6b78e04..cdbdd53a3 100644 --- a/newexample/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt @@ -34,6 +34,8 @@ import com.splendo.kaluga.permissions.registerAllPermissionsNotRegistered import com.splendo.kaluga.resources.localized import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject @Serializable enum class PermissionView(val title: String) { @@ -52,7 +54,7 @@ class PermissionNavigationBundleSpec : NavigationBundleSpec(PermissionNavigationBundleSpec().toBundle { spec -> spec.convertValue(permissionView) }) -class PermissionsListViewModel(private val permissionsBuilder: PermissionsBuilder, navigator: Navigator) : NavigatingViewModel(navigator) { +class PermissionsListViewModel(navigator: Navigator) : NavigatingViewModel(navigator), KoinComponent { val permissions = observableOf( listOf( @@ -66,6 +68,7 @@ class PermissionsListViewModel(private val permissionsBuilder: PermissionsBuilde PermissionView.Storage ) ) + private val permissionsBuilder: PermissionsBuilder by inject() fun onPermissionPressed(permissionView: PermissionView) { coroutineScope.launch { diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/resources/ButtonViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/resources/ButtonViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/resources/ColorViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/resources/ColorViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/resources/LabelViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/resources/LabelViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/resources/ResourcesListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/resources/ResourcesListViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt rename to newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt new file mode 100644 index 000000000..ab8e1f8b1 --- /dev/null +++ b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -0,0 +1,36 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.di + +import com.splendo.kaluga.bluetooth.BluetoothBuilder +import com.splendo.kaluga.location.LocationStateRepoBuilder +import org.koin.core.module.Module +import org.koin.dsl.KoinAppDeclaration +import org.koin.dsl.module + +internal val iosModule = module { } + +fun initKoin(customModules: List = emptyList()) = initKoin( + iosModule, + { LocationStateRepoBuilder(permissionsBuilder = it) }, + { BluetoothBuilder(permissionsBuilder = it) }, + customModules +) + +internal actual val appDeclaration: KoinAppDeclaration = { +} \ No newline at end of file diff --git a/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt new file mode 100644 index 000000000..2fc8f1287 --- /dev/null +++ b/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -0,0 +1,22 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.di + +import org.koin.dsl.KoinAppDeclaration + +internal actual val appDeclaration: KoinAppDeclaration = { } diff --git a/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt new file mode 100644 index 000000000..2fc8f1287 --- /dev/null +++ b/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -0,0 +1,22 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.di + +import org.koin.dsl.KoinAppDeclaration + +internal actual val appDeclaration: KoinAppDeclaration = { } From fc31fcb05aa7b459cb756351a6f107b7840def40 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 9 Nov 2022 13:09:42 +0100 Subject: [PATCH 028/227] iOS continued --- bluetooth/build.gradle.kts | 3 + .../kotlin/device/DeviceInfo.kt | 4 + .../commonMain/kotlin/device/DeviceInfo.kt | 29 +++ .../src/iosMain/kotlin/device/DeviceInfo.kt | 2 + .../src/jsMain/kotlin/device/DeviceInfo.kt | 3 + .../src/jvmMain/kotlin/device/DeviceInfo.kt | 5 + .../architecture/KNArchitectureFramework.kt | 229 ------------------ .../android/src/main/AndroidManifest.xml | 1 + .../splendo/kaluga/example/InfoFragment.kt | 20 +- .../architecture/ArchitectureInputActivity.kt | 3 +- .../example/bluetooth/BluetoothActivity.kt | 4 +- .../bluetooth/BluetoothMoreActivity.kt | 13 +- .../kaluga/example/link/LinksActivity.kt | 5 +- .../location/LocationBackgroundService.kt | 6 +- .../permissions/PermissionsDemoActivity.kt | 25 +- .../NetworkFragment.kt => NetworkActivity.kt} | 25 +- .../kaluga/example/system/SystemActivity.kt | 19 +- ...gment_network.xml => activity_network.xml} | 0 .../src/main/res/layout/activity_system.xml | 17 +- newexample/gradle.properties | 2 +- newexample/ios/Demo.xcodeproj/project.pbxproj | 102 ++------ .../Demo/Alerts/AlertsViewController.swift | 10 +- newexample/ios/Demo/AppDelegate.swift | 4 +- .../ArchitectureDetailsViewController.swift | 25 +- .../ArchitectureInputViewController.swift | 37 +-- .../ios/Demo/Beacons/BeaconsViewCell.swift | 2 +- .../Demo/Beacons/BeaconsViewController.swift | 10 +- ...BluetoothDeviceDetailsViewController.swift | 101 ++++---- .../Bluetooth/BluetoothViewController.swift | 55 +++-- .../DateTimePickerViewController.swift | 24 +- .../ios/Demo/ExampleViewController.swift | 57 +++-- .../FeaturesListViewController.swift | 61 +++-- .../ios/Demo/Info/InfoViewController.swift | 65 +++-- .../KeyboardManagerViewController.swift | 16 +- .../ios/Demo/Links/LinksViewController.swift | 12 +- .../LoadingViewController.swift | 12 +- .../Location/LocationViewController.swift | 18 +- .../PermissionListViewController.swift | 48 ++-- .../PermissionViewController.swift | 48 ++-- .../Demo/Resources/ButtonViewController.swift | 6 +- .../Demo/Resources/ColorViewController.swift | 6 +- .../Demo/Resources/LabelViewController.swift | 10 +- .../ResourcesListViewController.swift | 54 +++-- .../Network/NetworkViewController.swift | 11 +- .../Demo/System/SystemViewController.swift | 44 ++-- newexample/ios/DemoTests/DemoTests.swift | 2 +- newexample/shared/build.gradle.kts | 5 +- .../example/shared/di/DependencyInjection.kt | 19 +- .../permissions/NotificationOptions.kt | 21 ++ .../ArchitectureInputViewModel.kt | 2 +- .../viewmodel/beacons/BeaconsListViewModel.kt | 10 +- .../BluetoothDeviceDetailViewModel.kt | 16 +- .../bluetooth/BluetoothListDeviceViewModel.kt | 13 +- .../bluetooth/BluetoothListViewModel.kt | 7 +- .../shared/viewmodel/info/InfoViewModel.kt | 72 ++---- .../shared/viewmodel/link/LinksViewModel.kt | 25 +- .../viewmodel/location/LocationViewModel.kt | 18 +- .../permissions/PermissionViewModel.kt | 11 +- .../permissions/PermissionsListViewModel.kt | 34 ++- .../viewmodel/system/SystemViewModel.kt | 11 +- .../system/network/NetworkViewModel.kt | 15 +- .../shared/viewmodel/info/InfoNavigator.kt | 35 +++ .../shared/viewmodel/link/BrowserNavigator.kt | 29 +++ .../permissions/NotificationOptions.kt | 23 ++ .../permissions/NotificationOptions.kt | 21 ++ .../permissions/NotificationOptions.kt | 21 ++ 66 files changed, 799 insertions(+), 864 deletions(-) rename newexample/android/src/main/java/com/splendo/kaluga/example/system/{fragments/NetworkFragment.kt => NetworkActivity.kt} (65%) rename newexample/android/src/main/res/layout/{fragment_network.xml => activity_network.xml} (100%) create mode 100644 newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt create mode 100644 newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt create mode 100644 newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt create mode 100644 newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt create mode 100644 newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt create mode 100644 newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt diff --git a/bluetooth/build.gradle.kts b/bluetooth/build.gradle.kts index 5f166f78f..3074d3fbd 100644 --- a/bluetooth/build.gradle.kts +++ b/bluetooth/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("multiplatform") + kotlin("plugin.serialization") id("jacoco") id("convention.publication") id("com.android.library") @@ -20,6 +21,8 @@ kotlin { commonMain { dependencies { api(project(":bluetooth-permissions", "")) + expose(Dependencies.KotlinX.Serialization.Core) + expose(Dependencies.KotlinX.Serialization.Json) } } commonTest { diff --git a/bluetooth/src/androidLibMain/kotlin/device/DeviceInfo.kt b/bluetooth/src/androidLibMain/kotlin/device/DeviceInfo.kt index b7e980e8c..c2e4cd326 100644 --- a/bluetooth/src/androidLibMain/kotlin/device/DeviceInfo.kt +++ b/bluetooth/src/androidLibMain/kotlin/device/DeviceInfo.kt @@ -15,6 +15,8 @@ */ +@file:JvmName("DeviceInfoAndroid") + package com.splendo.kaluga.bluetooth.device import com.splendo.kaluga.bluetooth.randomUUIDString @@ -23,5 +25,7 @@ actual typealias Identifier = String actual fun randomIdentifier() = randomUUIDString() +actual fun identifierFromString(stringValue: String): Identifier? = stringValue + actual val Identifier.stringValue: String get() = this diff --git a/bluetooth/src/commonMain/kotlin/device/DeviceInfo.kt b/bluetooth/src/commonMain/kotlin/device/DeviceInfo.kt index 6207a2595..1bc054965 100644 --- a/bluetooth/src/commonMain/kotlin/device/DeviceInfo.kt +++ b/bluetooth/src/commonMain/kotlin/device/DeviceInfo.kt @@ -19,14 +19,43 @@ package com.splendo.kaluga.bluetooth.device import com.splendo.kaluga.base.utils.DefaultKalugaDate import com.splendo.kaluga.base.utils.KalugaDate +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import kotlin.math.pow expect class Identifier expect fun randomIdentifier(): Identifier +expect fun identifierFromString(stringValue: String): Identifier? + expect val Identifier.stringValue: String +@Serializable(with = IdentifierSerializer::class) +data class SerializableIdentifier(val identifier: Identifier) + +val Identifier.serializable get() = SerializableIdentifier(this) + +open class IdentifierSerializer : + KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("IdentifierString", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: SerializableIdentifier) { + val string = value.identifier.stringValue + encoder.encodeString(string) + } + + override fun deserialize(decoder: Decoder): SerializableIdentifier { + val string = decoder.decodeString() + return SerializableIdentifier(identifierFromString(string)!!) + } +} + expect interface DeviceWrapper { val name: String? val identifier: Identifier diff --git a/bluetooth/src/iosMain/kotlin/device/DeviceInfo.kt b/bluetooth/src/iosMain/kotlin/device/DeviceInfo.kt index 4ce31a414..b63fa7394 100644 --- a/bluetooth/src/iosMain/kotlin/device/DeviceInfo.kt +++ b/bluetooth/src/iosMain/kotlin/device/DeviceInfo.kt @@ -23,5 +23,7 @@ actual typealias Identifier = NSUUID actual fun randomIdentifier() = Identifier.UUID() +actual fun identifierFromString(stringValue: String): Identifier? = NSUUID(stringValue) + actual val Identifier.stringValue: String get() = UUIDString diff --git a/bluetooth/src/jsMain/kotlin/device/DeviceInfo.kt b/bluetooth/src/jsMain/kotlin/device/DeviceInfo.kt index 7ed059d60..dbd17364e 100644 --- a/bluetooth/src/jsMain/kotlin/device/DeviceInfo.kt +++ b/bluetooth/src/jsMain/kotlin/device/DeviceInfo.kt @@ -19,10 +19,13 @@ package com.splendo.kaluga.bluetooth.device import com.splendo.kaluga.bluetooth.UUID import com.splendo.kaluga.bluetooth.randomUUIDString +import com.splendo.kaluga.bluetooth.unsafeUUIDFrom actual typealias Identifier = UUID actual fun randomIdentifier() = Identifier(randomUUIDString()) +actual fun identifierFromString(stringValue: String): Identifier? = unsafeUUIDFrom(stringValue) + actual val Identifier.stringValue: String get() = uuidString diff --git a/bluetooth/src/jvmMain/kotlin/device/DeviceInfo.kt b/bluetooth/src/jvmMain/kotlin/device/DeviceInfo.kt index 7ed059d60..572300133 100644 --- a/bluetooth/src/jvmMain/kotlin/device/DeviceInfo.kt +++ b/bluetooth/src/jvmMain/kotlin/device/DeviceInfo.kt @@ -15,14 +15,19 @@ */ +@file:JvmName("DeviceInfoJVM") + package com.splendo.kaluga.bluetooth.device import com.splendo.kaluga.bluetooth.UUID import com.splendo.kaluga.bluetooth.randomUUIDString +import com.splendo.kaluga.bluetooth.unsafeUUIDFrom actual typealias Identifier = UUID actual fun randomIdentifier() = Identifier(randomUUIDString()) +actual fun identifierFromString(stringValue: String): Identifier? = unsafeUUIDFrom(stringValue) + actual val Identifier.stringValue: String get() = uuidString diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt index a719db1ec..ea305d5d6 100644 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt +++ b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt @@ -82,109 +82,6 @@ import platform.UserNotifications.UNAuthorizationOptionSound class KNArchitectureFramework { - fun createExampleViewModel( - parent: UIViewController, - containerView: UIView, - featuresList: () -> UIViewController, - info: () -> UIViewController - ): ExampleViewModel { - return ExampleViewModel( - ViewControllerNavigator(parent) { action -> - NavigationSpec.Nested( - NavigationSpec.Nested.Type.Replace(1), - containerView, - when (action) { - is ExampleTabNavigation.FeatureList -> featuresList - is ExampleTabNavigation.Info -> info - } - ) - } - ) - } - - fun createFeatureListViewModel(parent: UIViewController): FeatureListViewModel { - return FeatureListViewModel( - ViewControllerNavigator(parent) { action -> - NavigationSpec.Segue( - when (action) { - is FeatureListNavigationAction.Location -> "showLocation" - is FeatureListNavigationAction.Permissions -> "showPermissions" - is FeatureListNavigationAction.Alerts -> "showAlerts" - is FeatureListNavigationAction.DateTimePicker -> "showDateTimePicker" - is FeatureListNavigationAction.LoadingIndicator -> "showHUD" - is FeatureListNavigationAction.Architecture -> "showArchitecture" - is FeatureListNavigationAction.Keyboard -> "showKeyboard" - is FeatureListNavigationAction.System -> "showSystem" - is FeatureListNavigationAction.Links -> "showLinks" - is FeatureListNavigationAction.Bluetooth -> "showBluetooth" - is FeatureListNavigationAction.Beacons -> "showBeacons" - is FeatureListNavigationAction.PlatformSpecific -> "showPlatformSpecific" - is FeatureListNavigationAction.Resources -> "showResources" - } - ) - } - ) - } - - fun createInfoViewModel(parent: UIViewController): InfoViewModel { - return InfoViewModel( - ReviewManager.Builder(), - ViewControllerNavigator(parent) { action -> - when (action) { - is InfoNavigation.Dialog -> { - NavigationSpec.Present( - true, - present = { - val title = action.bundle?.get(DialogSpecRow.TitleRow) ?: "" - val message = action.bundle?.get(DialogSpecRow.MessageRow) ?: "" - UIAlertController.alertControllerWithTitle(title, message, UIAlertControllerStyleAlert).apply { - addAction(UIAlertAction.actionWithTitle("OK", UIAlertActionStyleDefault) {}) - } - } - ) - } - is InfoNavigation.Link -> NavigationSpec.Browser( - NSURL.URLWithString( - action.bundle!!.get(LinkSpecRow.LinkRow) - )!!, - NavigationSpec.Browser.Type.Normal - ) - is InfoNavigation.Mail -> NavigationSpec.Email( - NavigationSpec.Email.EmailSettings( - to = action.bundle?.get( - MailSpecRow.ToRow - ) ?: emptyList(), - subject = action.bundle?.get(MailSpecRow.SubjectRow) - ) - ) - } - } - ) - } - - fun createBluetoothListViewModel(parent: UIViewController, bluetooth: Bluetooth, createDeviceDetailsViewController: (Identifier, Bluetooth) -> UIViewController): BluetoothListViewModel { - return BluetoothListViewModel( - bluetooth, - ViewControllerNavigator(parent) { action -> - NavigationSpec.Push( - push = { - val bundle = action.bundle ?: return@Push UIViewController() - val identifier = NSUUID(uUIDString = bundle.get(DeviceDetailsSpecRow.UUIDRow)) - createDeviceDetailsViewController(identifier, bluetooth) - } - ) - } - ) - } - - fun createBluetoothDeviceDetailsViewModel(identifier: Identifier, bluetooth: Bluetooth): BluetoothDeviceDetailViewModel { - return BluetoothDeviceDetailViewModel(bluetooth, identifier) - } - - fun createBeaconsListViewModel(parent: UIViewController, service: Beacons): BeaconsListViewModel { - return BeaconsListViewModel(service) - } - fun createPermissionListViewModel(parent: UIViewController, createPermissionViewController: (Permission) -> UIViewController): PermissionsListViewModel { return PermissionsListViewModel( ViewControllerNavigator(parent) { action -> @@ -223,128 +120,9 @@ class KNArchitectureFramework { return LocationViewModel(permission, repoBuilder) } - fun createArchitectureInputViewModel(parent: UIViewController, createDetailsViewController: (InputDetails) -> UIViewController): ArchitectureInputViewModel { - return ArchitectureInputViewModel( - ViewControllerNavigator(parent) { action -> - NavigationSpec.Present( - present = { - createDetailsViewController(action.bundle?.get(action.type) ?: InputDetails("", 0)) - } - ) - } - ) - } - - fun createArchitectureDetailsViewModel(parent: UIViewController, inputDetails: InputDetails, onDismiss: (InputDetails) -> Unit): ArchitectureDetailsViewModel { - return ArchitectureDetailsViewModel( - inputDetails, - ViewControllerNavigator(parent) { action -> - NavigationSpec.Dismiss( - completion = { - onDismiss(action.bundle?.get(action.type) ?: InputDetails("", 0)) - } - ) - } - ) - } - fun createKeyboardViewModel(focusHandler: FocusHandler): KeyboardViewModel { return KeyboardViewModel(KeyboardManager.Builder(), focusHandler) } - - fun createSystemViewModel(parent: UIViewController): SystemViewModel { - return SystemViewModel( - ViewControllerNavigator(parent) { action -> - when (action) { - SystemNavigationActions.Network -> - NavigationSpec.Segue("showNetwork") - } - } - ) - } - - fun createResourcesViewModel(parent: UIViewController): ResourcesListViewModel { - return ResourcesListViewModel( - ViewControllerNavigator(parent) { action -> - NavigationSpec.Segue( - when (action) { - is ResourcesListNavigationAction.Button -> "showButton" - is ResourcesListNavigationAction.Color -> "showColor" - is ResourcesListNavigationAction.Label -> "showLabel" - } - ) - } - ) - } - - fun createButtonViewModel(parent: UIViewController) = ButtonViewModel( - StyledStringBuilder.Provider(), - AlertPresenter.Builder(parent) - ) - - fun createLabelViewModel() = LabelViewModel(StyledStringBuilder.Provider()) - - fun createColorViewModel(parent: UIViewController) = ColorViewModel(AlertPresenter.Builder(parent)) - - fun bind(viewModel: VM, to: UIViewController, onLifecycleChanges: onLifeCycleChanged): LifecycleManager { - return viewModel.addLifecycleManager(to, onLifecycleChanges) - } - - fun createLinksViewModel( - parent: UIViewController, - animated: Boolean, - completion: (() -> Unit)? = null - ): LinksViewModel { - return LinksViewModel( - LinksBuilder(), - AlertPresenter.Builder(parent), - ViewControllerNavigator(parent) { action -> - when (action) { - is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser( - NSURL.URLWithString(action.bundle!!.get(BrowserSpecRow.UrlSpecRow))!!, - NavigationSpec.Browser.Type.Normal - ) - } - } - ) - } -} - -fun ExampleViewModel.observeTabs(stackView: UIStackView, addOnPressed: (UIButton, () -> Unit) -> Unit): List { - val selectedButtonDisposeBag = DisposeBag() - return listOf( - tabs.observeInitialized { tabs -> - selectedButtonDisposeBag.dispose() - stackView.arrangedSubviews.forEach { subView -> (subView as UIView).removeFromSuperview() } - tabs.forEach { tab -> - val button = UIButton() - button.setTitle(tab.title, UIControlStateNormal) - button.setTitleColor(UIColor.systemBlueColor, UIControlStateSelected) - button.setTitleColor(UIColor.systemBlueColor, UIControlStateHighlighted) - button.setTitleColor(UIColor.grayColor, UIControlStateNormal) - this.tab.observeInitialized { selectedTab -> - button.setSelected(selectedTab == tab) - }.addTo(selectedButtonDisposeBag) - addOnPressed(button) { - this.tab.post(tab) - } - stackView.addArrangedSubview(button) - } - } - ) -} - -fun SystemViewModel.observeModules(onModuleChanged: (List, (Int) -> Unit) -> Unit): Disposable = - modules.observeInitialized { modules -> - val moduleName = modules.map { it.name } - onModuleChanged(moduleName) { onButtonTapped(modules[it]) } - } - -fun FeatureListViewModel.observeFeatures(onFeaturesChanged: (List, (Int) -> Unit) -> Unit): Disposable { - return feature.observeInitialized { features -> - val titles = features.map { feature -> feature.title } - onFeaturesChanged(titles) { index -> this.onFeaturePressed(features[index]) } - } } fun InfoViewModel.observeButtons(onInfoButtonsChanged: (List, (Int) -> Unit) -> Unit): Disposable { @@ -359,10 +137,3 @@ fun PermissionsListViewModel.observePermissions(onPermissionsChanged: (List this.onPermissionPressed(permissions[index]) } } } - -fun ResourcesListViewModel.observeResources(onResourcesChanged: (List, (Int) -> Unit) -> Unit): Disposable { - return resources.observeInitialized { features -> - val titles = features.map { feature -> feature.title } - onResourcesChanged(titles) { index -> this.onResourceSelected(features[index]) } - } -} diff --git a/newexample/android/src/main/AndroidManifest.xml b/newexample/android/src/main/AndroidManifest.xml index 15687a9f7..2cd6b95de 100644 --- a/newexample/android/src/main/AndroidManifest.xml +++ b/newexample/android/src/main/AndroidManifest.xml @@ -107,6 +107,7 @@ android:label="@string/feature_resources_button"/> + diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt index 0520e4b2f..7f2d19139 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt @@ -29,11 +29,9 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment -import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpecRow +import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpec import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel -import com.splendo.kaluga.example.shared.viewmodel.info.LinkSpecRow -import com.splendo.kaluga.example.shared.viewmodel.info.MailSpecRow import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import java.net.URL @@ -45,22 +43,20 @@ class InfoFragment : KalugaViewModelFragment(R.layout.fragment_in ActivityNavigator> { action -> when (action) { is InfoNavigation.Dialog -> { - val title = action.bundle?.get(DialogSpecRow.TitleRow) ?: "" - val message = action.bundle?.get(DialogSpecRow.MessageRow) ?: "" NavigationSpec.Dialog( createDialog = { - InfoDialog(title, message) + InfoDialog(action.value) } ) } is InfoNavigation.Link -> NavigationSpec.Browser( - URL(action.bundle!!.get(LinkSpecRow.LinkRow)), + URL(action.value), NavigationSpec.Browser.Type.Normal ) is InfoNavigation.Mail -> NavigationSpec.Email( NavigationSpec.Email.EmailSettings( - to = action.bundle?.get(MailSpecRow.ToRow) ?: emptyList(), - subject = action.bundle?.get(MailSpecRow.SubjectRow) + to = action.value.to, + subject = action.value.subject ) ) } @@ -108,7 +104,7 @@ class InfoAdapter(private val viewModel: InfoViewModel) : RecyclerView.Adapter(R.id.title).text = title - v.findViewById(R.id.message).text = message + v.findViewById(R.id.title).text = dialogSpec.title + v.findViewById(R.id.message).text = dialogSpec.message return v } diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt index b4e3f51fa..3c131451e 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt @@ -31,6 +31,7 @@ import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.databinding.ActivityArchitectureInputBinding import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails +import com.splendo.kaluga.example.shared.viewmodel.architecture.InputNavigation import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -48,7 +49,7 @@ class ArchitectureInputActivity : KalugaViewModelActivity> { + ActivityNavigator { NavigationSpec.Activity( launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } ) diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt index d07256ba2..35a75ea35 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt @@ -28,8 +28,8 @@ import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R import com.splendo.kaluga.example.databinding.ActivityBluetoothBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListNavigation import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetails import kotlinx.coroutines.runBlocking import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -38,7 +38,7 @@ class BluetoothActivity : KalugaViewModelActivity() { override val viewModel: BluetoothListViewModel by viewModel { parametersOf( - ActivityNavigator { + ActivityNavigator { NavigationSpec.Activity() } ) diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt index 22b422dfd..49ce564ef 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt @@ -18,22 +18,21 @@ package com.splendo.kaluga.example.bluetooth import android.os.Bundle -import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.bluetooth.device.SerializableIdentifier import com.splendo.kaluga.example.databinding.ActivityBluetoothMoreBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpec -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpecRow import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf class BluetoothMoreActivity : KalugaViewModelActivity() { override val viewModel: BluetoothDeviceDetailViewModel by viewModel { - val deviceDetailsSpec = DeviceDetailsSpec() - intent.extras?.toNavigationBundle(deviceDetailsSpec)?.let { bundle -> - parametersOf(bundle.get(DeviceDetailsSpecRow.UUIDRow)) - } ?: parametersOf("") + parametersOf( + intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(SerializableIdentifier.serializer()))!!.identifier + ) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt index 485777505..846c75ea4 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt @@ -25,7 +25,6 @@ import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R import com.splendo.kaluga.example.databinding.ActivityLinkBinding import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -35,10 +34,10 @@ class LinksActivity : KalugaViewModelActivity(R.layout.activity_ override val viewModel: LinksViewModel by viewModel { parametersOf( - ActivityNavigator> { + ActivityNavigator> { when (it) { is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser( - URL(it.bundle!!.get(BrowserSpecRow.UrlSpecRow)), + URL(it.value), NavigationSpec.Browser.Type.Normal ) } diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt index d7cd3d96e..27a852cd7 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt @@ -28,10 +28,8 @@ import androidx.core.app.NotificationManagerCompat import com.splendo.kaluga.example.R import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel import com.splendo.kaluga.permissions.location.LocationPermission -import org.koin.core.component.KoinComponent -import org.koin.core.component.get -class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinComponent { +class LocationBackgroundService : androidx.lifecycle.LifecycleService() { companion object { const val notificationId = 1 @@ -43,7 +41,7 @@ class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinCom private val notificationService by lazy { applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } - private val viewModel = LocationViewModel(permission, get()) + private val viewModel = LocationViewModel(permission) override fun onCreate() { super.onCreate() diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt index ca92bc4cd..aee8b70c2 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt @@ -22,11 +22,10 @@ import android.view.View import android.widget.TextView import android.widget.Toast import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpec -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpecRow import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission @@ -43,29 +42,15 @@ import org.koin.core.parameter.parametersOf class PermissionsDemoActivity : KalugaViewModelActivity(R.layout.activity_permissions_demo) { override val viewModel: PermissionViewModel by viewModel { - val permissionNavSpec = PermissionNavigationBundleSpec() - intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle -> - val permission = when (bundle.get(PermissionNavigationBundleSpecRow)) { - PermissionView.Bluetooth -> BluetoothPermission - PermissionView.Calendar -> CalendarPermission(allowWrite = true) - PermissionView.Camera -> CameraPermission - PermissionView.Contacts -> ContactsPermission(allowWrite = true) - PermissionView.Location -> LocationPermission(background = true, precise = true) - PermissionView.Microphone -> MicrophonePermission - PermissionView.Notifications -> NotificationsPermission() - PermissionView.Storage -> StoragePermission(allowWrite = true) - } - parametersOf(permission) + intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.let { permissionView -> + parametersOf(permissionView.permission) } ?: parametersOf() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val permissionNavSpec = PermissionNavigationBundleSpec() - intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle -> - supportActionBar?.title = bundle.get(PermissionNavigationBundleSpecRow).title - } + supportActionBar?.title = intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.title viewModel.permissionStateMessage.observe { findViewById(R.id.permissions_message).text = it diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt similarity index 65% rename from newexample/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt rename to newexample/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt index 3535caea9..51d96e51f 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt @@ -15,30 +15,25 @@ */ -package com.splendo.kaluga.example.system.fragments +package com.splendo.kaluga.example.system import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.databinding.FragmentNetworkBinding +import com.splendo.kaluga.example.databinding.ActivityNetworkBinding import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel import org.koin.androidx.viewmodel.ext.android.viewModel -class NetworkFragment : KalugaViewModelFragment(R.layout.fragment_network) { +class NetworkActivity : KalugaViewModelActivity(R.layout.activity_network) { override val viewModel: NetworkViewModel by viewModel() - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val binding = FragmentNetworkBinding.inflate(inflater) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val binding = ActivityNetworkBinding.inflate(layoutInflater, null, false) binding.lifecycleOwner = this binding.viewModel = viewModel - return binding.root + setContentView(binding.root) } -} + +} \ No newline at end of file diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt index 293f385b3..4a1a63290 100644 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt +++ b/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt @@ -20,29 +20,24 @@ package com.splendo.kaluga.example.system import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup -import android.widget.Button -import androidx.appcompat.widget.AppCompatButton import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ViewListButtonBinding import com.splendo.kaluga.example.shared.viewmodel.system.SystemFeatures import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel -import com.splendo.kaluga.example.system.fragments.NetworkFragment import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf class SystemActivity : KalugaViewModelActivity(R.layout.activity_system) { override val viewModel: SystemViewModel by viewModel { parametersOf( - ActivityNavigator> { action -> + ActivityNavigator { action -> when (action) { - SystemNavigationActions.Network -> NavigationSpec.Fragment( - R.id.system_features_fragment, - createFragment = { NetworkFragment() } - ) + SystemNavigationActions.Network -> NavigationSpec.Activity() } } ) @@ -65,7 +60,9 @@ class SystemFeatureAdapter( private val viewModel: SystemViewModel ) : RecyclerView.Adapter() { - inner class SystemFeatureViewHolder(val button: Button) : RecyclerView.ViewHolder(button) + inner class SystemFeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { + val button = binding.button + } var modules: List = emptyList() set(newValue) { @@ -74,8 +71,8 @@ class SystemFeatureAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SystemFeatureViewHolder { - val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton - return SystemFeatureViewHolder(button) + val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SystemFeatureViewHolder(binding) } override fun onBindViewHolder(holder: SystemFeatureViewHolder, position: Int) { diff --git a/newexample/android/src/main/res/layout/fragment_network.xml b/newexample/android/src/main/res/layout/activity_network.xml similarity index 100% rename from newexample/android/src/main/res/layout/fragment_network.xml rename to newexample/android/src/main/res/layout/activity_network.xml diff --git a/newexample/android/src/main/res/layout/activity_system.xml b/newexample/android/src/main/res/layout/activity_system.xml index bfd4af551..77a494611 100644 --- a/newexample/android/src/main/res/layout/activity_system.xml +++ b/newexample/android/src/main/res/layout/activity_system.xml @@ -24,19 +24,10 @@ android:layout_gravity="center_horizontal" android:layout_margin="@dimen/networkLayoutsMargin"> - - - - - + android:layout_height="wrap_content" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> \ No newline at end of file diff --git a/newexample/gradle.properties b/newexample/gradle.properties index da6d54955..938da4248 100644 --- a/newexample/gradle.properties +++ b/newexample/gradle.properties @@ -1,5 +1,5 @@ #Gradle -org.gradle.jvmargs=-Xmx3048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx3048M" +org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4096M" #Kotlin kotlin.code.style=official diff --git a/newexample/ios/Demo.xcodeproj/project.pbxproj b/newexample/ios/Demo.xcodeproj/project.pbxproj index 6d30214da..3a1573fc1 100644 --- a/newexample/ios/Demo.xcodeproj/project.pbxproj +++ b/newexample/ios/Demo.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 7478297FCF44ED1D49525D4E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74FDC8922442294AF0776058 /* LaunchScreen.storyboard */; }; 749B42F84CA72A5D4A0F3AD9 /* DemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741DDADC0C8034E32EA9C3D6 /* DemoUITests.swift */; }; 74C75886D08DD78EA65E6DD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 74C9C443CC304CB387AB324D /* Assets.xcassets */; }; - 74C9929AB26988E8CC66893D /* gradle.properties in Resources */ = {isa = PBXBuildFile; fileRef = 74F1F3223C9CF11420F72B08 /* gradle.properties */; }; 74D656BE6B4BD50115400411 /* DemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 744CF99B70DA1947AA98CA3B /* DemoTests.swift */; }; 74E6599B023623BD96737582 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74F483E9C1890D693F808DDA /* Main.storyboard */; }; 978348A7235DF586005B140B /* PermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978348A6235DF586005B140B /* PermissionViewController.swift */; }; @@ -102,27 +101,18 @@ 649979D225CC638C00348419 /* SystemViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemViewController.swift; sourceTree = ""; }; 649979E625CCD37200348419 /* NetworkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkViewController.swift; sourceTree = ""; }; 740C363F8973304E72F429BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; - 741672C742FA38B5D4971C10 /* gradlew */ = {isa = PBXFileReference; lastKnownFileType = text; path = gradlew; sourceTree = ""; }; - 741A9B938B05534BD1B508C6 /* settings.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = settings.gradle.kts; sourceTree = ""; }; 741DDADC0C8034E32EA9C3D6 /* DemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoUITests.swift; sourceTree = ""; }; - 741E82A1DDEE399E80A02B54 /* gradle-wrapper.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = "gradle-wrapper.jar"; sourceTree = ""; }; 7428D8755D288FD7EA2E6537 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 742CCE9033CEC8C8004D624F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 7433F67D6FEF093F4C827A5A /* build.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = build.gradle.kts; sourceTree = ""; }; 744CF99B70DA1947AA98CA3B /* DemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoTests.swift; sourceTree = ""; }; - 744F12775216AF134CBAF4E9 /* build.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = build.gradle.kts; sourceTree = ""; }; 746834742E284396FD5452F4 /* DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 746E133CAEC5C7B1D516B11F /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74A0718952D0CC604E49709B /* DemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 74B0C3AB70AAB7878B48F6B1 /* gradle-wrapper.properties */ = {isa = PBXFileReference; lastKnownFileType = file.properties; path = "gradle-wrapper.properties"; sourceTree = ""; }; 74BBBF2C57521E71982179E0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 74BEC2BADB02749FD409313B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 74C07E8A2DB465B1CE47DC5E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 74C22623993814F606777266 /* KotlinNativeFramework.kt */ = {isa = PBXFileReference; lastKnownFileType = file.kt; path = KotlinNativeFramework.kt; sourceTree = ""; }; 74C710A1E2E41AF0F4A9FFC5 /* LocationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = ""; }; 74C9C443CC304CB387AB324D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 74E253D6EF4BB7BA4916943D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; - 74F1F3223C9CF11420F72B08 /* gradle.properties */ = {isa = PBXFileReference; lastKnownFileType = file.properties; path = gradle.properties; sourceTree = ""; }; 978348A6235DF586005B140B /* PermissionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -288,23 +278,6 @@ path = Network; sourceTree = ""; }; - 743414FDF97167BD81A5293E /* wrapper */ = { - isa = PBXGroup; - children = ( - 74B0C3AB70AAB7878B48F6B1 /* gradle-wrapper.properties */, - 741E82A1DDEE399E80A02B54 /* gradle-wrapper.jar */, - ); - path = wrapper; - sourceTree = ""; - }; - 743EA5EA3B99CA8E01574460 /* KotlinNativeFrameworkMain */ = { - isa = PBXGroup; - children = ( - 7470228E28317355EFB70D41 /* kotlin */, - ); - path = KotlinNativeFrameworkMain; - sourceTree = ""; - }; 745520950D85E4696686141D = { isa = PBXGroup; children = ( @@ -313,37 +286,7 @@ 746CAD9D15ECB24F4ED84E11 /* Demo */, 7492DE0914511F3DC96A30A8 /* DemoTests */, 74D6698E6E24A4F93E6B289A /* DemoUITests */, - 747F646D72854D37D5686909 /* KotlinNativeFramework */, - 74588EBE65D1E22D05BFD2A5 /* Supporting Files */, - ); - sourceTree = ""; - }; - 745778F955B940F5CB818364 /* gradle */ = { - isa = PBXGroup; - children = ( - 743414FDF97167BD81A5293E /* wrapper */, - ); - path = gradle; - sourceTree = ""; - }; - 74588EBE65D1E22D05BFD2A5 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 7433F67D6FEF093F4C827A5A /* build.gradle.kts */, - 741672C742FA38B5D4971C10 /* gradlew */, - 745778F955B940F5CB818364 /* gradle */, - 741A9B938B05534BD1B508C6 /* settings.gradle.kts */, - 74F1F3223C9CF11420F72B08 /* gradle.properties */, - ); - path = "Supporting Files"; - sourceTree = ""; - }; - 745DD28172357B2F37C397E8 /* src */ = { - isa = PBXGroup; - children = ( - 743EA5EA3B99CA8E01574460 /* KotlinNativeFrameworkMain */, ); - path = src; sourceTree = ""; }; 746CAD9D15ECB24F4ED84E11 /* Demo */ = { @@ -376,14 +319,6 @@ path = Demo; sourceTree = ""; }; - 7470228E28317355EFB70D41 /* kotlin */ = { - isa = PBXGroup; - children = ( - 74C22623993814F606777266 /* KotlinNativeFramework.kt */, - ); - path = kotlin; - sourceTree = ""; - }; 747A549D9FF0D1B49E65E190 /* Products */ = { isa = PBXGroup; children = ( @@ -394,16 +329,6 @@ name = Products; sourceTree = ""; }; - 747F646D72854D37D5686909 /* KotlinNativeFramework */ = { - isa = PBXGroup; - children = ( - 74E253D6EF4BB7BA4916943D /* Info.plist */, - 74ED9583BC95062A511A899E /* Supporting Files */, - 745DD28172357B2F37C397E8 /* src */, - ); - path = KotlinNativeFramework; - sourceTree = ""; - }; 7492DE0914511F3DC96A30A8 /* DemoTests */ = { isa = PBXGroup; children = ( @@ -422,14 +347,6 @@ path = DemoUITests; sourceTree = ""; }; - 74ED9583BC95062A511A899E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 744F12775216AF134CBAF4E9 /* build.gradle.kts */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; 978348A5235DF51D005B140B /* Permissions */ = { isa = PBXGroup; children = ( @@ -550,7 +467,6 @@ 7478297FCF44ED1D49525D4E /* LaunchScreen.storyboard in Resources */, 74E6599B023623BD96737582 /* Main.storyboard in Resources */, 209471EA24C07F5F001426CD /* BluetoothDescriptorCell.xib in Resources */, - 74C9929AB26988E8CC66893D /* gradle.properties in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -580,7 +496,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd \"$SRCROOT/Supporting Files\"\n./gradlew :KotlinNativeFramework:embedAndSignAppleFrameworkForXcode\n"; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -756,10 +672,16 @@ DEVELOPMENT_TEAM = 2732HQPVJP; FRAMEWORK_SEARCH_PATHS = ( $BUILT_PRODUCTS_DIR, - "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Demo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + KalugaExampleShared, + ); PRODUCT_BUNDLE_IDENTIFIER = com.splendo.kaluga.example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Kaluga example"; @@ -882,10 +804,16 @@ DEVELOPMENT_TEAM = 2732HQPVJP; FRAMEWORK_SEARCH_PATHS = ( $BUILT_PRODUCTS_DIR, - "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Demo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + KalugaExampleShared, + ); PRODUCT_BUNDLE_IDENTIFIER = com.splendo.kaluga.example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Kaluga example"; diff --git a/newexample/ios/Demo/Alerts/AlertsViewController.swift b/newexample/ios/Demo/Alerts/AlertsViewController.swift index 743ea8f06..96e81b313 100644 --- a/newexample/ios/Demo/Alerts/AlertsViewController.swift +++ b/newexample/ios/Demo/Alerts/AlertsViewController.swift @@ -17,21 +17,21 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class AlertsViewController: UITableViewController { private lazy var viewModel = AlertViewModel(builder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { return [] } + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/newexample/ios/Demo/AppDelegate.swift b/newexample/ios/Demo/AppDelegate.swift index b1b416e18..cefdc285e 100644 --- a/newexample/ios/Demo/AppDelegate.swift +++ b/newexample/ios/Demo/AppDelegate.swift @@ -17,6 +17,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ import UIKit +import KalugaExampleShared @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -24,6 +25,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + DependencyInjectionKt.doInitKoin(customModules: []) return true } @@ -43,7 +45,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let vc = storyboard.instantiateViewController(withIdentifier: "LinksViewController") as! LinksViewController self.window?.rootViewController = vc - vc.handleIncomingLink(url: stringUrl) +// vc.handleIncomingLink(url: stringUrl) return true } diff --git a/newexample/ios/Demo/Architecture/ArchitectureDetailsViewController.swift b/newexample/ios/Demo/Architecture/ArchitectureDetailsViewController.swift index 577b793ef..cc6b9fcfc 100644 --- a/newexample/ios/Demo/Architecture/ArchitectureDetailsViewController.swift +++ b/newexample/ios/Demo/Architecture/ArchitectureDetailsViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ArchitectureDetailsViewController: UIViewController { @@ -30,12 +30,15 @@ class ArchitectureDetailsViewController: UIViewController { if #available(iOS 13.0, *) { vc.isModalInPresentation = true } - vc.viewModel = KNArchitectureFramework().createArchitectureDetailsViewModel(parent: vc, inputDetails: inputDetails) { inputDetails in - onDismiss(inputDetails) + let navigator = ViewControllerNavigator(parentVC: vc) { action in + NavigationSpec.Dismiss(toDismiss: { $0 }, animated: true) { + onDismiss(action.value!) + } } + vc.viewModel = ArchitectureDetailsViewModel(initialDetail: inputDetails, navigator: navigator) return vc } - + var viewModel: ArchitectureDetailsViewModel! private var lifecycleManager: LifecycleManager! @@ -47,16 +50,16 @@ class ArchitectureDetailsViewController: UIViewController { deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - + return [ viewModel.name.observe { name in self?.nameLabel.text = name as? String ?? "" @@ -67,11 +70,11 @@ class ArchitectureDetailsViewController: UIViewController { ] } } - + @objc @IBAction func onInversePressed(sender: Any?) { viewModel.onInversePressed() } - + @objc @IBAction func onCloseButtonPressed(sender: Any?) { viewModel.onClosePressed() } diff --git a/newexample/ios/Demo/Architecture/ArchitectureInputViewController.swift b/newexample/ios/Demo/Architecture/ArchitectureInputViewController.swift index d9762d316..9317cd640 100644 --- a/newexample/ios/Demo/Architecture/ArchitectureInputViewController.swift +++ b/newexample/ios/Demo/Architecture/ArchitectureInputViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ArchitectureInputViewController: UIViewController { @@ -29,27 +29,30 @@ class ArchitectureInputViewController: UIViewController { @IBOutlet weak var numberError: UIImageView! @IBOutlet weak var detailsButton: UIButton! - - lazy var viewModel = KNArchitectureFramework().createArchitectureInputViewModel(parent: self) { [weak self] inputDetails in - return ArchitectureDetailsViewController.create(inputDetails: inputDetails) { [weak self] inputDetails in - self?.onDetailsDismissed(inputDetails: inputDetails) + + lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Present(animated: true, presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue)) { + ArchitectureDetailsViewController.create(inputDetails: action.value!) { [weak self] inputDetails in + self?.onDetailsDismissed(inputDetails: inputDetails) + } } } + lazy var viewModel = ArchitectureInputViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - + return [ viewModel.nameHeader.observeInitialized { header in self?.nameLabel.text = header as String? @@ -72,30 +75,30 @@ class ArchitectureInputViewController: UIViewController { ] } } - + @objc @IBAction func onShowDetailsPressed(sender: Any?) { viewModel.onShowDetailsPressed() } - + private func onDetailsDismissed(inputDetails: InputDetails) { viewModel.nameInput.post(newValue: NSString(string: inputDetails.name)) viewModel.numberInput.post(newValue: NSString(string: "\(inputDetails.number)")) } - + } extension ArchitectureInputViewController : UITextFieldDelegate { - + func textFieldDidEndEditing(_ textField: UITextField) { postInput(text: textField.text ?? "", fromTextField: textField) } - + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let resultingString = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) postInput(text: resultingString, fromTextField: textField) return false } - + private func postInput(text: String, fromTextField textField: UITextField) { if (textField == nameInput) { viewModel.nameInput.post(newValue: NSString(string: text)) diff --git a/newexample/ios/Demo/Beacons/BeaconsViewCell.swift b/newexample/ios/Demo/Beacons/BeaconsViewCell.swift index 829f8565d..928f6e859 100644 --- a/newexample/ios/Demo/Beacons/BeaconsViewCell.swift +++ b/newexample/ios/Demo/Beacons/BeaconsViewCell.swift @@ -15,7 +15,7 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class BeaconsViewCell: UICollectionViewCell { diff --git a/newexample/ios/Demo/Beacons/BeaconsViewController.swift b/newexample/ios/Demo/Beacons/BeaconsViewController.swift index 678e5786a..653f7c54a 100644 --- a/newexample/ios/Demo/Beacons/BeaconsViewController.swift +++ b/newexample/ios/Demo/Beacons/BeaconsViewController.swift @@ -15,7 +15,7 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class BeaconsViewController: UICollectionViewController { @@ -35,11 +35,7 @@ class BeaconsViewController: UICollectionViewController { return flowLayout }() - private lazy var viewModel = KNArchitectureFramework() - .createBeaconsListViewModel( - parent: self, - service: KNBeaconsFramework().service - ) + private lazy var viewModel = BeaconsListViewModel() deinit { lifecycleManager.unbind() @@ -54,7 +50,7 @@ class BeaconsViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ diff --git a/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift b/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift index e6b178457..cb593bf85 100644 --- a/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift +++ b/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift @@ -16,24 +16,24 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { +class BluetoothDeviceDetailsViewController : UIViewController { struct Const { static let storyboard = UIStoryboard(name: "Main", bundle: nil) static let storyboardId = "BluetoothDeviceDetails" } - static func create(deviceUuid: UUID, bluetooth: Bluetooth) -> BluetoothDeviceDetailsViewController { + static func create(identifier: UUID) -> BluetoothDeviceDetailsViewController { let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! BluetoothDeviceDetailsViewController if #available(iOS 13.0, *) { vc.isModalInPresentation = true } - vc.viewModel = KNArchitectureFramework().createBluetoothDeviceDetailsViewModel(identifier: deviceUuid, bluetooth: bluetooth) + vc.viewModel = BluetoothDeviceDetailViewModel(identifier: identifier) return vc } - + var viewModel: BluetoothDeviceDetailViewModel! private var lifecycleManager: LifecycleManager! @@ -46,29 +46,29 @@ class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewD @IBOutlet var servicesList: UICollectionView! private var services: [BluetoothServiceViewModel] = [] - + private var isInvalidating: Bool = false - + deinit { lifecycleManager.unbind() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + servicesHeader.text = NSLocalizedString("bluetooth_services_header", comment: "") deviceIdentifier.text = viewModel.identifierString - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) flowLayout.minimumLineSpacing = 4 servicesList.collectionViewLayout = flowLayout - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in - + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return []} - + return [ viewModel.name.observe { name in self?.deviceName.text = name as String? @@ -92,32 +92,32 @@ class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewD self?.servicesList.layoutIfNeeded() } ] - }) + } } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return services.count } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let serviceCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothServiceView.Companion.identifier, for: indexPath) as! BluetoothServiceView serviceCell.parent = self serviceCell.service = services[indexPath.row] return serviceCell } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothServiceView)?.startMonitoring() } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothServiceView)?.stopMonitoring() } } - + fileprivate func updateListSize() { isInvalidating = true servicesList.collectionViewLayout.invalidateLayout() @@ -146,13 +146,13 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo @IBOutlet var characteristicsListHeight: NSLayoutConstraint! private var characteristics: [BluetoothCharacteristicViewModel] = [] - + override func awakeFromNib() { super.awakeFromNib() - + serviceHeader.text = NSLocalizedString("bluetooth_service", comment: "") characteristicsHeader.text = NSLocalizedString("bluetooth_characteristics", comment: "") - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) @@ -162,14 +162,14 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo characteristicsList.delegate = self characteristicsList.register(UINib(nibName: "BluetoothCharacteristicCell", bundle: nil), forCellWithReuseIdentifier: BluetoothCharacteristicView.Companion.identifier) } - + fileprivate func startMonitoring() { disposeBag.dispose() guard let service = self.service else { return } service.didResume() - + serviceIdentifier.text = service.uuid service.characteristics.observe { [weak self] characteristics in self?.characteristics = characteristics as? [BluetoothCharacteristicViewModel] ?? [] @@ -177,12 +177,12 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo self?.updateListSize(isInvalidating: false) }.addTo(disposeBag: disposeBag) } - + fileprivate func stopMonitoring() { service?.didPause() disposeBag.dispose() } - + fileprivate func updateListSize(isInvalidating: Bool) { self.isInvalidating = isInvalidating characteristicsList.collectionViewLayout.invalidateLayout() @@ -192,30 +192,30 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo parent?.updateListSize() self.isInvalidating = false } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return characteristics.count } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let characteristicCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCharacteristicView.Companion.identifier, for: indexPath) as! BluetoothCharacteristicView characteristicCell.parent = self characteristicCell.characteristic = characteristics[indexPath.row] return characteristicCell } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothCharacteristicView)?.startMonitoring() } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothCharacteristicView)?.stopMonitoring() } } - + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) layoutIfNeeded() @@ -225,7 +225,7 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo } -class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource { +class BluetoothCharacteristicView : UICollectionViewCell { fileprivate struct Companion { static let identifier = "BluetoothCharacteristicView" @@ -234,7 +234,7 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega fileprivate weak var parent: BluetoothServiceView? fileprivate var characteristic: BluetoothCharacteristicViewModel? private let disposeBag = DisposeBag() - + private var isInvalidating: Bool = false @IBOutlet var characteristicIdentifier: UILabel! @@ -245,12 +245,12 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega @IBOutlet var descriptorsListHeight: NSLayoutConstraint! private var descriptors: [BluetoothDescriptorViewModel] = [] - + override func awakeFromNib() { super.awakeFromNib() - + descriptorsHeader.text = NSLocalizedString("bluetooth_descriptors", comment: "") - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) @@ -258,31 +258,31 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega descriptorsList.collectionViewLayout = flowLayout descriptorsList.register(UINib(nibName: "BluetoothDescriptorCell", bundle: nil), forCellWithReuseIdentifier: BluetoothDescriptorView.Companion.identifier) } - + fileprivate func startMonitoring() { disposeBag.dispose() guard let characteristic = self.characteristic else { return } characteristic.didResume() - + characteristicIdentifier.text = characteristic.uuid characteristic.descriptors.observe { [weak self] descriptors in self?.descriptors = descriptors as? [BluetoothDescriptorViewModel] ?? [] self?.descriptorsList.reloadData() self?.updateListSize(isInvalidating: false) }.addTo(disposeBag: disposeBag) - + characteristic.value.observe { [weak self] value in self?.characteristicValue.text = value as String? }.addTo(disposeBag: disposeBag) } - + fileprivate func stopMonitoring() { disposeBag.dispose() characteristic?.didPause() } - + private func updateListSize(isInvalidating: Bool) { self.isInvalidating = isInvalidating descriptorsList.collectionViewLayout.invalidateLayout() @@ -292,36 +292,35 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega parent?.updateListSize(isInvalidating: true) self.isInvalidating = false } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return descriptors.count } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let descriptorCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothDescriptorView.Companion.identifier, for: indexPath) as! BluetoothDescriptorView descriptorCell.descriptor = descriptors[indexPath.row] return descriptorCell } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothDescriptorView)?.startMonitoring() } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothDescriptorView)?.stopMonitoring() } } - + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) layoutIfNeeded() layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) return layoutAttributes } - } class BluetoothDescriptorView : UICollectionViewCell { @@ -342,19 +341,19 @@ class BluetoothDescriptorView : UICollectionViewCell { return } descriptor.didResume() - + descriptorIdentifier.text = descriptor.uuid - + descriptor.value.observe { [weak self] value in self?.descriptorValue.text = value as String? }.addTo(disposeBag: disposeBag) } - + fileprivate func stopMonitoring() { disposeBag.dispose() descriptor?.didResume() } - + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) layoutIfNeeded() diff --git a/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift b/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift index 1e60a04b4..82903a4b1 100644 --- a/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift +++ b/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift @@ -16,14 +16,17 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class BluetoothViewController : UICollectionViewController { - - lazy var viewModel = KNArchitectureFramework().createBluetoothListViewModel(parent: self, bluetooth: KNBluetoothFramework().bluetooth) { uuid, bluetooth in - return BluetoothDeviceDetailsViewController.create(deviceUuid: uuid, bluetooth: bluetooth) + + lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Push(animated: true) { + BluetoothDeviceDetailsViewController.create(identifier: action.value!.identifier) + } } - + lazy var viewModel = BluetoothListViewModel(navigator: navigator) + private var devices: [BluetoothListDeviceViewModel] = [] private var lifecycleManager: LifecycleManager! @@ -33,20 +36,20 @@ class BluetoothViewController : UICollectionViewController { override func awakeFromNib() { super.awakeFromNib() - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) flowLayout.minimumLineSpacing = 4 collectionView.collectionViewLayout = flowLayout } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - + return [ viewModel.isScanning.observe { isScanning in self?.updateNavigationItem(isScanning: isScanning as? Bool ?? false) @@ -60,9 +63,9 @@ class BluetoothViewController : UICollectionViewController { self?.updateTitle(title: title as String?) } ] - }) + } } - + private func updateNavigationItem(isScanning: Bool) { if (isScanning) { self.navigationItem.setRightBarButton(UIBarButtonItem(title: NSLocalizedString("bluetooth_stop_scanning", comment: ""), style: .plain, target: self, action: #selector(self.toggleScanning)), animated: true) @@ -74,41 +77,41 @@ class BluetoothViewController : UICollectionViewController { private func updateTitle(title: String?) { self.title = title } - + @objc private func toggleScanning() { viewModel.onScanPressed() } - + override func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } - + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { devices.count } - + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let bluetoothCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCell.Companion.identifier, for: indexPath) as! BluetoothCell bluetoothCell.device = devices[indexPath.row] return bluetoothCell } - + override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard let btCell = cell as? BluetoothCell else { return } - + btCell.startMonitoring() } - + override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard let btCell = cell as? BluetoothCell else { return } - + btCell.stopMonitoring() } - + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { devices[indexPath.row].toggleFoldOut() let context = UICollectionViewFlowLayoutInvalidationContext() @@ -143,19 +146,19 @@ class BluetoothCell: UICollectionViewCell { @IBOutlet var moreButton: UIButton! fileprivate var device: BluetoothListDeviceViewModel? = nil - + @IBAction func onConnectPressed() { device?.onConnectPressed() } - + @IBAction func onDisonnectPressed() { device?.onDisconnectPressed() } - + @IBAction func onMorePressed() { device?.onMorePressed() } - + func startMonitoring() { disposeBag.dispose() guard let device = device else { @@ -222,7 +225,7 @@ class BluetoothCell: UICollectionViewCell { self?.manufacturerData.text = manufacturerData as? String } } - + func stopMonitoring() { disposeBag.dispose() device?.didPause() diff --git a/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift b/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift index d32da17ed..9ee2d520e 100644 --- a/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift +++ b/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class DateTimePickerViewController : UIViewController { @@ -24,33 +24,31 @@ class DateTimePickerViewController : UIViewController { lazy var viewModel = DateTimePickerViewModel(dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ - viewModel.dateLabel.observe(onNext: { (time) in - if let timeString = (time as? String) { - self?.timeLabel.text = String(timeString) - } - }) + viewModel.dateLabel.observe { (time) in + self?.timeLabel.text = time as? String + } ] - }) + } } - + @IBAction func selectDatePressed() { viewModel.onSelectDatePressed() } - + @IBAction func selectTimePressed() { viewModel.onSelectTimePressed() diff --git a/newexample/ios/Demo/ExampleViewController.swift b/newexample/ios/Demo/ExampleViewController.swift index 6b0be8f80..d6ad2d207 100644 --- a/newexample/ios/Demo/ExampleViewController.swift +++ b/newexample/ios/Demo/ExampleViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ExampleViewController : UIViewController { @@ -31,26 +31,55 @@ class ExampleViewController : UIViewController { lazy var featuresListController = Const.storyboard.instantiateViewController(withIdentifier: Const.featuresList) as! FeaturesListViewController lazy var infoViewController = Const.storyboard.instantiateViewController(withIdentifier: Const.infoView) as! InfoViewController - - lazy var viewModel: ExampleViewModel = KNArchitectureFramework().createExampleViewModel(parent: self, - containerView: containerView, - featuresList: { self.featuresListController }, - info: { self.infoViewController }) - + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Nested(type: NavigationSpec.NestedTypeReplace(tag: 1), containerView: self.containerView) { + switch action { + case is ExampleTabNavigation.FeatureList: return self.featuresListController + case is ExampleTabNavigation.Info: return self.infoViewController + default: return UIViewController() + } + } + } + lazy var viewModel: ExampleViewModel = ExampleViewModel(navigator: navigator) var lifecycleManager: LifecycleManager! - + let selectedButtonDisposeBag = DisposeBag() + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel, let bottomView = self?.bottomView else { return [] } - return viewModel.observeTabs(stackView: bottomView) { (button: UIButton, action: @escaping () -> KotlinUnit) in - button.addAction { let _ = action() } - } + + return [ + viewModel.tabs.observeInitialized { tabs in + guard let disposeBag = self?.selectedButtonDisposeBag else { return } + disposeBag.dispose() + let tabs = tabs ?? [] + bottomView.arrangedSubviews.forEach { $0.removeFromSuperview() } + for tab in tabs { + guard let tab = tab as? ExampleViewModel.Tab else { return } + let button = UIButton() + button.setTitle(tab.title, for: .normal) + button.setTitleColor(UIColor.systemBlue, for: .selected) + button.setTitleColor(UIColor.systemBlue, for: .highlighted) + button.setTitleColor(UIColor.gray, for: .normal) + + viewModel.tab.observeInitialized { selectedTab in + button.isSelected = selectedTab == tab + }.addTo(disposeBag: disposeBag) + button.addAction { + viewModel.tab.post(newValue: tab) + } + bottomView.addArrangedSubview(button) + } + + } + ] } } diff --git a/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift b/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift index 1f6f19df9..e520eaa27 100644 --- a/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift +++ b/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift @@ -16,49 +16,55 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class FeaturesListViewController : UITableViewController { - - private lazy var viewModel: FeatureListViewModel = KNArchitectureFramework().createFeatureListViewModel(parent: self) + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Segue(identifier: action.segueKey) + } + private lazy var viewModel: FeatureListViewModel = FeatureListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! private var features = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? = nil + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.observeFeatures() { (features: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.features = features - self?.onSelected = onSelected + return [viewModel.feature.observeInitialized { next in + let features = next ?? [] + self?.features = features.map { ($0 as! Feature).title } + self?.onSelected = { (index: Int) in + viewModel.onFeaturePressed(feature: features[index] as! Feature) + } self?.tableView.reloadData() }] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return features.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: FeaturesListCell.Const.identifier, for: indexPath) as! FeaturesListCell cell.label.text = features[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } @@ -73,3 +79,26 @@ class FeaturesListCell : UITableViewCell { @IBOutlet weak var label: UILabel! } + +private extension FeatureListNavigationAction { + var segueKey: String { + get { + switch self { + case is FeatureListNavigationAction.Alerts: return "showAlerts" + case is FeatureListNavigationAction.Architecture: return "showArchitecture" + case is FeatureListNavigationAction.Beacons: return "showBeacons" + case is FeatureListNavigationAction.Bluetooth: return "showBluetooth" + case is FeatureListNavigationAction.DateTimePicker: return "showDateTimePicker" + case is FeatureListNavigationAction.Keyboard: return "showKeyboard" + case is FeatureListNavigationAction.Links: return "showLinks" + case is FeatureListNavigationAction.LoadingIndicator: return "showHUD" + case is FeatureListNavigationAction.Location: return "showLocation" + case is FeatureListNavigationAction.Permissions: return "showPermissions" + case is FeatureListNavigationAction.PlatformSpecific: return "showPlatformSpecific" + case is FeatureListNavigationAction.Resources: return "showResources" + case is FeatureListNavigationAction.System: return "showSystem" + default: return "" + } + } + } +} diff --git a/newexample/ios/Demo/Info/InfoViewController.swift b/newexample/ios/Demo/Info/InfoViewController.swift index e0f6825a8..a2f18dc06 100644 --- a/newexample/ios/Demo/Info/InfoViewController.swift +++ b/newexample/ios/Demo/Info/InfoViewController.swift @@ -16,53 +16,78 @@ */ import UIKit -import KotlinNativeFramework +import MessageUI +import KalugaExampleShared class InfoViewController : UITableViewController { - - private lazy var viewModel: InfoViewModel = KNArchitectureFramework().createInfoViewModel(parent: self) + + private lazy var navigator = InfoNavigatorKt.InfoNavigator( + parent: self, + onDialogSpec: { dialogSpec in + NavigationSpec.Present( + animated: true, + presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), + transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue) + ) { + let alert = UIAlertController.init(title: dialogSpec.title, message: dialogSpec.message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + return alert + } + }, + onLink: { link in NavigationSpec.Browser(url: URL(string: link)!, viewType: NavigationSpec.BrowserTypeNormal()) }, + onMailSpec: { mailSpec in + let settings = NavigationSpec.Email.EmailEmailSettings(type: NavigationSpec.EmailTypePlain(), to: mailSpec.to, cc: [], bcc: [], subject: mailSpec.subject, body: nil, attachments: []) + return NavigationSpec.Email(emailSettings: settings, delegate: nil, animated: true) + } + ) + private lazy var viewModel: InfoViewModel = InfoViewModel( + reviewManagerBuilder: ReviewManager.Builder(), + navigator: navigator) private var buttons = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil + private var onSelected: ((Int) -> Void)? = nil private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - - return [viewModel.observeButtons() { (buttons: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.buttons = buttons - self?.onSelected = onSelected - self?.tableView.reloadData() - }] + + return [ + viewModel.buttons.observeInitialized { next in + let buttons = next?.compactMap { $0 as? InfoViewModel.Button } ?? [] + self?.buttons = buttons.map { $0.title } + self?.onSelected = { (index: Int) in viewModel.onButtonPressed(button: buttons[index]) } + self?.tableView.reloadData() + } + ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return buttons.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: InfoButtonCell.Const.identifier, for: indexPath) as! InfoButtonCell cell.label.text = buttons[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } diff --git a/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift b/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift index 3b973ab9b..8e0d31743 100644 --- a/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift +++ b/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class KeyboardManagerViewController : UIViewController { @@ -25,24 +25,24 @@ class KeyboardManagerViewController : UIViewController { private lazy var editFieldFocusHandler = { return UIKitFocusHandler(view: self.editField) }() - lazy var viewModel = KNArchitectureFramework().createKeyboardViewModel(focusHandler: self.editFieldFocusHandler) + lazy var viewModel = KeyboardViewModel(keyboardManagerBuilder: KeyboardManager.Builder(application: UIApplication.shared), editFieldFocusHandler: editFieldFocusHandler) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { return [] }) + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } } - + @IBAction func showButtonPressed() { viewModel.onShowPressed() } - + @IBAction func hideButtonPressed() { viewModel.onHidePressed() diff --git a/newexample/ios/Demo/Links/LinksViewController.swift b/newexample/ios/Demo/Links/LinksViewController.swift index 9f14309e3..34ab58446 100644 --- a/newexample/ios/Demo/Links/LinksViewController.swift +++ b/newexample/ios/Demo/Links/LinksViewController.swift @@ -17,17 +17,15 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared class LinksViewController : UIViewController { @IBOutlet weak var browserButton: UIButton! @IBOutlet weak var instructionsText: UILabel! - - private let knArchitectureFramework = KNArchitectureFramework() - private lazy var viewModel: LinksViewModel = { - return knArchitectureFramework.createLinksViewModel(parent: self, animated: true, completion: nil) - }() + + private lazy var navigator: ViewControllerNavigator> = BrowserNavigatorKt.BrowserNavigator(parent: self) + private lazy var viewModel: LinksViewModel = LinksViewModel(linkRepoBuilder: LinksLinksBuilder(), builder: AlertPresenter.Builder(viewController: self), navigator: navigator) private var lifecycleManager: LifecycleManager! deinit { @@ -37,7 +35,7 @@ class LinksViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [ self.viewModel.browserButtonText.observe { buttonText in self.browserButton.setTitle(buttonText as String?, for: .normal) diff --git a/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift b/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift index 8c2412492..94ce6baad 100644 --- a/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift +++ b/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift @@ -16,23 +16,23 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class LoadingViewController: UITableViewController { private lazy var viewModel = HudViewModel(builder: HUD.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { return [] } + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) switch (indexPath.row) { diff --git a/newexample/ios/Demo/Location/LocationViewController.swift b/newexample/ios/Demo/Location/LocationViewController.swift index a2632b8af..2b82f3b9c 100644 --- a/newexample/ios/Demo/Location/LocationViewController.swift +++ b/newexample/ios/Demo/Location/LocationViewController.swift @@ -18,7 +18,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands import UIKit import CoreLocation -import KotlinNativeFramework +import KalugaExampleShared class LocationViewController: UIViewController { @@ -30,9 +30,9 @@ class LocationViewController: UIViewController { @IBOutlet weak var label: UILabel! - lazy var viewModel = KNArchitectureFramework().createLocationViewModel(permission: Const.permission, repoBuilder: KNLocationFramework().getPermissionRepoBuilder()) + lazy var viewModel = LocationViewModel(permission: Const.permission) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } @@ -40,13 +40,15 @@ class LocationViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = viewModel.addLifecycleManager(parent: self, onLifecycle: { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.location.observe(onNext: { (location) in - self?.label.text = location as? String ?? "" - })] - }) + return [ + viewModel.location.observe { (location) in + self?.label.text = location as? String ?? "" + } + ] + } } } diff --git a/newexample/ios/Demo/Permissions/PermissionListViewController.swift b/newexample/ios/Demo/Permissions/PermissionListViewController.swift index 08b634940..2fd828e7c 100644 --- a/newexample/ios/Demo/Permissions/PermissionListViewController.swift +++ b/newexample/ios/Demo/Permissions/PermissionListViewController.swift @@ -16,51 +16,61 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class PermissionListViewController : UITableViewController { - - private lazy var viewModel: PermissionsListViewModel = KNArchitectureFramework().createPermissionListViewModel(parent: self) { (permission) -> UIViewController in - return PermissionViewController.create(permission: permission) + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Push(animated: true) { + guard let permission = action.value?.permission else { + return UIViewController() + } + + return PermissionViewController.create(permission: permission) + } } + private lazy var viewModel: PermissionsListViewModel = PermissionsListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! private var permissions = [PermissionView]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? = nil + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.observePermissions { (permissionViews: [PermissionView], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.permissions = permissionViews - self?.onSelected = onSelected - self?.tableView.reloadData() - }] + return [ + viewModel.permissions.observeInitialized { next in + let permissions = next?.compactMap { $0 as? PermissionView } ?? [] + self?.permissions = permissions + self?.onSelected = { (index: Int) in viewModel.onPermissionPressed(permissionView: permissions[index]) } + self?.tableView.reloadData() + } + ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return permissions.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: PermissionsListCell.Const.identifier, for: indexPath) as! PermissionsListCell cell.label.text = permissions[indexPath.row].title return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } diff --git a/newexample/ios/Demo/Permissions/PermissionViewController.swift b/newexample/ios/Demo/Permissions/PermissionViewController.swift index 29ce5c847..4bca12051 100644 --- a/newexample/ios/Demo/Permissions/PermissionViewController.swift +++ b/newexample/ios/Demo/Permissions/PermissionViewController.swift @@ -17,20 +17,18 @@ import UIKit import Foundation -import KotlinNativeFramework +import KalugaExampleShared class PermissionViewController: UIViewController { private struct Const { static let storyboard = UIStoryboard(name: "Main", bundle: nil) static let permissionVc = "Permission" - - static let permissions = KNPermissionsFramework().getPermissions() } static func create(permission: Permission) -> PermissionViewController { let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.permissionVc) as! PermissionViewController - vc.viewModel = KNArchitectureFramework().createPermissionViewModel(permissions: Const.permissions, permission: permission) + vc.viewModel = PermissionViewModel(permission: permission) return vc } @@ -39,47 +37,47 @@ class PermissionViewController: UIViewController { var viewModel: PermissionViewModel! private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - + requestPermissionButton.setTitle(NSLocalizedString("permission_request", comment: ""), for: .normal) - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - + return [ - viewModel.permissionStateMessage.observe(onNext: { (message) in + viewModel.permissionStateMessage.observe { message in self?.permissionStateLabel.text = NSLocalizedString(message as? String ?? "", comment: "") - - }), - - viewModel.requestMessage.observe(onNext: { (optionalMessage) in + + }, + + viewModel.requestMessage.observe { optionalMessage in guard let message = optionalMessage as? String else { return } - + let alert = UIAlertController(title: NSLocalizedString("permission_request", comment: ""), message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) self?.present(alert, animated: true, completion: nil) - - }), - - viewModel.showPermissionButton.observe(onNext: { (show) in + + }, + + viewModel.showPermissionButton.observe { show in self?.requestPermissionButton.isHidden = !(show as? Bool ?? false) - }) + } ] - }) + } } - - + + @IBAction func requestPermission(sender: Any?) { viewModel.requestPermission() } diff --git a/newexample/ios/Demo/Resources/ButtonViewController.swift b/newexample/ios/Demo/Resources/ButtonViewController.swift index 68871ce6f..f00e435ff 100644 --- a/newexample/ios/Demo/Resources/ButtonViewController.swift +++ b/newexample/ios/Demo/Resources/ButtonViewController.swift @@ -15,11 +15,11 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ButtonViewController : UITableViewController { - private lazy var viewModel: ButtonViewModel = KNArchitectureFramework().createButtonViewModel(parent: self) + private lazy var viewModel: ButtonViewModel = ButtonViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider(), alertPresenterBuilder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! private var buttons = [KalugaButton]() @@ -31,7 +31,7 @@ class ButtonViewController : UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.allowsSelection = false - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ viewModel.buttons.observe { labels in diff --git a/newexample/ios/Demo/Resources/ColorViewController.swift b/newexample/ios/Demo/Resources/ColorViewController.swift index 4f2ce7a2f..c8412766a 100644 --- a/newexample/ios/Demo/Resources/ColorViewController.swift +++ b/newexample/ios/Demo/Resources/ColorViewController.swift @@ -15,7 +15,7 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ColorViewController : UIViewController { @@ -43,7 +43,7 @@ class ColorViewController : UIViewController { private var blendedLightenedColors: [BackgroundStyle] = [] private var blendedDarkenedColors: [BackgroundStyle] = [] - private lazy var viewModel: ColorViewModel = KNArchitectureFramework().createColorViewModel(parent: self) + private lazy var viewModel: ColorViewModel = ColorViewModel(alertPresenterBuilder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! deinit { @@ -58,7 +58,7 @@ class ColorViewController : UIViewController { sourceDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) blendedLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) blendedDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ diff --git a/newexample/ios/Demo/Resources/LabelViewController.swift b/newexample/ios/Demo/Resources/LabelViewController.swift index 055c3ba09..dd8695a79 100644 --- a/newexample/ios/Demo/Resources/LabelViewController.swift +++ b/newexample/ios/Demo/Resources/LabelViewController.swift @@ -15,11 +15,11 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class LabelViewController : UITableViewController { - - private lazy var viewModel: LabelViewModel = KNArchitectureFramework().createLabelViewModel() + + private var viewModel: LabelViewModel = LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider()) private var lifecycleManager: LifecycleManager! private var labels = [KalugaLabel]() @@ -32,11 +32,11 @@ class LabelViewController : UITableViewController { super.viewDidLoad() tableView.allowsSelection = false - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ viewModel.labels.observe { labels in - self?.labels = labels?.compactMap { $0 as? KalugaLabel } ?? [] + self?.labels = labels?.compactMap { $0 as? KalugaLabel } ?? [] self?.tableView.reloadData() } ] diff --git a/newexample/ios/Demo/Resources/ResourcesListViewController.swift b/newexample/ios/Demo/Resources/ResourcesListViewController.swift index 906c7c6e7..40d7799a8 100644 --- a/newexample/ios/Demo/Resources/ResourcesListViewController.swift +++ b/newexample/ios/Demo/Resources/ResourcesListViewController.swift @@ -15,49 +15,56 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ResourcesListViewController : UITableViewController { + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Segue(identifier: action.segueKey) + } - private lazy var viewModel: ResourcesListViewModel = KNArchitectureFramework().createResourcesViewModel(parent: self) + private lazy var viewModel: ResourcesListViewModel = ResourcesListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! private var resources = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? = nil + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.observeResources() { (resources: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.resources = resources - self?.onSelected = onSelected - self?.tableView.reloadData() - }] + return [ + viewModel.resources.observeInitialized { next in + let resources = next ?? [] + self?.resources = resources.map { ($0 as! Resource).title } + self?.onSelected = { (index: Int) in viewModel.onResourceSelected(resource: resources[index] as! Resource) } + self?.tableView.reloadData() + } + ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return resources.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: ResourcesListCell.Const.identifier, for: indexPath) as! ResourcesListCell cell.label.text = resources[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } @@ -72,3 +79,16 @@ class ResourcesListCell : UITableViewCell { @IBOutlet weak var label: UILabel! } + +private extension ResourcesListNavigationAction { + var segueKey: String { + get { + switch self { + case is ResourcesListNavigationAction.Button: return "showButton" + case is ResourcesListNavigationAction.Color: return "showColor" + case is ResourcesListNavigationAction.Label: return "showLabel" + default: return "" + } + } + } +} diff --git a/newexample/ios/Demo/System/Network/NetworkViewController.swift b/newexample/ios/Demo/System/Network/NetworkViewController.swift index a3c6c7cef..ead546b08 100644 --- a/newexample/ios/Demo/System/Network/NetworkViewController.swift +++ b/newexample/ios/Demo/System/Network/NetworkViewController.swift @@ -17,11 +17,10 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared class NetworkViewController : UIViewController { - - private let knArchitectureFramework = KNArchitectureFramework() + @IBOutlet weak var networkStateText: UILabel! private var lifecycleManager: LifecycleManager! @@ -34,12 +33,12 @@ class NetworkViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ - viewModel.networkState.observe { value in - self?.networkStateText.text = value as? String + viewModel.networkState.observe { next in + self?.networkStateText.text = next as? String } ] } diff --git a/newexample/ios/Demo/System/SystemViewController.swift b/newexample/ios/Demo/System/SystemViewController.swift index 9945dfd0d..782e14c5b 100644 --- a/newexample/ios/Demo/System/SystemViewController.swift +++ b/newexample/ios/Demo/System/SystemViewController.swift @@ -17,56 +17,60 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared class SystemViewController : UITableViewController { - - private let knArchitectureFramework = KNArchitectureFramework() - private lazy var viewModel: SystemViewModel = { - return self.knArchitectureFramework.createSystemViewModel(parent: self) - }() - + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + switch action { + case is SystemNavigationActions.Network: return NavigationSpec.Segue(identifier: "showNetwork") + default: return NavigationSpec.Segue(identifier: "") + } + } + private lazy var viewModel: SystemViewModel = SystemViewModel(navigator: navigator) + private var modules = [String]() - private var onModuleTapped: ((KotlinInt) -> KotlinUnit)? = nil + private var onModuleTapped: ((Int) -> Void)? = nil private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ - viewModel.observeModules { (modules: [String], onButtonTapped: @escaping (KotlinInt) -> KotlinUnit) in - self?.modules = modules - self?.onModuleTapped = onButtonTapped + viewModel.modules.observeInitialized { next in + let modules = next?.compactMap { $0 as? SystemFeatures } ?? [] + self?.modules = modules.map{ $0.name } + self?.onModuleTapped = { (index: Int) in viewModel.onButtonTapped(systemFeatures: modules[index]) } self?.tableView.reloadData() } ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return modules.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: SystemListCell.Const.identifier, for: indexPath) as! SystemListCell cell.label.text = modules[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onModuleTapped?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onModuleTapped?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } diff --git a/newexample/ios/DemoTests/DemoTests.swift b/newexample/ios/DemoTests/DemoTests.swift index 2aa6b9b30..dbe30118f 100644 --- a/newexample/ios/DemoTests/DemoTests.swift +++ b/newexample/ios/DemoTests/DemoTests.swift @@ -18,7 +18,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands import XCTest @testable import Demo -import KotlinNativeFramework +import KalugaExampleShared class DemoTests: XCTestCase { diff --git a/newexample/shared/build.gradle.kts b/newexample/shared/build.gradle.kts index 01ad3d30b..fcc1ed645 100644 --- a/newexample/shared/build.gradle.kts +++ b/newexample/shared/build.gradle.kts @@ -21,11 +21,12 @@ val modules = listOf( "logging" to false, "resources" to true, "review" to true, - "system" to false, - "permissions" to false + "system" to true, + "permissions" to true ) commonComponent { + logger.lifecycle("Configure framework") baseName = "KalugaExampleShared" isStatic = false transitiveExport = true diff --git a/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt index 972f1dfce..4cc0062ae 100644 --- a/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt +++ b/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -20,7 +20,6 @@ package com.splendo.kaluga.example.shared.di import com.splendo.kaluga.alerts.AlertPresenter import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.base.ApplicationHolder import com.splendo.kaluga.bluetooth.BluetoothBuilder import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter @@ -31,10 +30,11 @@ import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDeta import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.CloseDetailsNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails +import com.splendo.kaluga.example.shared.viewmodel.architecture.InputNavigation import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListNavigation import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetails import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel @@ -43,7 +43,6 @@ import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel @@ -93,11 +92,11 @@ internal val androidModule = module { PermissionsListViewModel(navigator) } - viewModel { (permission: Permission) -> PermissionViewModel(get(), permission) } + viewModel { (permission: Permission) -> PermissionViewModel(permission) } - viewModel { (permission: LocationPermission) -> LocationViewModel(permission, get()) } + viewModel { (permission: LocationPermission) -> LocationViewModel(permission) } - viewModel { (navigator: Navigator>) -> + viewModel { (navigator: Navigator) -> ArchitectureInputViewModel(navigator) } @@ -124,7 +123,7 @@ internal val androidModule = module { KeyboardViewModel(keyboardBuilder, focusHandler) } - viewModel { (navigator: Navigator>) -> + viewModel { (navigator: Navigator>) -> LinksViewModel( LinksBuilder(), AlertPresenter.Builder(), @@ -132,17 +131,17 @@ internal val androidModule = module { ) } - viewModel { (navigator: Navigator>) -> + viewModel { (navigator: Navigator) -> SystemViewModel( navigator ) } viewModel { - NetworkViewModel(NetworkStateRepoBuilder(get())) + NetworkViewModel(NetworkStateRepoBuilder()) } - viewModel { (navigator: Navigator) -> + viewModel { (navigator: Navigator) -> BluetoothListViewModel(navigator) } diff --git a/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt new file mode 100644 index 000000000..5c0a64d55 --- /dev/null +++ b/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.shared.viewmodel.permissions + +import com.splendo.kaluga.permissions.notifications.NotificationOptions + +actual val notificationOptions = NotificationOptions() diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt index 98dec51f7..49d4fa4fe 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt @@ -37,7 +37,7 @@ class InputNavigation(inputDetails: InputDetails) : SingleValueNavigationAction< NavigationBundleSpecType.SerializedType(InputDetails.serializer()) ) -class ArchitectureInputViewModel(navigator: Navigator>) : NavigatingViewModel>(navigator) { +class ArchitectureInputViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { val nameHeader = observableOf("Enter your Name") val numberHeader = observableOf("Enter a Number") diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt index 2a626d61c..a2551c4ef 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt @@ -62,10 +62,12 @@ class BeaconsListViewModel : BaseLifecycleViewModel(), KoinComponent { } fun onScanPressed() { - if (_isScanning.value) { - service.stopMonitoring() - } else { - service.startMonitoring(coroutineScope) + coroutineScope.launch { + if (_isScanning.value) { + service.stopMonitoring() + } else { + service.startMonitoring(coroutineScope) + } } } diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt index 522e649e2..2fd53abd0 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt @@ -17,17 +17,18 @@ package com.splendo.kaluga.example.shared.viewmodel.bluetooth -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.observable.toUninitializedObservable import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import com.splendo.kaluga.base.text.format import com.splendo.kaluga.bluetooth.Bluetooth import com.splendo.kaluga.bluetooth.device.ConnectableDeviceState -import com.splendo.kaluga.bluetooth.device.NotConnectableDeviceState import com.splendo.kaluga.bluetooth.device.Identifier +import com.splendo.kaluga.bluetooth.device.NotConnectableDeviceState +import com.splendo.kaluga.bluetooth.device.SerializableIdentifier +import com.splendo.kaluga.bluetooth.device.serializable import com.splendo.kaluga.bluetooth.device.stringValue import com.splendo.kaluga.bluetooth.distance import com.splendo.kaluga.bluetooth.get @@ -40,17 +41,14 @@ import com.splendo.kaluga.resources.localized import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor import org.koin.core.component.KoinComponent import org.koin.core.component.inject -class DeviceDetailsSpec : NavigationBundleSpec>(setOf(DeviceDetailsSpecRow.UUIDRow)) - -sealed class DeviceDetailsSpecRow(associatedType: NavigationBundleSpecType) : NavigationBundleSpecRow(associatedType) { - object UUIDRow : DeviceDetailsSpecRow(NavigationBundleSpecType.StringType) -} +class DeviceDetails(value: Identifier) : SingleValueNavigationAction(value.serializable, NavigationBundleSpecType.SerializedType(SerializableIdentifier.serializer())) class BluetoothDeviceDetailViewModel(private val identifier: Identifier) : BaseLifecycleViewModel(), KoinComponent { diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt index f4d98b64a..2eccab381 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt @@ -44,7 +44,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -class BluetoothListDeviceViewModel(private val identifier: Identifier, bluetooth: Bluetooth, navigator: Navigator) : NavigatingViewModel(navigator) { +class BluetoothListDeviceViewModel(private val identifier: Identifier, bluetooth: Bluetooth, navigator: Navigator) : NavigatingViewModel(navigator) { enum class ConnectButtonState { Connect, @@ -101,16 +101,7 @@ class BluetoothListDeviceViewModel(private val identifier: Identifier, bluetooth device.disconnect() } - fun onMorePressed() = - navigator.navigate( - BluetoothListNavigation( - DeviceDetailsSpec().toBundle { specRow -> - when (specRow) { - is DeviceDetailsSpecRow.UUIDRow -> specRow.convertValue(identifier.stringValue) - } - } - ) - ) + fun onMorePressed() = navigator.navigate(DeviceDetails(identifier)) private fun parseServiceUUIDs(uuids: List): String { val uuidString = uuids.fold("") { result, next -> diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt index 8c3988189..19d3e5455 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt @@ -17,24 +17,19 @@ package com.splendo.kaluga.example.shared.viewmodel.bluetooth -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundle import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel import com.splendo.kaluga.bluetooth.Bluetooth import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -class BluetoothListNavigation(bundle: NavigationBundle>) : NavigationAction>(bundle) - -class BluetoothListViewModel(navigator: Navigator) : NavigatingViewModel(navigator), KoinComponent { +class BluetoothListViewModel(navigator: Navigator) : NavigatingViewModel(navigator), KoinComponent { private val bluetooth: Bluetooth by inject() private val _isScanning = MutableStateFlow(false) diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt index 843592790..e377a01c1 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt @@ -17,43 +17,25 @@ package com.splendo.kaluga.example.shared.viewmodel.info -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundle -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.toBundle +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.observableOf import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel import com.splendo.kaluga.review.ReviewManager import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable -class DialogSpec : NavigationBundleSpec(setOf(DialogSpecRow.TitleRow, DialogSpecRow.MessageRow)) +@Serializable +data class DialogSpec(val title: String, val message: String) +@Serializable +data class MailSpec(val to: List, val subject: String) -sealed class DialogSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.StringType) { - object TitleRow : DialogSpecRow() - object MessageRow : DialogSpecRow() -} - -class LinkSpec : NavigationBundleSpec(setOf(LinkSpecRow.LinkRow)) - -sealed class LinkSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.StringType) { - object LinkRow : LinkSpecRow() -} - -class MailSpec : NavigationBundleSpec>(setOf(MailSpecRow.ToRow, MailSpecRow.SubjectRow)) - -sealed class MailSpecRow(associatedType: NavigationBundleSpecType) : NavigationBundleSpecRow(associatedType) { - object ToRow : MailSpecRow>(NavigationBundleSpecType.StringArrayType) - object SubjectRow : MailSpecRow(NavigationBundleSpecType.StringType) -} - -sealed class InfoNavigation>(bundle: NavigationBundle) : NavigationAction(bundle) { +sealed class InfoNavigation(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { - class Dialog(bundle: NavigationBundle) : InfoNavigation(bundle) - class Link(bundle: NavigationBundle) : InfoNavigation(bundle) - class Mail(bundle: NavigationBundle>) : InfoNavigation>(bundle) + class Dialog(title: String, message: String) : InfoNavigation(DialogSpec(title, message), NavigationBundleSpecType.SerializedType(DialogSpec.serializer())) + class Link(link: String) : InfoNavigation(link, NavigationBundleSpecType.StringType) + class Mail(to: List, subject: String) : InfoNavigation(MailSpec(to, subject), NavigationBundleSpecType.SerializedType(MailSpec.serializer())) } class InfoViewModel( @@ -74,36 +56,10 @@ class InfoViewModel( fun onButtonPressed(button: Button) { when (button) { - is Button.About -> InfoNavigation.Dialog( - DialogSpec().toBundle { row -> - when (row) { - is DialogSpecRow.TitleRow -> row.convertValue("About Us") - is DialogSpecRow.MessageRow -> row.convertValue("Kaluga is developed by Splendo Consulting BV") - } - } - ) - is Button.Website -> InfoNavigation.Link( - LinkSpec().toBundle { row -> - when (row) { - is LinkSpecRow.LinkRow -> row.convertValue("https://kaluga.splendo.com") - } - } - ) - is Button.GitHub -> InfoNavigation.Link( - LinkSpec().toBundle { row -> - when (row) { - is LinkSpecRow.LinkRow -> row.convertValue("https://github.com/splendo/kaluga") - } - } - ) - is Button.Mail -> InfoNavigation.Mail( - MailSpec().toBundle { row -> - when (row) { - is MailSpecRow.ToRow -> row.convertValue(listOf("info@splendo.com")) - is MailSpecRow.SubjectRow -> row.convertValue("Question about Kaluga") - } - } - ) + is Button.About -> InfoNavigation.Dialog("About Us", "Kaluga is developed by Splendo Consulting BV") + is Button.Website -> InfoNavigation.Link("https://kaluga.splendo.com") + is Button.GitHub -> InfoNavigation.Link("https://github.com/splendo/kaluga") + is Button.Mail -> InfoNavigation.Mail(listOf("info@splendo.com"), ("Question about Kaluga")) is Button.Review -> { coroutineScope.launch { reviewManager.attemptToRequestReview() diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt index f11a4c15c..a2eb9544d 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt @@ -26,6 +26,7 @@ import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.navigation.toBundle import com.splendo.kaluga.architecture.observable.observableOf import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel @@ -40,21 +41,15 @@ data class Repository( val type: String ) -class BrowserSpec : NavigationBundleSpec(setOf(BrowserSpecRow.UrlSpecRow)) - -sealed class BrowserNavigationActions>(bundle: NavigationBundle?) : NavigationAction(bundle) { - class OpenWebView(bundle: NavigationBundle?) : BrowserNavigationActions(bundle) -} - -sealed class BrowserSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.StringType) { - object UrlSpecRow : BrowserSpecRow() +sealed class BrowserNavigationActions(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { + class OpenWebView(url: String) : BrowserNavigationActions(url, NavigationBundleSpecType.StringType) } class LinksViewModel( linkRepoBuilder: LinksBuilder, val builder: AlertPresenter.Builder, - navigator: Navigator> -) : NavigatingViewModel>(navigator) { + navigator: Navigator> +) : NavigatingViewModel>(navigator) { val browserButtonText = observableOf("browser_button_text".localized()) val linksInstructions = observableOf("links_instructions".localized()) @@ -77,15 +72,7 @@ class LinksViewModel( val result = linksRepo.validateLink("https://kaluga-links.web.app") if (result != null) { navigator.navigate( - BrowserNavigationActions.OpenWebView( - BrowserSpec().toBundle { row -> - when (row) { - is BrowserSpecRow.UrlSpecRow -> row.convertValue( - result - ) - } - } - ) + BrowserNavigationActions.OpenWebView(result) ) } else { showAlert( diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt index 936f18a68..7d3ec401f 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt @@ -19,19 +19,31 @@ package com.splendo.kaluga.example.shared.viewmodel.location import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.base.singleThreadDispatcher +import com.splendo.kaluga.location.BaseLocationManager import com.splendo.kaluga.location.Location import com.splendo.kaluga.location.LocationStateRepoBuilder import com.splendo.kaluga.location.location +import com.splendo.kaluga.logging.Logger import com.splendo.kaluga.permissions.location.LocationPermission import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class LocationViewModel(permission: LocationPermission, repoBuilder: LocationStateRepoBuilder) : BaseLifecycleViewModel() { +private val locationDispatcher = singleThreadDispatcher("LocationDispatcher") - private val locationStateRepo = repoBuilder.create(permission) +class LocationViewModel(permission: LocationPermission) : BaseLifecycleViewModel(), KoinComponent { + + private val logger: Logger by inject() + private val repoBuilder: LocationStateRepoBuilder by inject() + private val locationStateRepo = repoBuilder.create( + permission, + { permission, permissions -> BaseLocationManager.Settings(permission, permissions, logger = logger) }, + coroutineScope.coroutineContext + locationDispatcher + ) private val _location = MutableStateFlow("") val location = _location.toInitializedObservable(coroutineScope) diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt index 51d2289ec..1872b1dbe 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt @@ -22,6 +22,7 @@ import com.splendo.kaluga.architecture.observable.UninitializedObservable import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.observable.toUninitializedObservable import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.base.singleThreadDispatcher import com.splendo.kaluga.permissions.base.Permission import com.splendo.kaluga.permissions.base.PermissionState import com.splendo.kaluga.permissions.base.Permissions @@ -30,10 +31,16 @@ import com.splendo.kaluga.resources.localized import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class PermissionViewModel(permissionsBuilder: PermissionsBuilder, private val permission: Permission) : BaseLifecycleViewModel() { +private val permissionsDispatcher = singleThreadDispatcher("PermissionsDispatcher") - private val permissions = Permissions(permissionsBuilder) +class PermissionViewModel(private val permission: Permission) : BaseLifecycleViewModel(), KoinComponent { + + private val permissionsBuilder: PermissionsBuilder by inject() + + private val permissions = Permissions(permissionsBuilder, coroutineScope.coroutineContext + permissionsDispatcher) val permissionStateMessage: UninitializedObservable = permissions[permission] .map { permissionState -> when (permissionState) { diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt index cdbdd53a3..d42361c25 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt @@ -17,20 +17,26 @@ package com.splendo.kaluga.example.shared.viewmodel.permissions -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.toBundle +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.observableOf import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel import com.splendo.kaluga.logging.RestrictedLogLevel import com.splendo.kaluga.logging.RestrictedLogger import com.splendo.kaluga.permissions.base.BasePermissionManager -import com.splendo.kaluga.permissions.base.Permissions +import com.splendo.kaluga.permissions.base.Permission import com.splendo.kaluga.permissions.base.PermissionsBuilder +import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission +import com.splendo.kaluga.permissions.calendar.CalendarPermission +import com.splendo.kaluga.permissions.camera.CameraPermission +import com.splendo.kaluga.permissions.contacts.ContactsPermission +import com.splendo.kaluga.permissions.location.LocationPermission +import com.splendo.kaluga.permissions.microphone.MicrophonePermission +import com.splendo.kaluga.permissions.notifications.NotificationOptions +import com.splendo.kaluga.permissions.notifications.NotificationsPermission import com.splendo.kaluga.permissions.registerAllPermissionsNotRegistered +import com.splendo.kaluga.permissions.storage.StoragePermission import com.splendo.kaluga.resources.localized import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -46,13 +52,23 @@ enum class PermissionView(val title: String) { Location("permissions_location".localized()), Microphone("permissions_microphone".localized()), Notifications("permissions_notifications".localized()), - Storage("permissions_storage".localized()) + Storage("permissions_storage".localized()); + + val permission: Permission get() = when (this) { + Bluetooth -> BluetoothPermission + Calendar -> CalendarPermission(allowWrite = true) + Camera -> CameraPermission + Contacts -> ContactsPermission(allowWrite = true) + Location -> LocationPermission(background = true, precise = true) + Microphone -> MicrophonePermission + Notifications -> NotificationsPermission(notificationOptions) + Storage -> StoragePermission(allowWrite = true) + } } -object PermissionNavigationBundleSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.SerializedType(PermissionView.serializer())) -class PermissionNavigationBundleSpec : NavigationBundleSpec(setOf(PermissionNavigationBundleSpecRow)) +expect val notificationOptions: NotificationOptions -class PermissionsListNavigationAction(permissionView: PermissionView) : NavigationAction(PermissionNavigationBundleSpec().toBundle { spec -> spec.convertValue(permissionView) }) +class PermissionsListNavigationAction(permissionView: PermissionView) : SingleValueNavigationAction(permissionView, NavigationBundleSpecType.SerializedType(PermissionView.serializer())) class PermissionsListViewModel(navigator: Navigator) : NavigatingViewModel(navigator), KoinComponent { diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt index 1dd8689b2..bec3c7dfd 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt @@ -28,16 +28,13 @@ sealed class SystemFeatures(val name: String) { object Network : SystemFeatures("network_feature".localized()) } -sealed class SystemNavigationActions( - value: Value, - type: NavigationBundleSpecType -) : SingleValueNavigationAction(value, type) { - object Network : SystemNavigationActions(Unit, NavigationBundleSpecType.UnitType) +sealed class SystemNavigationActions : SingleValueNavigationAction(Unit, NavigationBundleSpecType.UnitType) { + object Network : SystemNavigationActions() } class SystemViewModel( - navigator: Navigator> -) : NavigatingViewModel>(navigator) { + navigator: Navigator +) : NavigatingViewModel(navigator) { val modules = observableOf( diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt index f79552975..f3ae571f9 100644 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt +++ b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt @@ -19,12 +19,11 @@ package com.splendo.kaluga.example.shared.viewmodel.system.network import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.system.network.Network +import com.splendo.kaluga.system.network.NetworkConnectionType import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder import com.splendo.kaluga.system.network.state.network import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch class NetworkViewModel( @@ -42,19 +41,19 @@ class NetworkViewModel( scope.launch { networkRepo.network().collect { when (it) { - is Network.Unknown.WithoutLastNetwork -> + is NetworkConnectionType.Unknown.WithoutLastNetwork -> _networkState.value = "Network's state is Unknown and without the last available connection." - is Network.Unknown.WithLastNetwork -> + is NetworkConnectionType.Unknown.WithLastNetwork -> _networkState.value = - "Network's state is Unknown and with last known connection as ${it.lastKnownNetwork}." - is Network.Known.Cellular -> + "Network's state is Unknown and with last known connection as ${it.lastKnown}." + is NetworkConnectionType.Known.Cellular -> _networkState.value = "Network's state is Available through Cellular." - is Network.Known.Wifi -> + is NetworkConnectionType.Known.Wifi -> _networkState.value = "Network's state is Available through WIFI." - is Network.Known.Absent -> + is NetworkConnectionType.Known.Absent -> _networkState.value = "Network's state is Absent." } diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt new file mode 100644 index 000000000..924fa6f1b --- /dev/null +++ b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.shared.viewmodel.info + +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.UIKit.UIViewController + +fun InfoNavigator( + parent: UIViewController, + onDialogSpec: (DialogSpec) -> NavigationSpec, + onLink: (String) -> NavigationSpec, + onMailSpec: (MailSpec) -> NavigationSpec + +) = ViewControllerNavigator>(parent) { action -> + when (action) { + is InfoNavigation.Dialog -> onDialogSpec(action.value) + is InfoNavigation.Link -> onLink(action.value) + is InfoNavigation.Mail -> onMailSpec(action.value) + } +} diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt new file mode 100644 index 000000000..9672a7e6e --- /dev/null +++ b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt @@ -0,0 +1,29 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.viewmodel.link + +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.Foundation.NSURL +import platform.UIKit.UIViewController + +fun BrowserNavigator(parent: UIViewController) = ViewControllerNavigator>(parent) { action -> + when (action) { + is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser(NSURL.URLWithString(action.value)!!, NavigationSpec.Browser.Type.Normal) + } +} diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt new file mode 100644 index 000000000..621259f1e --- /dev/null +++ b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.shared.viewmodel.permissions + +import com.splendo.kaluga.permissions.notifications.NotificationOptions +import platform.UserNotifications.UNAuthorizationOptionAlert +import platform.UserNotifications.UNAuthorizationOptionSound + +actual val notificationOptions = NotificationOptions(UNAuthorizationOptionAlert or UNAuthorizationOptionSound) diff --git a/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt new file mode 100644 index 000000000..1ddf8c61b --- /dev/null +++ b/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.shared.viewmodel.permissions + +import com.splendo.kaluga.permissions.notifications.NotificationOptions + +actual val notificationOptions = NotificationOptions() \ No newline at end of file diff --git a/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt new file mode 100644 index 000000000..5c0a64d55 --- /dev/null +++ b/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2022 Splendo Consulting B.V. The Netherlands + * + * 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 com.splendo.kaluga.example.shared.viewmodel.permissions + +import com.splendo.kaluga.permissions.notifications.NotificationOptions + +actual val notificationOptions = NotificationOptions() From 71f4737540ff2ba94e03022144b207f1c8abccae Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 16 Nov 2022 16:52:08 +0100 Subject: [PATCH 029/227] Move newExample to old folder --- {newexample => example}/.gitignore | 0 example/android/build.gradle | 92 - .../android/build.gradle.kts | 0 example/android/proguard-rules.pro | 21 - example/android/src/main/AndroidManifest.xml | 11 +- .../kaluga/example/ExampleApplication.kt | 10 +- .../splendo/kaluga/example/InfoFragment.kt | 20 +- .../kaluga/example/alerts/AlertsActivity.kt | 2 +- .../ArchitectureDetailsActivity.kt | 10 +- .../architecture/ArchitectureInputActivity.kt | 3 +- .../example/bluetooth/BluetoothActivity.kt | 4 +- .../bluetooth/BluetoothMoreActivity.kt | 13 +- .../bottomSheet/BottomSheetActivity.kt | 0 .../bottomSheet/ui/BottomSheetLayout.kt | 0 .../bottomSheet/ui/BottomSheetParentLayout.kt | 0 .../ui/BottomSheetParentSubPageLayout.kt | 0 .../ui/BottomSheetSubPageLayout.kt | 0 .../viewModel/NavigationMappers.kt | 0 .../example/contacts/ContactsActivity.kt | 0 .../contacts/ui/ContactDetailsLayout.kt | 0 .../example/contacts/ui/ContactsLayout.kt | 0 .../example/contacts/ui/ContactsListLayout.kt | 0 .../kaluga/example/contacts/ui/Styles.kt | 0 .../contacts/viewModel/NavigationMappers.kt | 0 .../com/splendo/kaluga/example/di/Modules.kt | 217 --- .../kaluga/example/link/LinksActivity.kt | 5 +- .../kaluga/example/loading/LoadingActivity.kt | 2 +- .../location/LocationBackgroundService.kt | 6 +- .../permissions/PermissionsDemoActivity.kt | 25 +- .../PlatformSpecificActivity.kt | 12 +- .../bottomSheet/BottomSheetActivity.kt | 32 - .../bottomSheet/ui/BottomSheetLayout.kt | 73 - .../bottomSheet/ui/BottomSheetParentLayout.kt | 124 -- .../ui/BottomSheetParentSubPageLayout.kt | 53 - .../ui/BottomSheetSubPageLayout.kt | 79 - .../viewModel/NavigationMappers.kt | 59 - .../compose/contacts/ContactsActivity.kt | 32 - .../contacts/ui/ContactDetailsLayout.kt | 97 - .../compose/contacts/ui/ContactsLayout.kt | 53 - .../compose/contacts/ui/ContactsListLayout.kt | 100 - .../compose/contacts/ui/Styles.kt | 17 - .../contacts/viewModel/NavigationMappers.kt | 52 - .../kaluga/example/system/NetworkActivity.kt | 0 .../kaluga/example/system/SystemActivity.kt | 19 +- .../system/fragments/NetworkFragment.kt | 44 - .../src/main/res/layout/activity_location.xml | 76 +- .../src/main/res/layout/activity_network.xml | 0 .../src/main/res/layout/activity_system.xml | 17 +- .../src/main/res/layout/fragment_network.xml | 43 - {newexample => example}/build.gradle.kts | 0 {newexample => example}/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 {newexample => example}/gradlew | 0 {newexample => example}/gradlew.bat | 0 .../inspectionProfiles/Project_Default.xml | 6 - example/ios/.idea/runConfigurations/Demo.xml | 11 - example/ios/Demo.xcodeproj/project.pbxproj | 102 +- .../Demo/Alerts/AlertsViewController.swift | 10 +- example/ios/Demo/AppDelegate.swift | 4 +- .../ArchitectureDetailsViewController.swift | 25 +- .../ArchitectureInputViewController.swift | 37 +- .../ios/Demo/Beacons/BeaconsViewCell.swift | 2 +- .../Demo/Beacons/BeaconsViewController.swift | 10 +- ...BluetoothDeviceDetailsViewController.swift | 101 +- .../Bluetooth/BluetoothViewController.swift | 55 +- .../DateTimePickerViewController.swift | 24 +- example/ios/Demo/ExampleViewController.swift | 57 +- .../FeaturesListViewController.swift | 61 +- .../ios/Demo/Info/InfoViewController.swift | 65 +- .../KeyboardManagerViewController.swift | 16 +- .../ios/Demo/Links/LinksViewController.swift | 12 +- .../LoadingViewController.swift | 12 +- .../Location/LocationViewController.swift | 18 +- .../PermissionListViewController.swift | 48 +- .../PermissionViewController.swift | 48 +- .../Demo/Resources/ButtonViewController.swift | 6 +- .../Demo/Resources/ColorViewController.swift | 6 +- .../Demo/Resources/LabelViewController.swift | 10 +- .../ResourcesListViewController.swift | 54 +- .../Network/NetworkViewController.swift | 11 +- .../Demo/System/SystemViewController.swift | 44 +- example/ios/DemoTests/DemoTests.swift | 2 +- example/ios/KotlinNativeFramework/Info.plist | 22 - .../KotlinNativeFramework/build.gradle.kts | 52 - .../kotlin/KotlinNativeFramework.kt | 28 - .../architecture/KNArchitectureFramework.kt | 139 -- .../kotlin/beacons/KNBeaconsFramework.kt | 39 - .../kotlin/bluetooth/KNBluetoothFramework.kt | 34 - .../kotlin/location/KNLocationFramework.kt | 7 - .../permissions/KNPermissionsFramework.kt | 29 - .../.idea/codeStyles/Project.xml | 127 -- .../.idea/codeStyles/codeStyleConfig.xml | 5 - .../.idea/copyright/apache.xml | 6 - .../.idea/copyright/profiles_settings.xml | 7 - .../.run/Ktlint Check.run.xml | 23 - .../.run/Ktlint Format.run.xml | 23 - example/ios/Supporting Files/build.gradle.kts | 41 - .../ios/Supporting Files/gradle.properties | 21 - .../gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 - example/ios/Supporting Files/gradlew | 172 -- .../ios/Supporting Files/settings.gradle.kts | 61 - example/readme.md | 81 - {newexample => example}/settings.gradle.kts | 0 example/shared/build.gradle.kts | 63 +- .../example/shared/di/DependencyInjection.kt | 0 .../BottomSheetParentSubPageViewModel.kt | 36 - .../bottomSheet/BottomSheetParentViewModel.kt | 42 - .../BottomSheetSubPageViewModel.kt | 41 - .../bottomSheet/BottomSheetViewModel.kt | 42 - .../compose/contacts/model/ContactDetails.kt | 23 - .../viewModel/ContactDetailsViewModel.kt | 48 - .../viewModel/ContactsListViewModel.kt | 60 - .../permissions/NotificationOptions.kt | 0 .../src/commonMain/kotlin/AlertViewModel.kt | 96 - .../src/commonMain/kotlin/HelloShared.kt | 23 - .../src/commonMain/kotlin/HudViewModel.kt | 51 - .../example/shared/di/DependencyInjection.kt | 0 .../shared/model/contacts/ContactDetails.kt | 0 .../example/shared/stylable/ButtonStyles.kt | 0 .../example/shared/stylable/TextStyles.kt | 0 .../shared}/viewmodel/ExampleViewModel.kt | 0 .../shared/viewmodel/alert/AlertViewModel.kt | 0 .../ArchitectureDetailsViewModel.kt | 0 .../ArchitectureInputViewModel.kt | 0 .../beacons/BeaconsListBeaconViewModel.kt | 0 .../viewmodel/beacons/BeaconsListViewModel.kt | 0 .../BluetoothCharacteristicViewModel.kt | 0 .../bluetooth/BluetoothDescriptorViewModel.kt | 0 .../BluetoothDeviceDetailViewModel.kt | 0 .../bluetooth/BluetoothListDeviceViewModel.kt | 0 .../bluetooth/BluetoothListViewModel.kt | 0 .../bluetooth/BluetoothServiceViewModel.kt | 0 .../BottomSheetParentSubPageViewModel.kt | 0 .../bottomsheet/BottomSheetParentViewModel.kt | 0 .../BottomSheetSubPageViewModel.kt | 0 .../bottomsheet/BottomSheetViewModel.kt | 0 .../contacts/ContactDetailsViewModel.kt | 0 .../contacts/ContactsListViewModel.kt | 0 .../datetimepicker/DateTimePickerViewModel.kt | 0 .../featureList/FeatureListViewModel.kt | 0 .../viewmodel/featureList/PlatformSpecific.kt | 0 .../shared/viewmodel/hud/HudViewModel.kt | 0 .../shared/viewmodel/info/InfoViewModel.kt | 0 .../viewmodel/keyboard/KeyboardViewModel.kt | 0 .../shared/viewmodel/link/LinksViewModel.kt | 0 .../viewmodel/location/LocationViewModel.kt | 0 .../permissions/PermissionViewModel.kt | 0 .../permissions/PermissionsListViewModel.kt | 0 .../viewmodel/resources/ButtonViewModel.kt | 0 .../viewmodel/resources/ColorViewModel.kt | 0 .../viewmodel/resources/LabelViewModel.kt | 0 .../resources/ResourcesListViewModel.kt | 0 .../viewmodel/system/SystemViewModel.kt | 0 .../system/network/NetworkViewModel.kt | 0 .../kotlin/stylable/ButtonStyles.kt | 224 --- .../commonMain/kotlin/stylable/TextStyles.kt | 37 - .../ArchitectureInputViewModel.kt | 85 - .../viewmodel/beacons/BeaconsListViewModel.kt | 76 - .../BluetoothDeviceDetailViewModel.kt | 107 - .../bluetooth/BluetoothListDeviceViewModel.kt | 139 -- .../bluetooth/BluetoothListViewModel.kt | 75 - .../kotlin/viewmodel/info/InfoViewModel.kt | 115 -- .../kotlin/viewmodel/link/LinksViewModel.kt | 115 -- .../viewmodel/location/LocationViewModel.kt | 61 - .../permissions/PermissionViewModel.kt | 61 - .../permissions/PermissionsListViewModel.kt | 66 - .../viewmodel/system/SystemViewModel.kt | 54 - .../system/network/NetworkViewModel.kt | 64 - .../example/shared/di/DependencyInjection.kt | 0 .../shared/viewmodel/info/InfoNavigator.kt | 0 .../shared/viewmodel/link/BrowserNavigator.kt | 0 .../permissions/NotificationOptions.kt | 0 .../example/shared/di/DependencyInjection.kt | 0 .../permissions/NotificationOptions.kt | 0 .../example/shared/di/DependencyInjection.kt | 0 .../permissions/NotificationOptions.kt | 0 example/web/.firebaserc | 17 - example/web/.gitignore | 66 - example/web/firebase.json | 43 - .../.well-known/apple-app-site-association | 11 - .../web/public/.well-known/assetlinks.json | 12 - example/web/public/404.html | 33 - example/web/public/index.html | 29 - example/web/public/style.css | 3 - .../android/src/main/AndroidManifest.xml | 117 -- .../splendo/kaluga/example/ExampleActivity.kt | 76 - .../kaluga/example/ExampleApplication.kt | 33 - .../kaluga/example/FeaturesListFragment.kt | 112 -- .../splendo/kaluga/example/InfoFragment.kt | 121 -- .../kaluga/example/alerts/AlertsActivity.kt | 42 - .../ArchitectureDetailsActivity.kt | 64 - .../architecture/ArchitectureInputActivity.kt | 75 - .../kaluga/example/beacons/BeaconsActivity.kt | 81 - .../kaluga/example/beacons/BeaconsAdapter.kt | 64 - .../kaluga/example/beacons/BeaconsBinding.kt | 31 - .../example/bluetooth/BluetoothActivity.kt | 93 - .../example/bluetooth/BluetoothAdapter.kt | 76 - .../BluetoothCharacteristicAdapter.kt | 78 - .../bluetooth/BluetoothDescriptorAdapter.kt | 77 - .../bluetooth/BluetoothMoreActivity.kt | 48 - .../bluetooth/BluetoothServiceAdapter.kt | 78 - .../datetimepicker/DateTimePickerActivity.kt | 21 - .../keyboard/KeyboardManagerActivity.kt | 46 - .../kaluga/example/link/LinksActivity.kt | 78 - .../kaluga/example/loading/LoadingActivity.kt | 45 - .../example/location/LocationActivity.kt | 61 - .../location/LocationBackgroundService.kt | 103 - .../permissions/PermissionsDemoActivity.kt | 69 - .../PermissionsDemoListActivity.kt | 81 - .../PlatformSpecificActivity.kt | 90 - .../example/resources/ButtonActivity.kt | 79 - .../kaluga/example/resources/ColorActivity.kt | 139 -- .../kaluga/example/resources/LabelActivity.kt | 77 - .../example/resources/ResourcesActivity.kt | 75 - .../kaluga/example/system/SystemActivity.kt | 89 - .../kaluga/example/view/ItemDecoration.kt | 33 - .../src/main/res/drawable/ic_account.xml | 5 - .../res/drawable/ic_launcher_foreground.xml | 72 - .../main/res/drawable/ic_refresh_circle.xml | 8 - .../src/main/res/drawable/ic_stop_circle.xml | 8 - .../src/main/res/layout/activity_alerts.xml | 39 - .../layout/activity_architecture_details.xml | 43 - .../layout/activity_architecture_input.xml | 63 - .../src/main/res/layout/activity_beacons.xml | 30 - .../main/res/layout/activity_bluetooth.xml | 29 - .../res/layout/activity_bluetooth_more.xml | 93 - .../res/layout/activity_date_time_picker.xml | 50 - .../src/main/res/layout/activity_example.xml | 24 - .../res/layout/activity_keyboard_manager.xml | 32 - .../src/main/res/layout/activity_link.xml | 53 - .../src/main/res/layout/activity_loading.xml | 27 - .../src/main/res/layout/activity_location.xml | 44 - .../src/main/res/layout/activity_main.xml | 19 - .../res/layout/activity_permissions_demo.xml | 30 - .../res/layout/activity_permissions_list.xml | 9 - .../main/res/layout/activity_resources.xml | 18 - .../res/layout/activity_resources_color.xml | 166 -- .../src/main/res/layout/activity_system.xml | 33 - .../src/main/res/layout/beacon_item.xml | 53 - .../layout/bluetooth_characteristic_item.xml | 69 - .../res/layout/bluetooth_descriptor_item.xml | 42 - .../src/main/res/layout/bluetooth_item.xml | 159 -- .../res/layout/bluetooth_service_item.xml | 61 - .../src/main/res/layout/dialog_info.xml | 27 - .../res/layout/fragment_features_list.xml | 9 - .../src/main/res/layout/fragment_info.xml | 9 - .../res/layout/loading_indicator_view_.xml | 25 - .../src/main/res/layout/view_list_button.xml | 10 - .../main/res/layout/view_list_text_view.xml | 14 - .../layout/view_resource_list_background.xml | 7 - .../src/main/res/menu/bluetooth_menu.xml | 18 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2147 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4049 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1422 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2536 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2990 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 5845 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 4620 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 9165 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 6296 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 12899 -> 0 bytes .../src/main/res/values-night/colors.xml | 7 - .../src/main/res/values-night/styles.xml | 11 - .../android/src/main/res/values/colors.xml | 30 - .../android/src/main/res/values/dimens.xml | 23 - .../res/values/ic_launcher_background.xml | 4 - .../android/src/main/res/values/strings.xml | 112 -- .../android/src/main/res/values/styles.xml | 30 - newexample/ios/Demo.xcodeproj/project.pbxproj | 867 --------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/IDETemplateMacros.plist | 22 - .../xcshareddata/xcschemes/Demo.xcscheme | 100 - .../xcshareddata/xcschemes/DemoTests.xcscheme | 52 - .../xcschemes/DemoUITests.xcscheme | 52 - .../Demo/Alerts/AlertsViewController.swift | 47 - newexample/ios/Demo/AppDelegate.swift | 82 - .../ArchitectureDetailsViewController.swift | 82 - .../ArchitectureInputViewController.swift | 109 -- .../AppIcon.appiconset/AppIcon20@2x~iPad.png | Bin 1340 -> 0 bytes .../AppIcon20@2x~iPhone.png | Bin 1340 -> 0 bytes .../AppIcon20@3x~iPhone.png | Bin 2053 -> 0 bytes .../AppIcon.appiconset/AppIcon20~iPad.png | Bin 667 -> 0 bytes .../AppIcon.appiconset/AppIcon29@1x~iPad.png | Bin 927 -> 0 bytes .../AppIcon.appiconset/AppIcon29@2x~iPad.png | Bin 2021 -> 0 bytes .../AppIcon29@2x~iPhone.png | Bin 2021 -> 0 bytes .../AppIcon29@3x~iPhone.png | Bin 3078 -> 0 bytes .../AppIcon.appiconset/AppIcon40@2x~iPad.png | Bin 2895 -> 0 bytes .../AppIcon40@2x~iPhone.png | Bin 2895 -> 0 bytes .../AppIcon40@3x~iPhone.png | Bin 3972 -> 0 bytes .../AppIcon.appiconset/AppIcon40~iPad.png | Bin 1340 -> 0 bytes .../AppIcon60@2x~iPhone.png | Bin 3972 -> 0 bytes .../AppIcon60@3x~iPhone.png | Bin 5454 -> 0 bytes .../AppIcon.appiconset/AppIcon76@2x~iPad.png | Bin 4925 -> 0 bytes .../AppIcon.appiconset/AppIcon76~iPad.png | Bin 2700 -> 0 bytes .../AppIcon.appiconset/AppIcon83@2x~iPad.png | Bin 5338 -> 0 bytes .../AppIcon~iOS-marketing.png | Bin 58518 -> 0 bytes .../AppIcon.appiconset/Contents.json | 116 -- .../ios/Demo/Assets.xcassets/Contents.json | 6 - .../close_icon.imageset/Contents.json | 15 - .../close_icon.imageset/close-24px.pdf | Bin 4345 -> 0 bytes .../darker_color.colorset/Contents.json | 38 - .../error_icon.imageset/Contents.json | 15 - .../error_icon.imageset/error-black-18dp.pdf | Bin 1020 -> 0 bytes .../li_colorAccent.colorset/Contents.json | 20 - .../li_colorBackground.colorset/Contents.json | 20 - .../Demo/Base.lproj/LaunchScreen.storyboard | 27 - .../ios/Demo/Base.lproj/Localizable.strings | 102 - .../ios/Demo/Base.lproj/Main.storyboard | 1727 ----------------- .../ios/Demo/Beacons/BeaconsViewCell.swift | 52 - .../Demo/Beacons/BeaconsViewController.swift | 111 -- .../Bluetooth/BluetoothCharacteristicCell.xib | 85 - .../Bluetooth/BluetoothDescriptorCell.xib | 53 - ...BluetoothDeviceDetailsViewController.swift | 363 ---- .../Bluetooth/BluetoothViewController.swift | 233 --- .../DateTimePickerViewController.swift | 56 - newexample/ios/Demo/Demo.entitlements | 10 - .../ios/Demo/ExampleViewController.swift | 86 - .../FeaturesListViewController.swift | 104 - .../ios/Demo/Helpers/UIControl+Closure.swift | 38 - newexample/ios/Demo/Info.plist | 82 - .../ios/Demo/Info/InfoViewController.swift | 104 - .../KeyboardManagerViewController.swift | 50 - .../ios/Demo/Links/LinksViewController.swift | 57 - .../ActivityViewController.swift | 20 - .../ActivityViewController.xib | 65 - .../LoadingViewController.swift | 44 - .../Location/LocationViewController.swift | 54 - .../PermissionListViewController.swift | 87 - .../PermissionViewController.swift | 85 - .../Demo/Resources/ButtonViewController.swift | 68 - .../Demo/Resources/ColorViewController.swift | 202 -- .../Demo/Resources/LabelViewController.swift | 69 - .../ResourcesListViewController.swift | 94 - .../Network/NetworkViewController.swift | 47 - .../Demo/System/SystemViewController.swift | 87 - ...omaticHeightCollectionViewFlowLayout.swift | 51 - newexample/ios/DemoTests/DemoTests.swift | 46 - newexample/ios/DemoTests/Info.plist | 22 - newexample/ios/DemoUITests/DemoUITests.swift | 44 - newexample/ios/DemoUITests/Info.plist | 22 - newexample/keystore/debug.keystore | Bin 2419 -> 0 bytes newexample/shared/build.gradle.kts | 57 - .../src/androidLibMain/AndroidManifest.xml | 6 - .../viewmodel/featureList/PlatformSpecific.kt | 57 - .../shared/viewmodel/ExampleViewModel.kt | 62 - .../ArchitectureDetailsViewModel.kt | 57 - .../beacons/BeaconsListBeaconViewModel.kt | 47 - .../BluetoothCharacteristicViewModel.kt | 75 - .../bluetooth/BluetoothDescriptorViewModel.kt | 60 - .../bluetooth/BluetoothServiceViewModel.kt | 64 - .../datetimepicker/DateTimePickerViewModel.kt | 57 - .../featureList/FeatureListViewModel.kt | 97 - .../viewmodel/featureList/PlatformSpecific.kt | 20 - .../viewmodel/keyboard/KeyboardViewModel.kt | 35 - .../viewmodel/resources/ButtonViewModel.kt | 86 - .../viewmodel/resources/ColorViewModel.kt | 192 -- .../viewmodel/resources/LabelViewModel.kt | 120 -- .../resources/ResourcesListViewModel.kt | 37 - .../viewmodel/featureList/PlatformSpecific.kt | 20 - .../viewmodel/featureList/PlatformSpecific.kt | 20 - .../viewmodel/featureList/PlatformSpecific.kt | 20 - 366 files changed, 589 insertions(+), 15902 deletions(-) rename {newexample => example}/.gitignore (100%) delete mode 100644 example/android/build.gradle rename {newexample => example}/android/build.gradle.kts (100%) delete mode 100644 example/android/proguard-rules.pro rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt (100%) rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt (100%) delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/BottomSheetActivity.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentSubPageLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetSubPageLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/viewModel/NavigationMappers.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ContactsActivity.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactDetailsLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsListLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/Styles.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/viewModel/NavigationMappers.kt rename {newexample => example}/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt (100%) delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt rename {newexample => example}/android/src/main/res/layout/activity_network.xml (100%) delete mode 100644 example/android/src/main/res/layout/fragment_network.xml rename {newexample => example}/build.gradle.kts (100%) rename {newexample => example}/gradle.properties (100%) rename {newexample => example}/gradle/wrapper/gradle-wrapper.jar (100%) rename {newexample => example}/gradle/wrapper/gradle-wrapper.properties (100%) rename {newexample => example}/gradlew (100%) rename {newexample => example}/gradlew.bat (100%) delete mode 100644 example/ios/.idea/inspectionProfiles/Project_Default.xml delete mode 100644 example/ios/.idea/runConfigurations/Demo.xml delete mode 100644 example/ios/KotlinNativeFramework/Info.plist delete mode 100644 example/ios/KotlinNativeFramework/build.gradle.kts delete mode 100644 example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt delete mode 100644 example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt delete mode 100644 example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt delete mode 100644 example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt delete mode 100644 example/ios/KotlinNativeFramework/src/commonMain/kotlin/location/KNLocationFramework.kt delete mode 100644 example/ios/KotlinNativeFramework/src/commonMain/kotlin/permissions/KNPermissionsFramework.kt delete mode 100644 example/ios/Supporting Files/.idea/codeStyles/Project.xml delete mode 100644 example/ios/Supporting Files/.idea/codeStyles/codeStyleConfig.xml delete mode 100644 example/ios/Supporting Files/.idea/copyright/apache.xml delete mode 100644 example/ios/Supporting Files/.idea/copyright/profiles_settings.xml delete mode 100644 example/ios/Supporting Files/.run/Ktlint Check.run.xml delete mode 100644 example/ios/Supporting Files/.run/Ktlint Format.run.xml delete mode 100644 example/ios/Supporting Files/build.gradle.kts delete mode 100644 example/ios/Supporting Files/gradle.properties delete mode 100644 example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.jar delete mode 100644 example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.properties delete mode 100755 example/ios/Supporting Files/gradlew delete mode 100644 example/ios/Supporting Files/settings.gradle.kts delete mode 100644 example/readme.md rename {newexample => example}/settings.gradle.kts (100%) rename {newexample => example}/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt (100%) delete mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentSubPageViewModel.kt delete mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentViewModel.kt delete mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetSubPageViewModel.kt delete mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetViewModel.kt delete mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/model/ContactDetails.kt delete mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactDetailsViewModel.kt delete mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactsListViewModel.kt rename {newexample => example}/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt (100%) delete mode 100644 example/shared/src/commonMain/kotlin/AlertViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/HelloShared.kt delete mode 100644 example/shared/src/commonMain/kotlin/HudViewModel.kt rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/ExampleViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/architecture/ArchitectureDetailsViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/beacons/BeaconsListBeaconViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/bluetooth/BluetoothServiceViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/datetimepicker/DateTimePickerViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/featureList/FeatureListViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/featureList/PlatformSpecific.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/keyboard/KeyboardViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/resources/ButtonViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/resources/ColorViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/resources/LabelViewModel.kt (100%) rename example/shared/src/commonMain/kotlin/{ => com/splendo/kaluga/example/shared}/viewmodel/resources/ResourcesListViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt (100%) rename {newexample => example}/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt (100%) delete mode 100644 example/shared/src/commonMain/kotlin/stylable/ButtonStyles.kt delete mode 100644 example/shared/src/commonMain/kotlin/stylable/TextStyles.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt rename {newexample => example}/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt (100%) rename {newexample => example}/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt (100%) rename {newexample => example}/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt (100%) rename {newexample => example}/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt (100%) rename {newexample => example}/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt (100%) rename {newexample => example}/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt (100%) rename {newexample => example}/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt (100%) rename {newexample => example}/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt (100%) delete mode 100644 example/web/.firebaserc delete mode 100644 example/web/.gitignore delete mode 100644 example/web/firebase.json delete mode 100644 example/web/public/.well-known/apple-app-site-association delete mode 100644 example/web/public/.well-known/assetlinks.json delete mode 100644 example/web/public/404.html delete mode 100644 example/web/public/index.html delete mode 100644 example/web/public/style.css delete mode 100644 newexample/android/src/main/AndroidManifest.xml delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt delete mode 100644 newexample/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt delete mode 100644 newexample/android/src/main/res/drawable/ic_account.xml delete mode 100644 newexample/android/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 newexample/android/src/main/res/drawable/ic_refresh_circle.xml delete mode 100644 newexample/android/src/main/res/drawable/ic_stop_circle.xml delete mode 100644 newexample/android/src/main/res/layout/activity_alerts.xml delete mode 100644 newexample/android/src/main/res/layout/activity_architecture_details.xml delete mode 100644 newexample/android/src/main/res/layout/activity_architecture_input.xml delete mode 100644 newexample/android/src/main/res/layout/activity_beacons.xml delete mode 100644 newexample/android/src/main/res/layout/activity_bluetooth.xml delete mode 100644 newexample/android/src/main/res/layout/activity_bluetooth_more.xml delete mode 100644 newexample/android/src/main/res/layout/activity_date_time_picker.xml delete mode 100644 newexample/android/src/main/res/layout/activity_example.xml delete mode 100644 newexample/android/src/main/res/layout/activity_keyboard_manager.xml delete mode 100644 newexample/android/src/main/res/layout/activity_link.xml delete mode 100644 newexample/android/src/main/res/layout/activity_loading.xml delete mode 100644 newexample/android/src/main/res/layout/activity_location.xml delete mode 100644 newexample/android/src/main/res/layout/activity_main.xml delete mode 100644 newexample/android/src/main/res/layout/activity_permissions_demo.xml delete mode 100644 newexample/android/src/main/res/layout/activity_permissions_list.xml delete mode 100644 newexample/android/src/main/res/layout/activity_resources.xml delete mode 100644 newexample/android/src/main/res/layout/activity_resources_color.xml delete mode 100644 newexample/android/src/main/res/layout/activity_system.xml delete mode 100644 newexample/android/src/main/res/layout/beacon_item.xml delete mode 100644 newexample/android/src/main/res/layout/bluetooth_characteristic_item.xml delete mode 100644 newexample/android/src/main/res/layout/bluetooth_descriptor_item.xml delete mode 100644 newexample/android/src/main/res/layout/bluetooth_item.xml delete mode 100644 newexample/android/src/main/res/layout/bluetooth_service_item.xml delete mode 100644 newexample/android/src/main/res/layout/dialog_info.xml delete mode 100644 newexample/android/src/main/res/layout/fragment_features_list.xml delete mode 100644 newexample/android/src/main/res/layout/fragment_info.xml delete mode 100644 newexample/android/src/main/res/layout/loading_indicator_view_.xml delete mode 100644 newexample/android/src/main/res/layout/view_list_button.xml delete mode 100644 newexample/android/src/main/res/layout/view_list_text_view.xml delete mode 100644 newexample/android/src/main/res/layout/view_resource_list_background.xml delete mode 100644 newexample/android/src/main/res/menu/bluetooth_menu.xml delete mode 100644 newexample/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 newexample/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 newexample/android/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 newexample/android/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 newexample/android/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 newexample/android/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 newexample/android/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 newexample/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 newexample/android/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 newexample/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 newexample/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 newexample/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 newexample/android/src/main/res/values-night/colors.xml delete mode 100644 newexample/android/src/main/res/values-night/styles.xml delete mode 100644 newexample/android/src/main/res/values/colors.xml delete mode 100644 newexample/android/src/main/res/values/dimens.xml delete mode 100644 newexample/android/src/main/res/values/ic_launcher_background.xml delete mode 100644 newexample/android/src/main/res/values/strings.xml delete mode 100644 newexample/android/src/main/res/values/styles.xml delete mode 100644 newexample/ios/Demo.xcodeproj/project.pbxproj delete mode 100644 newexample/ios/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 newexample/ios/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 newexample/ios/Demo.xcodeproj/xcshareddata/IDETemplateMacros.plist delete mode 100644 newexample/ios/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme delete mode 100644 newexample/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoTests.xcscheme delete mode 100644 newexample/ios/Demo.xcodeproj/xcshareddata/xcschemes/DemoUITests.xcscheme delete mode 100644 newexample/ios/Demo/Alerts/AlertsViewController.swift delete mode 100644 newexample/ios/Demo/AppDelegate.swift delete mode 100644 newexample/ios/Demo/Architecture/ArchitectureDetailsViewController.swift delete mode 100644 newexample/ios/Demo/Architecture/ArchitectureInputViewController.swift delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon20@2x~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon20@2x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon20@3x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon20~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon29@1x~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon29@2x~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon29@2x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon29@3x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon40@2x~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon40@2x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon40@3x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon40~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon60@2x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon60@3x~iPhone.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon76@2x~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon76~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon83@2x~iPad.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/AppIcon~iOS-marketing.png delete mode 100644 newexample/ios/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 newexample/ios/Demo/Assets.xcassets/Contents.json delete mode 100644 newexample/ios/Demo/Assets.xcassets/close_icon.imageset/Contents.json delete mode 100644 newexample/ios/Demo/Assets.xcassets/close_icon.imageset/close-24px.pdf delete mode 100644 newexample/ios/Demo/Assets.xcassets/darker_color.colorset/Contents.json delete mode 100644 newexample/ios/Demo/Assets.xcassets/error_icon.imageset/Contents.json delete mode 100644 newexample/ios/Demo/Assets.xcassets/error_icon.imageset/error-black-18dp.pdf delete mode 100644 newexample/ios/Demo/Assets.xcassets/li_colorAccent.colorset/Contents.json delete mode 100644 newexample/ios/Demo/Assets.xcassets/li_colorBackground.colorset/Contents.json delete mode 100644 newexample/ios/Demo/Base.lproj/LaunchScreen.storyboard delete mode 100644 newexample/ios/Demo/Base.lproj/Localizable.strings delete mode 100644 newexample/ios/Demo/Base.lproj/Main.storyboard delete mode 100644 newexample/ios/Demo/Beacons/BeaconsViewCell.swift delete mode 100644 newexample/ios/Demo/Beacons/BeaconsViewController.swift delete mode 100644 newexample/ios/Demo/Bluetooth/BluetoothCharacteristicCell.xib delete mode 100644 newexample/ios/Demo/Bluetooth/BluetoothDescriptorCell.xib delete mode 100644 newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift delete mode 100644 newexample/ios/Demo/Bluetooth/BluetoothViewController.swift delete mode 100644 newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift delete mode 100644 newexample/ios/Demo/Demo.entitlements delete mode 100644 newexample/ios/Demo/ExampleViewController.swift delete mode 100644 newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift delete mode 100644 newexample/ios/Demo/Helpers/UIControl+Closure.swift delete mode 100644 newexample/ios/Demo/Info.plist delete mode 100644 newexample/ios/Demo/Info/InfoViewController.swift delete mode 100644 newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift delete mode 100644 newexample/ios/Demo/Links/LinksViewController.swift delete mode 100644 newexample/ios/Demo/LoadingIndicator/ActivityViewController.swift delete mode 100644 newexample/ios/Demo/LoadingIndicator/ActivityViewController.xib delete mode 100644 newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift delete mode 100644 newexample/ios/Demo/Location/LocationViewController.swift delete mode 100644 newexample/ios/Demo/Permissions/PermissionListViewController.swift delete mode 100644 newexample/ios/Demo/Permissions/PermissionViewController.swift delete mode 100644 newexample/ios/Demo/Resources/ButtonViewController.swift delete mode 100644 newexample/ios/Demo/Resources/ColorViewController.swift delete mode 100644 newexample/ios/Demo/Resources/LabelViewController.swift delete mode 100644 newexample/ios/Demo/Resources/ResourcesListViewController.swift delete mode 100644 newexample/ios/Demo/System/Network/NetworkViewController.swift delete mode 100644 newexample/ios/Demo/System/SystemViewController.swift delete mode 100644 newexample/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift delete mode 100644 newexample/ios/DemoTests/DemoTests.swift delete mode 100644 newexample/ios/DemoTests/Info.plist delete mode 100644 newexample/ios/DemoUITests/DemoUITests.swift delete mode 100644 newexample/ios/DemoUITests/Info.plist delete mode 100644 newexample/keystore/debug.keystore delete mode 100644 newexample/shared/build.gradle.kts delete mode 100644 newexample/shared/src/androidLibMain/AndroidManifest.xml delete mode 100644 newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt delete mode 100644 newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt delete mode 100644 newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt delete mode 100644 newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt delete mode 100644 newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt diff --git a/newexample/.gitignore b/example/.gitignore similarity index 100% rename from newexample/.gitignore rename to example/.gitignore diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index 9d15f9d28..000000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,92 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.kapt' - id 'org.jlleitschuh.gradle.ktlint' - id "org.jetbrains.kotlin.plugin.serialization" -} - -//noinspection UnnecessaryQualifiedReference -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - jvmTarget = "1.8" - } -} - -android { - - compileSdkVersion gradle.android_compile_sdk_version - buildToolsVersion gradle.android_build_tools_version - defaultConfig { - applicationId "com.splendo.kaluga.example" - minSdkVersion gradle.android_min_sdk_version - targetSdkVersion gradle.android_target_sdk_version - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - signingConfigs { - debug { - keyAlias "key0" - keyPassword "nckI1UYofHIMkOnXpmZJVA" - storeFile file("../keystore/debug.keystore") - storePassword "nckI1UYofHIMkOnXpmZJVA" - } - } - - buildTypes { - debug { - signingConfig signingConfigs.debug - } - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - packagingOptions { - exclude 'META-INF/kotlinx-coroutines-core.kotlin_module' - exclude 'META-INF/shared_debug.kotlin_module' - exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module' - } - - buildFeatures.dataBinding = true -} - -kotlin { - sourceSets.all { - languageSettings { - optIn 'kotlin.ExperimentalStdlibApi' - } - } -} - -gradle.ext.component_type = gradle.ext.component_type_composeApp - -apply from: "../../gradle/android_compose.gradle.kts" - -gradle.ext.component_type = gradle.ext.component_type_default - -dependencies { - implementation "com.splendo.kaluga:architecture-compose:$gradle.ext.library_version" - implementation "com.splendo.kaluga:resources-compose:$gradle.ext.library_version" - implementation project(':shared') - - implementation "androidx.fragment:fragment-ktx:$gradle.androidx_fragment_version" - implementation "com.google.android.gms:play-services-location:$gradle.play_services_version" - implementation "com.google.android.material:material:$gradle.material_version" - implementation "androidx.constraintlayout:constraintlayout:$gradle.androidx_constraint_layout_version" - - implementation "io.insert-koin:koin-android:$gradle.koin_version" - implementation "androidx.lifecycle:lifecycle-service:$gradle.androidx_lifecycle_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$gradle.kotlinx_coroutines_version!!" - - implementation "androidx.compose.material:material:$gradle.androidx_compose_version" - implementation "androidx.navigation:navigation-compose:$gradle.androidx_navigation_compose_version" - implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:$gradle.serialization_version" - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$gradle.serialization_version" - - implementation "com.google.android.material:material:$gradle.material_version" - implementation "com.google.android.material:compose-theme-adapter:$gradle.material_components_adapter_version" -} diff --git a/newexample/android/build.gradle.kts b/example/android/build.gradle.kts similarity index 100% rename from newexample/android/build.gradle.kts rename to example/android/build.gradle.kts diff --git a/example/android/proguard-rules.pro b/example/android/proguard-rules.pro deleted file mode 100644 index 2f9dc5a47..000000000 --- a/example/android/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle.kts. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/example/android/src/main/AndroidManifest.xml b/example/android/src/main/AndroidManifest.xml index 3f7425dfa..2cd6b95de 100644 --- a/example/android/src/main/AndroidManifest.xml +++ b/example/android/src/main/AndroidManifest.xml @@ -44,8 +44,8 @@ android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> + android:label="Features list" + android:exported="true"> @@ -53,9 +53,9 @@ + android:label="@string/feature_location"/> + android:label="@string/permissions_list"/> @@ -86,7 +86,7 @@ android:label="@string/feature_platform_specific"/> - + diff --git a/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt b/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt index 4b3b334fe..7622e5ab7 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt @@ -20,10 +20,7 @@ package com.splendo.kaluga.example import android.app.Application import com.splendo.kaluga.base.ApplicationHolder -import com.splendo.kaluga.example.di.utilitiesModule -import com.splendo.kaluga.example.di.viewModelModule -import org.koin.android.ext.koin.androidContext -import org.koin.core.context.startKoin +import com.splendo.kaluga.example.shared.di.initKoin class ExampleApplication : Application() { @@ -31,9 +28,6 @@ class ExampleApplication : Application() { super.onCreate() ApplicationHolder.application = this - startKoin { - androidContext(this@ExampleApplication) - modules(utilitiesModule, viewModelModule) - } + initKoin() } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt index 0520e4b2f..7f2d19139 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt @@ -29,11 +29,9 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment -import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpecRow +import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpec import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel -import com.splendo.kaluga.example.shared.viewmodel.info.LinkSpecRow -import com.splendo.kaluga.example.shared.viewmodel.info.MailSpecRow import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import java.net.URL @@ -45,22 +43,20 @@ class InfoFragment : KalugaViewModelFragment(R.layout.fragment_in ActivityNavigator> { action -> when (action) { is InfoNavigation.Dialog -> { - val title = action.bundle?.get(DialogSpecRow.TitleRow) ?: "" - val message = action.bundle?.get(DialogSpecRow.MessageRow) ?: "" NavigationSpec.Dialog( createDialog = { - InfoDialog(title, message) + InfoDialog(action.value) } ) } is InfoNavigation.Link -> NavigationSpec.Browser( - URL(action.bundle!!.get(LinkSpecRow.LinkRow)), + URL(action.value), NavigationSpec.Browser.Type.Normal ) is InfoNavigation.Mail -> NavigationSpec.Email( NavigationSpec.Email.EmailSettings( - to = action.bundle?.get(MailSpecRow.ToRow) ?: emptyList(), - subject = action.bundle?.get(MailSpecRow.SubjectRow) + to = action.value.to, + subject = action.value.subject ) ) } @@ -108,7 +104,7 @@ class InfoAdapter(private val viewModel: InfoViewModel) : RecyclerView.Adapter(R.id.title).text = title - v.findViewById(R.id.message).text = message + v.findViewById(R.id.title).text = dialogSpec.title + v.findViewById(R.id.message).text = dialogSpec.message return v } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt index 6b6656944..5fa52e39d 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt @@ -23,7 +23,7 @@ import android.os.Bundle import androidx.appcompat.widget.AppCompatButton import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.AlertViewModel +import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel import org.koin.androidx.viewmodel.ext.android.viewModel @SuppressLint("SetTextI18n") diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt index 879360e98..58cffa8c6 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt @@ -19,11 +19,14 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.example.architecture import android.os.Bundle +import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.toTypedProperty +import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.databinding.ActivityArchitectureDetailsBinding import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.CloseDetailsNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -37,7 +40,12 @@ class ArchitectureDetailsActivity : KalugaViewModelActivity - parametersOf(details) + parametersOf( + details, + ActivityNavigator { + NavigationSpec.Close(resultCode) + } + ) } ?: parametersOf("", 0) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt index b4e3f51fa..3c131451e 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt @@ -31,6 +31,7 @@ import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.databinding.ActivityArchitectureInputBinding import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails +import com.splendo.kaluga.example.shared.viewmodel.architecture.InputNavigation import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -48,7 +49,7 @@ class ArchitectureInputActivity : KalugaViewModelActivity> { + ActivityNavigator { NavigationSpec.Activity( launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt index d07256ba2..35a75ea35 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt @@ -28,8 +28,8 @@ import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R import com.splendo.kaluga.example.databinding.ActivityBluetoothBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListNavigation import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel +import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetails import kotlinx.coroutines.runBlocking import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -38,7 +38,7 @@ class BluetoothActivity : KalugaViewModelActivity() { override val viewModel: BluetoothListViewModel by viewModel { parametersOf( - ActivityNavigator { + ActivityNavigator { NavigationSpec.Activity() } ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt index 22b422dfd..49ce564ef 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt @@ -18,22 +18,21 @@ package com.splendo.kaluga.example.bluetooth import android.os.Bundle -import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.bluetooth.device.SerializableIdentifier import com.splendo.kaluga.example.databinding.ActivityBluetoothMoreBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpec -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpecRow import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf class BluetoothMoreActivity : KalugaViewModelActivity() { override val viewModel: BluetoothDeviceDetailViewModel by viewModel { - val deviceDetailsSpec = DeviceDetailsSpec() - intent.extras?.toNavigationBundle(deviceDetailsSpec)?.let { bundle -> - parametersOf(bundle.get(DeviceDetailsSpecRow.UUIDRow)) - } ?: parametersOf("") + parametersOf( + intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(SerializableIdentifier.serializer()))!!.identifier + ) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentSubPageLayout.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt rename to example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt rename to example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt rename to example/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt diff --git a/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt b/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt deleted file mode 100644 index f367f3c64..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt +++ /dev/null @@ -1,217 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.di - -/* ktlint-disable no-wildcard-imports */ -import com.splendo.kaluga.alerts.AlertPresenter -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.base.singleThreadDispatcher -import com.splendo.kaluga.bluetooth.Bluetooth -import com.splendo.kaluga.bluetooth.BluetoothBuilder -import com.splendo.kaluga.bluetooth.beacons.Beacons -import com.splendo.kaluga.bluetooth.scanner.BaseScanner -import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter -import com.splendo.kaluga.example.architecture.ArchitectureDetailsActivity -import com.splendo.kaluga.example.shared.AlertViewModel -import com.splendo.kaluga.example.shared.HudViewModel -import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation -import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails -import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListNavigation -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel -import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel -import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel -import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation -import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel -import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow -import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel -import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel -import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel -import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel -import com.splendo.kaluga.hud.HUD -import com.splendo.kaluga.keyboard.FocusHandler -import com.splendo.kaluga.keyboard.KeyboardManager -import com.splendo.kaluga.links.LinksBuilder -import com.splendo.kaluga.location.LocationStateRepoBuilder -import com.splendo.kaluga.logging.RestrictedLogLevel -import com.splendo.kaluga.logging.RestrictedLogger -import com.splendo.kaluga.permissions.base.BasePermissionManager -import com.splendo.kaluga.permissions.base.Permission -import com.splendo.kaluga.permissions.base.Permissions -import com.splendo.kaluga.permissions.base.PermissionsBuilder -import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.registerAllPermissions -import com.splendo.kaluga.resources.StyledStringBuilder -import com.splendo.kaluga.review.ReviewManager -import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder -import kotlin.time.Duration.Companion.minutes -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.dsl.module - -val utilitiesModule = module { - single { - Permissions( - PermissionsBuilder().apply { - registerAllPermissions( - settings = BasePermissionManager.Settings( - logger = RestrictedLogger(RestrictedLogLevel.Verbose) - ) - ) - }, - coroutineContext = singleThreadDispatcher("Permissions") - ) - } - single { LocationStateRepoBuilder() } - single { - BluetoothBuilder().create({ BaseScanner.Settings(get()) }) - } - single { Beacons(get(), timeout = 1.minutes) } -} - -val viewModelModule = module { - viewModel { (navigator: Navigator) -> - ExampleViewModel( - navigator - ) - } - - viewModel { (navigator: Navigator) -> - FeatureListViewModel( - navigator - ) - } - - viewModel { (navigator: Navigator>) -> - InfoViewModel( - ReviewManager.Builder(), - navigator - ) - } - - viewModel { (navigator: Navigator) -> - PermissionsListViewModel( - navigator - ) - } - - viewModel { (permission: Permission) -> PermissionViewModel(get(), permission) } - - viewModel { (permission: LocationPermission) -> LocationViewModel(permission, get()) } - - viewModel { (navigator: Navigator>) -> - ArchitectureInputViewModel( - navigator - ) - } - - viewModel { (initialDetail: InputDetails) -> - ArchitectureDetailsViewModel( - initialDetail, - ActivityNavigator { - NavigationSpec.Close(ArchitectureDetailsActivity.resultCode) - } - ) - } - - viewModel { - AlertViewModel(AlertPresenter.Builder()) - } - - viewModel { - DateTimePickerViewModel(DateTimePickerPresenter.Builder()) - } - - viewModel { - HudViewModel(HUD.Builder()) - } - - viewModel { (keyboardBuilder: KeyboardManager.Builder, focusHandler: FocusHandler) -> - KeyboardViewModel(keyboardBuilder, focusHandler) - } - - viewModel { (navigator: Navigator>) -> - LinksViewModel( - LinksBuilder(), - AlertPresenter.Builder(), - navigator - ) - } - - viewModel { (navigator: Navigator>) -> - SystemViewModel( - navigator - ) - } - - viewModel { - NetworkViewModel(NetworkStateRepoBuilder(get())) - } - - viewModel { (navigator: Navigator) -> - BluetoothListViewModel( - get(), - navigator - ) - } - - viewModel { (identifier: com.splendo.kaluga.bluetooth.device.Identifier) -> - BluetoothDeviceDetailViewModel(get(), identifier) - } - - viewModel { - BeaconsListViewModel(get()) - } - - viewModel { (navigator: Navigator) -> - ResourcesListViewModel( - navigator - ) - } - - viewModel { - ColorViewModel(AlertPresenter.Builder()) - } - - viewModel { - LabelViewModel(StyledStringBuilder.Provider()) - } - - viewModel { - ButtonViewModel(StyledStringBuilder.Provider(), AlertPresenter.Builder()) - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt index 485777505..846c75ea4 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt @@ -25,7 +25,6 @@ import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R import com.splendo.kaluga.example.databinding.ActivityLinkBinding import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -35,10 +34,10 @@ class LinksActivity : KalugaViewModelActivity(R.layout.activity_ override val viewModel: LinksViewModel by viewModel { parametersOf( - ActivityNavigator> { + ActivityNavigator> { when (it) { is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser( - URL(it.bundle!!.get(BrowserSpecRow.UrlSpecRow)), + URL(it.value), NavigationSpec.Browser.Type.Normal ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt index 1e6ea8bcf..11468040e 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt @@ -23,7 +23,7 @@ import android.os.Bundle import androidx.appcompat.widget.AppCompatButton import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.HudViewModel +import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel import org.koin.androidx.viewmodel.ext.android.viewModel @SuppressLint("SetTextI18n") diff --git a/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt b/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt index d7cd3d96e..27a852cd7 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt @@ -28,10 +28,8 @@ import androidx.core.app.NotificationManagerCompat import com.splendo.kaluga.example.R import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel import com.splendo.kaluga.permissions.location.LocationPermission -import org.koin.core.component.KoinComponent -import org.koin.core.component.get -class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinComponent { +class LocationBackgroundService : androidx.lifecycle.LifecycleService() { companion object { const val notificationId = 1 @@ -43,7 +41,7 @@ class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinCom private val notificationService by lazy { applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } - private val viewModel = LocationViewModel(permission, get()) + private val viewModel = LocationViewModel(permission) override fun onCreate() { super.onCreate() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt index ca92bc4cd..aee8b70c2 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt @@ -22,11 +22,10 @@ import android.view.View import android.widget.TextView import android.widget.Toast import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpec -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpecRow import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission @@ -43,29 +42,15 @@ import org.koin.core.parameter.parametersOf class PermissionsDemoActivity : KalugaViewModelActivity(R.layout.activity_permissions_demo) { override val viewModel: PermissionViewModel by viewModel { - val permissionNavSpec = PermissionNavigationBundleSpec() - intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle -> - val permission = when (bundle.get(PermissionNavigationBundleSpecRow)) { - PermissionView.Bluetooth -> BluetoothPermission - PermissionView.Calendar -> CalendarPermission(allowWrite = true) - PermissionView.Camera -> CameraPermission - PermissionView.Contacts -> ContactsPermission(allowWrite = true) - PermissionView.Location -> LocationPermission(background = true, precise = true) - PermissionView.Microphone -> MicrophonePermission - PermissionView.Notifications -> NotificationsPermission() - PermissionView.Storage -> StoragePermission(allowWrite = true) - } - parametersOf(permission) + intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.let { permissionView -> + parametersOf(permissionView.permission) } ?: parametersOf() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val permissionNavSpec = PermissionNavigationBundleSpec() - intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle -> - supportActionBar?.title = bundle.get(PermissionNavigationBundleSpecRow).title - } + supportActionBar?.title = intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.title viewModel.permissionStateMessage.observe { findViewById(R.id.permissions_message).text = it diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt index a8a03b3ac..e47f80400 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt @@ -34,9 +34,9 @@ import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeA import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.BottomSheetActivity -import com.splendo.kaluga.example.platformspecific.compose.contacts.ContactsActivity -import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding +import com.splendo.kaluga.example.bottomSheet.BottomSheetActivity +import com.splendo.kaluga.example.contacts.ContactsActivity +import com.splendo.kaluga.example.contacts.ui.Padding import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformFeatureListNavigationAction import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformSpecificFeaturesViewModel @@ -51,8 +51,10 @@ class PlatformSpecificActivity : KalugaViewModelComposeActivity NavigationSpec.Activity(ContactsActivity::class.java) - is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity(BottomSheetActivity::class.java) + is PlatformFeatureListNavigationAction.ComposeNavigation -> NavigationSpec.Activity( + ContactsActivity::class.java) + is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity( + BottomSheetActivity::class.java) } @Composable diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/BottomSheetActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/BottomSheetActivity.kt deleted file mode 100644 index 9f9b90ab8..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/BottomSheetActivity.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.bottomSheet - -import androidx.compose.runtime.Composable -import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui.BottomSheetParentLayout - -class BottomSheetActivity : KalugaViewModelComposeActivity() { - override val viewModel = BaseLifecycleViewModel() - - @Composable - override fun Layout(viewModel: BaseLifecycleViewModel) { - BottomSheetParentLayout() - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetLayout.kt deleted file mode 100644 index f91d50da7..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetLayout.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetNavigationRouteMapper -import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetViewModel - -@Composable -fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetNavigationRouteMapper, - ) - - val viewModel = store { - remember { - BottomSheetViewModel(navigator) - } - } - - ViewModelComposable(viewModel) { - HardwareBackButtonNavigation(onBackButtonClickHandler = { onClosePressed() }) - Column( - Modifier - .fillMaxWidth() - .padding(Padding.default) - ) { - Text(text) - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Padding.default), - onClick = { onSubPagePressed() } - ) { - Text(buttonText) - } - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentLayout.kt deleted file mode 100644 index c0e9c1df9..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentLayout.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.Text -import androidx.compose.material.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRouteController -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetSheetContentRouteController -import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator -import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteController -import com.splendo.kaluga.architecture.compose.navigation.NavigatingModalBottomSheetLayout -import com.splendo.kaluga.architecture.compose.navigation.route -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetParentNavigationRouteMapper -import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentViewModel - -@Composable -fun BottomSheetParentLayout() { - MdcTheme { - val bottomSheetRouteController = BottomSheetRouteController( - NavHostRouteController(rememberNavController()), - BottomSheetSheetContentRouteController( - rememberNavController(), - rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), - rememberCoroutineScope() - ) - ) - - bottomSheetRouteController.NavigatingModalBottomSheetLayout( - sheetContent = { contentNavHostController, sheetContentNavHostController, sheetState -> - composable(BottomSheetParentNavigation.ShowSheet.route()) { - BottomSheetLayout(contentNavHostController, sheetContentNavHostController, sheetState) - } - composable(BottomSheetNavigation.SubPage.route()) { - BottomSheetSubPageLayout( - contentNavHostController, sheetContentNavHostController, sheetState - ) - } - }, - contentRoot = { contentNavHostController, sheetContentNavHostController, sheetState -> - BottomSheetParentLayoutContent(contentNavHostController, sheetContentNavHostController, sheetState) - }, - content = { contentNavHostController, _, _ -> - composable(BottomSheetParentNavigation.SubPage.route()) { - BottomSheetParentSubPageLayout(contentNavHostController) - } - } - ) - } -} - -@Composable -fun BottomSheetParentLayoutContent(contentNavHostController: NavHostController, sheetNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - - val navigator = ModalBottomSheetNavigator( - NavHostRouteController(contentNavHostController), - BottomSheetSheetContentRouteController( - sheetNavHostController, - sheetState, - rememberCoroutineScope() - ), - ::bottomSheetParentNavigationRouteMapper - ) - - val viewModel = store { - remember { - BottomSheetParentViewModel(navigator) - } - } - - ViewModelComposable(viewModel) { - Column(Modifier.fillMaxWidth()) { - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Padding.default), - onClick = { onShowSheetPressed() } - ) { - Text(sheetText) - } - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Padding.default), - onClick = { onSubPagePressed() } - ) { - Text(subPageText) - } - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentSubPageLayout.kt deleted file mode 100644 index 1168846bb..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentSubPageLayout.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetParentSubPageNavigationRouteMapper -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentSubPageViewModel - -@Composable -fun BottomSheetParentSubPageLayout(navHostController: NavHostController) { - val navigator = RouteNavigator( - navHostController, - ::bottomSheetParentSubPageNavigationRouteMapper - ) - - val viewModel = store { - remember { - BottomSheetParentSubPageViewModel(navigator) - } - } - - ViewModelComposable(viewModel) { - HardwareBackButtonNavigation(onBackButtonClickHandler = { viewModel.onBackPressed() }) - Column(Modifier.fillMaxWidth()) { - Text(text) - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetSubPageLayout.kt deleted file mode 100644 index 27bc57c3f..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetSubPageLayout.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetSubPageNavigationRouteMapper -import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetSubPageViewModel - -@Composable -fun BottomSheetSubPageLayout( - contentNavHostController: NavHostController, - sheetContentNavHostController: NavHostController, - sheetState: ModalBottomSheetState -) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetSubPageNavigationRouteMapper - ) - - val viewModel = store { - remember { - BottomSheetSubPageViewModel(navigator) - } - } - - ViewModelComposable(viewModel) { - HardwareBackButtonNavigation(onBackButtonClickHandler = { viewModel.onBackPressed() }) - Column( - Modifier - .fillMaxWidth() - .padding(Padding.default) - ) { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - Button( - modifier = Modifier.padding(Padding.default), - onClick = { onClosePressed() } - ) { - Text("X") - } - } - Text(text) - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/viewModel/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/viewModel/NavigationMappers.kt deleted file mode 100644 index acc30ad88..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/viewModel/NavigationMappers.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel - -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRoute -import com.splendo.kaluga.architecture.compose.navigation.Route -import com.splendo.kaluga.architecture.compose.navigation.bottomSheetContent -import com.splendo.kaluga.architecture.compose.navigation.bottomSheetSheetContent -import com.splendo.kaluga.architecture.compose.navigation.next -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentSubPageNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetSubPageNavigation - -/** Maps a navigation action to a route string. */ -internal fun bottomSheetParentNavigationRouteMapper(action: BottomSheetParentNavigation): BottomSheetRoute { - return when (action) { - is BottomSheetParentNavigation.SubPage -> action.next.bottomSheetContent - is BottomSheetParentNavigation.ShowSheet -> action.next.bottomSheetSheetContent - } -} - -/** Maps a navigation action to a route string. */ -internal fun bottomSheetParentSubPageNavigationRouteMapper(action: BottomSheetParentSubPageNavigation): Route { - return when (action) { - is BottomSheetParentSubPageNavigation.Back -> Route.Back - } -} - -/** Maps a navigation action to a route string. */ -internal fun bottomSheetNavigationRouteMapper(action: BottomSheetNavigation): BottomSheetRoute { - return when (action) { - is BottomSheetNavigation.Close -> Route.Close.bottomSheetSheetContent - is BottomSheetNavigation.SubPage -> action.next.bottomSheetSheetContent - } -} - -/** Maps a navigation action to a route string. */ -internal fun bottomSheetSubPageNavigationRouteMapper(action: BottomSheetSubPageNavigation): BottomSheetRoute { - return when (action) { - is BottomSheetSubPageNavigation.Close -> Route.Close.bottomSheetSheetContent - is BottomSheetSubPageNavigation.Back -> Route.Back.bottomSheetSheetContent - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ContactsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ContactsActivity.kt deleted file mode 100644 index 1533bfd85..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ContactsActivity.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.contacts - -import androidx.compose.runtime.Composable -import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.ContactsLayout - -class ContactsActivity : KalugaViewModelComposeActivity() { - override val viewModel = BaseLifecycleViewModel() - - @Composable - override fun Layout(viewModel: BaseLifecycleViewModel) { - ContactsLayout() - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactDetailsLayout.kt deleted file mode 100644 index 1ca6ee350..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactDetailsLayout.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.contacts.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment.Companion.CenterHorizontally -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController -import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator -import com.splendo.kaluga.architecture.compose.navigation.rememberCombinedNavigator -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.platformspecific.compose.contacts.viewModel.contactDetailsNavigationActivityMapper -import com.splendo.kaluga.example.platformspecific.compose.contacts.viewModel.contactDetailsNavigationRouteMapper -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsNavigation -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsViewModel - -@Composable -fun ContactDetailsLayout(contactDetails: ContactDetails, navHostController: NavHostController) { - val routeNavigator = RouteNavigator( - navHostController, - ::contactDetailsNavigationRouteMapper - ) - - val navigator = rememberCombinedNavigator { action: ContactDetailsNavigation<*> -> - when (action) { - is ContactDetailsNavigation.Close -> routeNavigator - is ContactDetailsNavigation.SendEmail -> ::contactDetailsNavigationActivityMapper.toActivityNavigator() - } - } - - val viewModel = store { - remember { - ContactDetailsViewModel(contactDetails, navigator) - } - } - ViewModelComposable(viewModel) { - HardwareBackButtonNavigation(::back) - - Column { - val image = painterResource(id = R.drawable.ic_account) - Image( - modifier = Modifier - .size(320.dp) - .align(CenterHorizontally), - painter = image, - contentDescription = "" - ) - - Column( - modifier = Modifier.padding(horizontal = Padding.x2) - .fillMaxWidth() - ) { - Text(text = "Name:", style = MaterialTheme.typography.h6) - Text(text = contactDetails.name, style = MaterialTheme.typography.body1) - DefaultSpacer() - - Text(text = "Email:", style = MaterialTheme.typography.h6) - Text(text = contactDetails.email, style = MaterialTheme.typography.body1) - DefaultSpacer() - - Button(onClick = ::sendEmail) { - Text(text = sendEmailButtonText, style = MaterialTheme.typography.button) - } - } - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsLayout.kt deleted file mode 100644 index bed60a083..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsLayout.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.contacts.ui - -import androidx.compose.runtime.Composable -import androidx.navigation.compose.rememberNavController -import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator -import com.splendo.kaluga.architecture.compose.navigation.SetupNavHost -import com.splendo.kaluga.architecture.compose.navigation.composable -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.example.platformspecific.compose.contacts.viewModel.contactListNavigationRouteMapper -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation - -@Composable -fun ContactsLayout() { - MdcTheme { - val navigator = RouteNavigator( - rememberNavController(), - ::contactListNavigationRouteMapper - ) - - navigator.SetupNavHost( - rootView = { - ContactsListLayout(navigator = navigator) - } - ) { navHostController -> - composable( - type = NavigationBundleSpecType.SerializedType( - ContactDetails.serializer() - ) - ) { details -> - ContactDetailsLayout(details, navHostController) - } - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsListLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsListLayout.kt deleted file mode 100644 index fd313db7f..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsListLayout.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.contacts.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment.Companion.CenterVertically -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.splendo.kaluga.architecture.compose.state -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListViewModel - -@Composable -fun ContactsListLayout(navigator: Navigator>) { - val viewModel = store { - remember { - ContactsListViewModel(navigator) - } - } - - ViewModelComposable(viewModel) { - val items by contacts.state() - LazyColumn(modifier = Modifier.padding(Padding.default)) { - items(items) { contactDetails -> - ListItem(contactDetails) { - onContactClick(contactDetails) - } - - DefaultSpacer() - } - } - } -} - -@Composable -private fun ListItem( - contactDetails: ContactDetails, - onClickHandler: () -> Unit -) { - Row( - modifier = Modifier - .clickable(onClick = onClickHandler) - .fillMaxWidth() - ) { - val image = painterResource(id = R.drawable.ic_account) - Image( - modifier = Modifier.size(48.dp), - painter = image, - contentDescription = "" - ) - - DefaultSpacer() - - Text( - modifier = Modifier.align(CenterVertically), - text = contactDetails.name, - style = MaterialTheme.typography.h5 - ) - } -} - -@Composable -@Preview -private fun ListItemPreview() { - ListItem(ContactDetails("name", "email")) { } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/Styles.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/Styles.kt deleted file mode 100644 index 44e415cb7..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/Styles.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.splendo.kaluga.example.platformspecific.compose.contacts.ui - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -object Padding { - val default = 8.dp - val x2 = 16.dp -} - -@Composable -fun DefaultSpacer() { - Spacer(modifier = Modifier.size(Padding.default)) -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/viewModel/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/viewModel/NavigationMappers.kt deleted file mode 100644 index 9bd6b0092..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/viewModel/NavigationMappers.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific.compose.contacts.viewModel - -import com.splendo.kaluga.architecture.compose.navigation.Route -import com.splendo.kaluga.architecture.compose.navigation.next -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsNavigation -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation - -/** Maps a navigation action to a route string. */ -internal fun contactListNavigationRouteMapper(action: ContactsListNavigation<*>): Route { - return when (action) { - is ContactsListNavigation.ShowContactDetails -> action.next - } -} - -/** Maps a navigation action to a route string. */ -internal fun contactDetailsNavigationRouteMapper(action: ContactDetailsNavigation<*>): Route { - return when (action) { - is ContactDetailsNavigation.Close -> Route.Back - else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}") - } -} - -/** Maps a navigation action to a NavigationSpec. */ -internal fun contactDetailsNavigationActivityMapper(action: ContactDetailsNavigation<*>): NavigationSpec { - return when (action) { - is ContactDetailsNavigation.SendEmail -> NavigationSpec.Email( - emailSettings = NavigationSpec.Email.EmailSettings( - type = NavigationSpec.Email.Type.Plain, - to = listOf(action.bundle!!.get(action.type)) - ) - ) - else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}") - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt similarity index 100% rename from newexample/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt diff --git a/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt index 293f385b3..4a1a63290 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt @@ -20,29 +20,24 @@ package com.splendo.kaluga.example.system import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup -import android.widget.Button -import androidx.appcompat.widget.AppCompatButton import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ViewListButtonBinding import com.splendo.kaluga.example.shared.viewmodel.system.SystemFeatures import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel -import com.splendo.kaluga.example.system.fragments.NetworkFragment import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf class SystemActivity : KalugaViewModelActivity(R.layout.activity_system) { override val viewModel: SystemViewModel by viewModel { parametersOf( - ActivityNavigator> { action -> + ActivityNavigator { action -> when (action) { - SystemNavigationActions.Network -> NavigationSpec.Fragment( - R.id.system_features_fragment, - createFragment = { NetworkFragment() } - ) + SystemNavigationActions.Network -> NavigationSpec.Activity() } } ) @@ -65,7 +60,9 @@ class SystemFeatureAdapter( private val viewModel: SystemViewModel ) : RecyclerView.Adapter() { - inner class SystemFeatureViewHolder(val button: Button) : RecyclerView.ViewHolder(button) + inner class SystemFeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { + val button = binding.button + } var modules: List = emptyList() set(newValue) { @@ -74,8 +71,8 @@ class SystemFeatureAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SystemFeatureViewHolder { - val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton - return SystemFeatureViewHolder(button) + val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SystemFeatureViewHolder(binding) } override fun onBindViewHolder(holder: SystemFeatureViewHolder, position: Int) { diff --git a/example/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt deleted file mode 100644 index 3535caea9..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.system.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.databinding.FragmentNetworkBinding -import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel - -class NetworkFragment : KalugaViewModelFragment(R.layout.fragment_network) { - - override val viewModel: NetworkViewModel by viewModel() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val binding = FragmentNetworkBinding.inflate(inflater) - binding.lifecycleOwner = this - binding.viewModel = viewModel - return binding.root - } -} diff --git a/example/android/src/main/res/layout/activity_location.xml b/example/android/src/main/res/layout/activity_location.xml index c8df9f909..12715da9c 100644 --- a/example/android/src/main/res/layout/activity_location.xml +++ b/example/android/src/main/res/layout/activity_location.xml @@ -1,42 +1,44 @@ - + + - + - + - + - \ No newline at end of file + + \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_network.xml b/example/android/src/main/res/layout/activity_network.xml similarity index 100% rename from newexample/android/src/main/res/layout/activity_network.xml rename to example/android/src/main/res/layout/activity_network.xml diff --git a/example/android/src/main/res/layout/activity_system.xml b/example/android/src/main/res/layout/activity_system.xml index bfd4af551..77a494611 100644 --- a/example/android/src/main/res/layout/activity_system.xml +++ b/example/android/src/main/res/layout/activity_system.xml @@ -24,19 +24,10 @@ android:layout_gravity="center_horizontal" android:layout_margin="@dimen/networkLayoutsMargin"> - - - - - + android:layout_height="wrap_content" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> \ No newline at end of file diff --git a/example/android/src/main/res/layout/fragment_network.xml b/example/android/src/main/res/layout/fragment_network.xml deleted file mode 100644 index 35562c4b0..000000000 --- a/example/android/src/main/res/layout/fragment_network.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/newexample/build.gradle.kts b/example/build.gradle.kts similarity index 100% rename from newexample/build.gradle.kts rename to example/build.gradle.kts diff --git a/newexample/gradle.properties b/example/gradle.properties similarity index 100% rename from newexample/gradle.properties rename to example/gradle.properties diff --git a/newexample/gradle/wrapper/gradle-wrapper.jar b/example/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from newexample/gradle/wrapper/gradle-wrapper.jar rename to example/gradle/wrapper/gradle-wrapper.jar diff --git a/newexample/gradle/wrapper/gradle-wrapper.properties b/example/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from newexample/gradle/wrapper/gradle-wrapper.properties rename to example/gradle/wrapper/gradle-wrapper.properties diff --git a/newexample/gradlew b/example/gradlew similarity index 100% rename from newexample/gradlew rename to example/gradlew diff --git a/newexample/gradlew.bat b/example/gradlew.bat similarity index 100% rename from newexample/gradlew.bat rename to example/gradlew.bat diff --git a/example/ios/.idea/inspectionProfiles/Project_Default.xml b/example/ios/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index d84c0b53b..000000000 --- a/example/ios/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/example/ios/.idea/runConfigurations/Demo.xml b/example/ios/.idea/runConfigurations/Demo.xml deleted file mode 100644 index b23d73dae..000000000 --- a/example/ios/.idea/runConfigurations/Demo.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/example/ios/Demo.xcodeproj/project.pbxproj b/example/ios/Demo.xcodeproj/project.pbxproj index 6d30214da..3a1573fc1 100644 --- a/example/ios/Demo.xcodeproj/project.pbxproj +++ b/example/ios/Demo.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 7478297FCF44ED1D49525D4E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74FDC8922442294AF0776058 /* LaunchScreen.storyboard */; }; 749B42F84CA72A5D4A0F3AD9 /* DemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741DDADC0C8034E32EA9C3D6 /* DemoUITests.swift */; }; 74C75886D08DD78EA65E6DD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 74C9C443CC304CB387AB324D /* Assets.xcassets */; }; - 74C9929AB26988E8CC66893D /* gradle.properties in Resources */ = {isa = PBXBuildFile; fileRef = 74F1F3223C9CF11420F72B08 /* gradle.properties */; }; 74D656BE6B4BD50115400411 /* DemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 744CF99B70DA1947AA98CA3B /* DemoTests.swift */; }; 74E6599B023623BD96737582 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74F483E9C1890D693F808DDA /* Main.storyboard */; }; 978348A7235DF586005B140B /* PermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978348A6235DF586005B140B /* PermissionViewController.swift */; }; @@ -102,27 +101,18 @@ 649979D225CC638C00348419 /* SystemViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemViewController.swift; sourceTree = ""; }; 649979E625CCD37200348419 /* NetworkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkViewController.swift; sourceTree = ""; }; 740C363F8973304E72F429BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; - 741672C742FA38B5D4971C10 /* gradlew */ = {isa = PBXFileReference; lastKnownFileType = text; path = gradlew; sourceTree = ""; }; - 741A9B938B05534BD1B508C6 /* settings.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = settings.gradle.kts; sourceTree = ""; }; 741DDADC0C8034E32EA9C3D6 /* DemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoUITests.swift; sourceTree = ""; }; - 741E82A1DDEE399E80A02B54 /* gradle-wrapper.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = "gradle-wrapper.jar"; sourceTree = ""; }; 7428D8755D288FD7EA2E6537 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 742CCE9033CEC8C8004D624F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 7433F67D6FEF093F4C827A5A /* build.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = build.gradle.kts; sourceTree = ""; }; 744CF99B70DA1947AA98CA3B /* DemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoTests.swift; sourceTree = ""; }; - 744F12775216AF134CBAF4E9 /* build.gradle.kts */ = {isa = PBXFileReference; lastKnownFileType = file.kts; path = build.gradle.kts; sourceTree = ""; }; 746834742E284396FD5452F4 /* DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 746E133CAEC5C7B1D516B11F /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74A0718952D0CC604E49709B /* DemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 74B0C3AB70AAB7878B48F6B1 /* gradle-wrapper.properties */ = {isa = PBXFileReference; lastKnownFileType = file.properties; path = "gradle-wrapper.properties"; sourceTree = ""; }; 74BBBF2C57521E71982179E0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 74BEC2BADB02749FD409313B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; 74C07E8A2DB465B1CE47DC5E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 74C22623993814F606777266 /* KotlinNativeFramework.kt */ = {isa = PBXFileReference; lastKnownFileType = file.kt; path = KotlinNativeFramework.kt; sourceTree = ""; }; 74C710A1E2E41AF0F4A9FFC5 /* LocationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = ""; }; 74C9C443CC304CB387AB324D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 74E253D6EF4BB7BA4916943D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; - 74F1F3223C9CF11420F72B08 /* gradle.properties */ = {isa = PBXFileReference; lastKnownFileType = file.properties; path = gradle.properties; sourceTree = ""; }; 978348A6235DF586005B140B /* PermissionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -288,23 +278,6 @@ path = Network; sourceTree = ""; }; - 743414FDF97167BD81A5293E /* wrapper */ = { - isa = PBXGroup; - children = ( - 74B0C3AB70AAB7878B48F6B1 /* gradle-wrapper.properties */, - 741E82A1DDEE399E80A02B54 /* gradle-wrapper.jar */, - ); - path = wrapper; - sourceTree = ""; - }; - 743EA5EA3B99CA8E01574460 /* KotlinNativeFrameworkMain */ = { - isa = PBXGroup; - children = ( - 7470228E28317355EFB70D41 /* kotlin */, - ); - path = KotlinNativeFrameworkMain; - sourceTree = ""; - }; 745520950D85E4696686141D = { isa = PBXGroup; children = ( @@ -313,37 +286,7 @@ 746CAD9D15ECB24F4ED84E11 /* Demo */, 7492DE0914511F3DC96A30A8 /* DemoTests */, 74D6698E6E24A4F93E6B289A /* DemoUITests */, - 747F646D72854D37D5686909 /* KotlinNativeFramework */, - 74588EBE65D1E22D05BFD2A5 /* Supporting Files */, - ); - sourceTree = ""; - }; - 745778F955B940F5CB818364 /* gradle */ = { - isa = PBXGroup; - children = ( - 743414FDF97167BD81A5293E /* wrapper */, - ); - path = gradle; - sourceTree = ""; - }; - 74588EBE65D1E22D05BFD2A5 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 7433F67D6FEF093F4C827A5A /* build.gradle.kts */, - 741672C742FA38B5D4971C10 /* gradlew */, - 745778F955B940F5CB818364 /* gradle */, - 741A9B938B05534BD1B508C6 /* settings.gradle.kts */, - 74F1F3223C9CF11420F72B08 /* gradle.properties */, - ); - path = "Supporting Files"; - sourceTree = ""; - }; - 745DD28172357B2F37C397E8 /* src */ = { - isa = PBXGroup; - children = ( - 743EA5EA3B99CA8E01574460 /* KotlinNativeFrameworkMain */, ); - path = src; sourceTree = ""; }; 746CAD9D15ECB24F4ED84E11 /* Demo */ = { @@ -376,14 +319,6 @@ path = Demo; sourceTree = ""; }; - 7470228E28317355EFB70D41 /* kotlin */ = { - isa = PBXGroup; - children = ( - 74C22623993814F606777266 /* KotlinNativeFramework.kt */, - ); - path = kotlin; - sourceTree = ""; - }; 747A549D9FF0D1B49E65E190 /* Products */ = { isa = PBXGroup; children = ( @@ -394,16 +329,6 @@ name = Products; sourceTree = ""; }; - 747F646D72854D37D5686909 /* KotlinNativeFramework */ = { - isa = PBXGroup; - children = ( - 74E253D6EF4BB7BA4916943D /* Info.plist */, - 74ED9583BC95062A511A899E /* Supporting Files */, - 745DD28172357B2F37C397E8 /* src */, - ); - path = KotlinNativeFramework; - sourceTree = ""; - }; 7492DE0914511F3DC96A30A8 /* DemoTests */ = { isa = PBXGroup; children = ( @@ -422,14 +347,6 @@ path = DemoUITests; sourceTree = ""; }; - 74ED9583BC95062A511A899E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 744F12775216AF134CBAF4E9 /* build.gradle.kts */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; 978348A5235DF51D005B140B /* Permissions */ = { isa = PBXGroup; children = ( @@ -550,7 +467,6 @@ 7478297FCF44ED1D49525D4E /* LaunchScreen.storyboard in Resources */, 74E6599B023623BD96737582 /* Main.storyboard in Resources */, 209471EA24C07F5F001426CD /* BluetoothDescriptorCell.xib in Resources */, - 74C9929AB26988E8CC66893D /* gradle.properties in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -580,7 +496,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd \"$SRCROOT/Supporting Files\"\n./gradlew :KotlinNativeFramework:embedAndSignAppleFrameworkForXcode\n"; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -756,10 +672,16 @@ DEVELOPMENT_TEAM = 2732HQPVJP; FRAMEWORK_SEARCH_PATHS = ( $BUILT_PRODUCTS_DIR, - "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Demo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + KalugaExampleShared, + ); PRODUCT_BUNDLE_IDENTIFIER = com.splendo.kaluga.example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Kaluga example"; @@ -882,10 +804,16 @@ DEVELOPMENT_TEAM = 2732HQPVJP; FRAMEWORK_SEARCH_PATHS = ( $BUILT_PRODUCTS_DIR, - "$(SRCROOT)/KotlinNativeFramework/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Demo/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + KalugaExampleShared, + ); PRODUCT_BUNDLE_IDENTIFIER = com.splendo.kaluga.example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Kaluga example"; diff --git a/example/ios/Demo/Alerts/AlertsViewController.swift b/example/ios/Demo/Alerts/AlertsViewController.swift index 743ea8f06..96e81b313 100644 --- a/example/ios/Demo/Alerts/AlertsViewController.swift +++ b/example/ios/Demo/Alerts/AlertsViewController.swift @@ -17,21 +17,21 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class AlertsViewController: UITableViewController { private lazy var viewModel = AlertViewModel(builder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { return [] } + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/example/ios/Demo/AppDelegate.swift b/example/ios/Demo/AppDelegate.swift index b1b416e18..cefdc285e 100644 --- a/example/ios/Demo/AppDelegate.swift +++ b/example/ios/Demo/AppDelegate.swift @@ -17,6 +17,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ import UIKit +import KalugaExampleShared @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -24,6 +25,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + DependencyInjectionKt.doInitKoin(customModules: []) return true } @@ -43,7 +45,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let vc = storyboard.instantiateViewController(withIdentifier: "LinksViewController") as! LinksViewController self.window?.rootViewController = vc - vc.handleIncomingLink(url: stringUrl) +// vc.handleIncomingLink(url: stringUrl) return true } diff --git a/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift b/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift index 577b793ef..cc6b9fcfc 100644 --- a/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift +++ b/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ArchitectureDetailsViewController: UIViewController { @@ -30,12 +30,15 @@ class ArchitectureDetailsViewController: UIViewController { if #available(iOS 13.0, *) { vc.isModalInPresentation = true } - vc.viewModel = KNArchitectureFramework().createArchitectureDetailsViewModel(parent: vc, inputDetails: inputDetails) { inputDetails in - onDismiss(inputDetails) + let navigator = ViewControllerNavigator(parentVC: vc) { action in + NavigationSpec.Dismiss(toDismiss: { $0 }, animated: true) { + onDismiss(action.value!) + } } + vc.viewModel = ArchitectureDetailsViewModel(initialDetail: inputDetails, navigator: navigator) return vc } - + var viewModel: ArchitectureDetailsViewModel! private var lifecycleManager: LifecycleManager! @@ -47,16 +50,16 @@ class ArchitectureDetailsViewController: UIViewController { deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - + return [ viewModel.name.observe { name in self?.nameLabel.text = name as? String ?? "" @@ -67,11 +70,11 @@ class ArchitectureDetailsViewController: UIViewController { ] } } - + @objc @IBAction func onInversePressed(sender: Any?) { viewModel.onInversePressed() } - + @objc @IBAction func onCloseButtonPressed(sender: Any?) { viewModel.onClosePressed() } diff --git a/example/ios/Demo/Architecture/ArchitectureInputViewController.swift b/example/ios/Demo/Architecture/ArchitectureInputViewController.swift index d9762d316..9317cd640 100644 --- a/example/ios/Demo/Architecture/ArchitectureInputViewController.swift +++ b/example/ios/Demo/Architecture/ArchitectureInputViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ArchitectureInputViewController: UIViewController { @@ -29,27 +29,30 @@ class ArchitectureInputViewController: UIViewController { @IBOutlet weak var numberError: UIImageView! @IBOutlet weak var detailsButton: UIButton! - - lazy var viewModel = KNArchitectureFramework().createArchitectureInputViewModel(parent: self) { [weak self] inputDetails in - return ArchitectureDetailsViewController.create(inputDetails: inputDetails) { [weak self] inputDetails in - self?.onDetailsDismissed(inputDetails: inputDetails) + + lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Present(animated: true, presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue)) { + ArchitectureDetailsViewController.create(inputDetails: action.value!) { [weak self] inputDetails in + self?.onDetailsDismissed(inputDetails: inputDetails) + } } } + lazy var viewModel = ArchitectureInputViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - + return [ viewModel.nameHeader.observeInitialized { header in self?.nameLabel.text = header as String? @@ -72,30 +75,30 @@ class ArchitectureInputViewController: UIViewController { ] } } - + @objc @IBAction func onShowDetailsPressed(sender: Any?) { viewModel.onShowDetailsPressed() } - + private func onDetailsDismissed(inputDetails: InputDetails) { viewModel.nameInput.post(newValue: NSString(string: inputDetails.name)) viewModel.numberInput.post(newValue: NSString(string: "\(inputDetails.number)")) } - + } extension ArchitectureInputViewController : UITextFieldDelegate { - + func textFieldDidEndEditing(_ textField: UITextField) { postInput(text: textField.text ?? "", fromTextField: textField) } - + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let resultingString = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) postInput(text: resultingString, fromTextField: textField) return false } - + private func postInput(text: String, fromTextField textField: UITextField) { if (textField == nameInput) { viewModel.nameInput.post(newValue: NSString(string: text)) diff --git a/example/ios/Demo/Beacons/BeaconsViewCell.swift b/example/ios/Demo/Beacons/BeaconsViewCell.swift index 829f8565d..928f6e859 100644 --- a/example/ios/Demo/Beacons/BeaconsViewCell.swift +++ b/example/ios/Demo/Beacons/BeaconsViewCell.swift @@ -15,7 +15,7 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class BeaconsViewCell: UICollectionViewCell { diff --git a/example/ios/Demo/Beacons/BeaconsViewController.swift b/example/ios/Demo/Beacons/BeaconsViewController.swift index 678e5786a..653f7c54a 100644 --- a/example/ios/Demo/Beacons/BeaconsViewController.swift +++ b/example/ios/Demo/Beacons/BeaconsViewController.swift @@ -15,7 +15,7 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class BeaconsViewController: UICollectionViewController { @@ -35,11 +35,7 @@ class BeaconsViewController: UICollectionViewController { return flowLayout }() - private lazy var viewModel = KNArchitectureFramework() - .createBeaconsListViewModel( - parent: self, - service: KNBeaconsFramework().service - ) + private lazy var viewModel = BeaconsListViewModel() deinit { lifecycleManager.unbind() @@ -54,7 +50,7 @@ class BeaconsViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ diff --git a/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift b/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift index e6b178457..cb593bf85 100644 --- a/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift +++ b/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift @@ -16,24 +16,24 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { +class BluetoothDeviceDetailsViewController : UIViewController { struct Const { static let storyboard = UIStoryboard(name: "Main", bundle: nil) static let storyboardId = "BluetoothDeviceDetails" } - static func create(deviceUuid: UUID, bluetooth: Bluetooth) -> BluetoothDeviceDetailsViewController { + static func create(identifier: UUID) -> BluetoothDeviceDetailsViewController { let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! BluetoothDeviceDetailsViewController if #available(iOS 13.0, *) { vc.isModalInPresentation = true } - vc.viewModel = KNArchitectureFramework().createBluetoothDeviceDetailsViewModel(identifier: deviceUuid, bluetooth: bluetooth) + vc.viewModel = BluetoothDeviceDetailViewModel(identifier: identifier) return vc } - + var viewModel: BluetoothDeviceDetailViewModel! private var lifecycleManager: LifecycleManager! @@ -46,29 +46,29 @@ class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewD @IBOutlet var servicesList: UICollectionView! private var services: [BluetoothServiceViewModel] = [] - + private var isInvalidating: Bool = false - + deinit { lifecycleManager.unbind() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + servicesHeader.text = NSLocalizedString("bluetooth_services_header", comment: "") deviceIdentifier.text = viewModel.identifierString - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) flowLayout.minimumLineSpacing = 4 servicesList.collectionViewLayout = flowLayout - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in - + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return []} - + return [ viewModel.name.observe { name in self?.deviceName.text = name as String? @@ -92,32 +92,32 @@ class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewD self?.servicesList.layoutIfNeeded() } ] - }) + } } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return services.count } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let serviceCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothServiceView.Companion.identifier, for: indexPath) as! BluetoothServiceView serviceCell.parent = self serviceCell.service = services[indexPath.row] return serviceCell } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothServiceView)?.startMonitoring() } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothServiceView)?.stopMonitoring() } } - + fileprivate func updateListSize() { isInvalidating = true servicesList.collectionViewLayout.invalidateLayout() @@ -146,13 +146,13 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo @IBOutlet var characteristicsListHeight: NSLayoutConstraint! private var characteristics: [BluetoothCharacteristicViewModel] = [] - + override func awakeFromNib() { super.awakeFromNib() - + serviceHeader.text = NSLocalizedString("bluetooth_service", comment: "") characteristicsHeader.text = NSLocalizedString("bluetooth_characteristics", comment: "") - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) @@ -162,14 +162,14 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo characteristicsList.delegate = self characteristicsList.register(UINib(nibName: "BluetoothCharacteristicCell", bundle: nil), forCellWithReuseIdentifier: BluetoothCharacteristicView.Companion.identifier) } - + fileprivate func startMonitoring() { disposeBag.dispose() guard let service = self.service else { return } service.didResume() - + serviceIdentifier.text = service.uuid service.characteristics.observe { [weak self] characteristics in self?.characteristics = characteristics as? [BluetoothCharacteristicViewModel] ?? [] @@ -177,12 +177,12 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo self?.updateListSize(isInvalidating: false) }.addTo(disposeBag: disposeBag) } - + fileprivate func stopMonitoring() { service?.didPause() disposeBag.dispose() } - + fileprivate func updateListSize(isInvalidating: Bool) { self.isInvalidating = isInvalidating characteristicsList.collectionViewLayout.invalidateLayout() @@ -192,30 +192,30 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo parent?.updateListSize() self.isInvalidating = false } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return characteristics.count } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let characteristicCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCharacteristicView.Companion.identifier, for: indexPath) as! BluetoothCharacteristicView characteristicCell.parent = self characteristicCell.characteristic = characteristics[indexPath.row] return characteristicCell } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothCharacteristicView)?.startMonitoring() } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothCharacteristicView)?.stopMonitoring() } } - + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) layoutIfNeeded() @@ -225,7 +225,7 @@ class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICo } -class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource { +class BluetoothCharacteristicView : UICollectionViewCell { fileprivate struct Companion { static let identifier = "BluetoothCharacteristicView" @@ -234,7 +234,7 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega fileprivate weak var parent: BluetoothServiceView? fileprivate var characteristic: BluetoothCharacteristicViewModel? private let disposeBag = DisposeBag() - + private var isInvalidating: Bool = false @IBOutlet var characteristicIdentifier: UILabel! @@ -245,12 +245,12 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega @IBOutlet var descriptorsListHeight: NSLayoutConstraint! private var descriptors: [BluetoothDescriptorViewModel] = [] - + override func awakeFromNib() { super.awakeFromNib() - + descriptorsHeader.text = NSLocalizedString("bluetooth_descriptors", comment: "") - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) @@ -258,31 +258,31 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega descriptorsList.collectionViewLayout = flowLayout descriptorsList.register(UINib(nibName: "BluetoothDescriptorCell", bundle: nil), forCellWithReuseIdentifier: BluetoothDescriptorView.Companion.identifier) } - + fileprivate func startMonitoring() { disposeBag.dispose() guard let characteristic = self.characteristic else { return } characteristic.didResume() - + characteristicIdentifier.text = characteristic.uuid characteristic.descriptors.observe { [weak self] descriptors in self?.descriptors = descriptors as? [BluetoothDescriptorViewModel] ?? [] self?.descriptorsList.reloadData() self?.updateListSize(isInvalidating: false) }.addTo(disposeBag: disposeBag) - + characteristic.value.observe { [weak self] value in self?.characteristicValue.text = value as String? }.addTo(disposeBag: disposeBag) } - + fileprivate func stopMonitoring() { disposeBag.dispose() characteristic?.didPause() } - + private func updateListSize(isInvalidating: Bool) { self.isInvalidating = isInvalidating descriptorsList.collectionViewLayout.invalidateLayout() @@ -292,36 +292,35 @@ class BluetoothCharacteristicView : UICollectionViewCell, UICollectionViewDelega parent?.updateListSize(isInvalidating: true) self.isInvalidating = false } - + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return descriptors.count } - + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let descriptorCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothDescriptorView.Companion.identifier, for: indexPath) as! BluetoothDescriptorView descriptorCell.descriptor = descriptors[indexPath.row] return descriptorCell } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothDescriptorView)?.startMonitoring() } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if (!isInvalidating) { (cell as? BluetoothDescriptorView)?.stopMonitoring() } } - + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) layoutIfNeeded() layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) return layoutAttributes } - } class BluetoothDescriptorView : UICollectionViewCell { @@ -342,19 +341,19 @@ class BluetoothDescriptorView : UICollectionViewCell { return } descriptor.didResume() - + descriptorIdentifier.text = descriptor.uuid - + descriptor.value.observe { [weak self] value in self?.descriptorValue.text = value as String? }.addTo(disposeBag: disposeBag) } - + fileprivate func stopMonitoring() { disposeBag.dispose() descriptor?.didResume() } - + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) layoutIfNeeded() diff --git a/example/ios/Demo/Bluetooth/BluetoothViewController.swift b/example/ios/Demo/Bluetooth/BluetoothViewController.swift index 1e60a04b4..82903a4b1 100644 --- a/example/ios/Demo/Bluetooth/BluetoothViewController.swift +++ b/example/ios/Demo/Bluetooth/BluetoothViewController.swift @@ -16,14 +16,17 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class BluetoothViewController : UICollectionViewController { - - lazy var viewModel = KNArchitectureFramework().createBluetoothListViewModel(parent: self, bluetooth: KNBluetoothFramework().bluetooth) { uuid, bluetooth in - return BluetoothDeviceDetailsViewController.create(deviceUuid: uuid, bluetooth: bluetooth) + + lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Push(animated: true) { + BluetoothDeviceDetailsViewController.create(identifier: action.value!.identifier) + } } - + lazy var viewModel = BluetoothListViewModel(navigator: navigator) + private var devices: [BluetoothListDeviceViewModel] = [] private var lifecycleManager: LifecycleManager! @@ -33,20 +36,20 @@ class BluetoothViewController : UICollectionViewController { override func awakeFromNib() { super.awakeFromNib() - + let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) flowLayout.minimumLineSpacing = 4 collectionView.collectionViewLayout = flowLayout } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - + return [ viewModel.isScanning.observe { isScanning in self?.updateNavigationItem(isScanning: isScanning as? Bool ?? false) @@ -60,9 +63,9 @@ class BluetoothViewController : UICollectionViewController { self?.updateTitle(title: title as String?) } ] - }) + } } - + private func updateNavigationItem(isScanning: Bool) { if (isScanning) { self.navigationItem.setRightBarButton(UIBarButtonItem(title: NSLocalizedString("bluetooth_stop_scanning", comment: ""), style: .plain, target: self, action: #selector(self.toggleScanning)), animated: true) @@ -74,41 +77,41 @@ class BluetoothViewController : UICollectionViewController { private func updateTitle(title: String?) { self.title = title } - + @objc private func toggleScanning() { viewModel.onScanPressed() } - + override func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } - + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { devices.count } - + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let bluetoothCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCell.Companion.identifier, for: indexPath) as! BluetoothCell bluetoothCell.device = devices[indexPath.row] return bluetoothCell } - + override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard let btCell = cell as? BluetoothCell else { return } - + btCell.startMonitoring() } - + override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard let btCell = cell as? BluetoothCell else { return } - + btCell.stopMonitoring() } - + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { devices[indexPath.row].toggleFoldOut() let context = UICollectionViewFlowLayoutInvalidationContext() @@ -143,19 +146,19 @@ class BluetoothCell: UICollectionViewCell { @IBOutlet var moreButton: UIButton! fileprivate var device: BluetoothListDeviceViewModel? = nil - + @IBAction func onConnectPressed() { device?.onConnectPressed() } - + @IBAction func onDisonnectPressed() { device?.onDisconnectPressed() } - + @IBAction func onMorePressed() { device?.onMorePressed() } - + func startMonitoring() { disposeBag.dispose() guard let device = device else { @@ -222,7 +225,7 @@ class BluetoothCell: UICollectionViewCell { self?.manufacturerData.text = manufacturerData as? String } } - + func stopMonitoring() { disposeBag.dispose() device?.didPause() diff --git a/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift b/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift index d32da17ed..9ee2d520e 100644 --- a/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift +++ b/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class DateTimePickerViewController : UIViewController { @@ -24,33 +24,31 @@ class DateTimePickerViewController : UIViewController { lazy var viewModel = DateTimePickerViewModel(dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ - viewModel.dateLabel.observe(onNext: { (time) in - if let timeString = (time as? String) { - self?.timeLabel.text = String(timeString) - } - }) + viewModel.dateLabel.observe { (time) in + self?.timeLabel.text = time as? String + } ] - }) + } } - + @IBAction func selectDatePressed() { viewModel.onSelectDatePressed() } - + @IBAction func selectTimePressed() { viewModel.onSelectTimePressed() diff --git a/example/ios/Demo/ExampleViewController.swift b/example/ios/Demo/ExampleViewController.swift index 6b0be8f80..d6ad2d207 100644 --- a/example/ios/Demo/ExampleViewController.swift +++ b/example/ios/Demo/ExampleViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ExampleViewController : UIViewController { @@ -31,26 +31,55 @@ class ExampleViewController : UIViewController { lazy var featuresListController = Const.storyboard.instantiateViewController(withIdentifier: Const.featuresList) as! FeaturesListViewController lazy var infoViewController = Const.storyboard.instantiateViewController(withIdentifier: Const.infoView) as! InfoViewController - - lazy var viewModel: ExampleViewModel = KNArchitectureFramework().createExampleViewModel(parent: self, - containerView: containerView, - featuresList: { self.featuresListController }, - info: { self.infoViewController }) - + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Nested(type: NavigationSpec.NestedTypeReplace(tag: 1), containerView: self.containerView) { + switch action { + case is ExampleTabNavigation.FeatureList: return self.featuresListController + case is ExampleTabNavigation.Info: return self.infoViewController + default: return UIViewController() + } + } + } + lazy var viewModel: ExampleViewModel = ExampleViewModel(navigator: navigator) var lifecycleManager: LifecycleManager! - + let selectedButtonDisposeBag = DisposeBag() + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel, let bottomView = self?.bottomView else { return [] } - return viewModel.observeTabs(stackView: bottomView) { (button: UIButton, action: @escaping () -> KotlinUnit) in - button.addAction { let _ = action() } - } + + return [ + viewModel.tabs.observeInitialized { tabs in + guard let disposeBag = self?.selectedButtonDisposeBag else { return } + disposeBag.dispose() + let tabs = tabs ?? [] + bottomView.arrangedSubviews.forEach { $0.removeFromSuperview() } + for tab in tabs { + guard let tab = tab as? ExampleViewModel.Tab else { return } + let button = UIButton() + button.setTitle(tab.title, for: .normal) + button.setTitleColor(UIColor.systemBlue, for: .selected) + button.setTitleColor(UIColor.systemBlue, for: .highlighted) + button.setTitleColor(UIColor.gray, for: .normal) + + viewModel.tab.observeInitialized { selectedTab in + button.isSelected = selectedTab == tab + }.addTo(disposeBag: disposeBag) + button.addAction { + viewModel.tab.post(newValue: tab) + } + bottomView.addArrangedSubview(button) + } + + } + ] } } diff --git a/example/ios/Demo/FeaturesList/FeaturesListViewController.swift b/example/ios/Demo/FeaturesList/FeaturesListViewController.swift index 1f6f19df9..e520eaa27 100644 --- a/example/ios/Demo/FeaturesList/FeaturesListViewController.swift +++ b/example/ios/Demo/FeaturesList/FeaturesListViewController.swift @@ -16,49 +16,55 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class FeaturesListViewController : UITableViewController { - - private lazy var viewModel: FeatureListViewModel = KNArchitectureFramework().createFeatureListViewModel(parent: self) + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Segue(identifier: action.segueKey) + } + private lazy var viewModel: FeatureListViewModel = FeatureListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! private var features = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? = nil + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.observeFeatures() { (features: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.features = features - self?.onSelected = onSelected + return [viewModel.feature.observeInitialized { next in + let features = next ?? [] + self?.features = features.map { ($0 as! Feature).title } + self?.onSelected = { (index: Int) in + viewModel.onFeaturePressed(feature: features[index] as! Feature) + } self?.tableView.reloadData() }] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return features.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: FeaturesListCell.Const.identifier, for: indexPath) as! FeaturesListCell cell.label.text = features[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } @@ -73,3 +79,26 @@ class FeaturesListCell : UITableViewCell { @IBOutlet weak var label: UILabel! } + +private extension FeatureListNavigationAction { + var segueKey: String { + get { + switch self { + case is FeatureListNavigationAction.Alerts: return "showAlerts" + case is FeatureListNavigationAction.Architecture: return "showArchitecture" + case is FeatureListNavigationAction.Beacons: return "showBeacons" + case is FeatureListNavigationAction.Bluetooth: return "showBluetooth" + case is FeatureListNavigationAction.DateTimePicker: return "showDateTimePicker" + case is FeatureListNavigationAction.Keyboard: return "showKeyboard" + case is FeatureListNavigationAction.Links: return "showLinks" + case is FeatureListNavigationAction.LoadingIndicator: return "showHUD" + case is FeatureListNavigationAction.Location: return "showLocation" + case is FeatureListNavigationAction.Permissions: return "showPermissions" + case is FeatureListNavigationAction.PlatformSpecific: return "showPlatformSpecific" + case is FeatureListNavigationAction.Resources: return "showResources" + case is FeatureListNavigationAction.System: return "showSystem" + default: return "" + } + } + } +} diff --git a/example/ios/Demo/Info/InfoViewController.swift b/example/ios/Demo/Info/InfoViewController.swift index e0f6825a8..a2f18dc06 100644 --- a/example/ios/Demo/Info/InfoViewController.swift +++ b/example/ios/Demo/Info/InfoViewController.swift @@ -16,53 +16,78 @@ */ import UIKit -import KotlinNativeFramework +import MessageUI +import KalugaExampleShared class InfoViewController : UITableViewController { - - private lazy var viewModel: InfoViewModel = KNArchitectureFramework().createInfoViewModel(parent: self) + + private lazy var navigator = InfoNavigatorKt.InfoNavigator( + parent: self, + onDialogSpec: { dialogSpec in + NavigationSpec.Present( + animated: true, + presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), + transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue) + ) { + let alert = UIAlertController.init(title: dialogSpec.title, message: dialogSpec.message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + return alert + } + }, + onLink: { link in NavigationSpec.Browser(url: URL(string: link)!, viewType: NavigationSpec.BrowserTypeNormal()) }, + onMailSpec: { mailSpec in + let settings = NavigationSpec.Email.EmailEmailSettings(type: NavigationSpec.EmailTypePlain(), to: mailSpec.to, cc: [], bcc: [], subject: mailSpec.subject, body: nil, attachments: []) + return NavigationSpec.Email(emailSettings: settings, delegate: nil, animated: true) + } + ) + private lazy var viewModel: InfoViewModel = InfoViewModel( + reviewManagerBuilder: ReviewManager.Builder(), + navigator: navigator) private var buttons = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil + private var onSelected: ((Int) -> Void)? = nil private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - - return [viewModel.observeButtons() { (buttons: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.buttons = buttons - self?.onSelected = onSelected - self?.tableView.reloadData() - }] + + return [ + viewModel.buttons.observeInitialized { next in + let buttons = next?.compactMap { $0 as? InfoViewModel.Button } ?? [] + self?.buttons = buttons.map { $0.title } + self?.onSelected = { (index: Int) in viewModel.onButtonPressed(button: buttons[index]) } + self?.tableView.reloadData() + } + ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return buttons.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: InfoButtonCell.Const.identifier, for: indexPath) as! InfoButtonCell cell.label.text = buttons[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } diff --git a/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift b/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift index 3b973ab9b..8e0d31743 100644 --- a/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift +++ b/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift @@ -16,7 +16,7 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class KeyboardManagerViewController : UIViewController { @@ -25,24 +25,24 @@ class KeyboardManagerViewController : UIViewController { private lazy var editFieldFocusHandler = { return UIKitFocusHandler(view: self.editField) }() - lazy var viewModel = KNArchitectureFramework().createKeyboardViewModel(focusHandler: self.editFieldFocusHandler) + lazy var viewModel = KeyboardViewModel(keyboardManagerBuilder: KeyboardManager.Builder(application: UIApplication.shared), editFieldFocusHandler: editFieldFocusHandler) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { return [] }) + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } } - + @IBAction func showButtonPressed() { viewModel.onShowPressed() } - + @IBAction func hideButtonPressed() { viewModel.onHidePressed() diff --git a/example/ios/Demo/Links/LinksViewController.swift b/example/ios/Demo/Links/LinksViewController.swift index 9f14309e3..34ab58446 100644 --- a/example/ios/Demo/Links/LinksViewController.swift +++ b/example/ios/Demo/Links/LinksViewController.swift @@ -17,17 +17,15 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared class LinksViewController : UIViewController { @IBOutlet weak var browserButton: UIButton! @IBOutlet weak var instructionsText: UILabel! - - private let knArchitectureFramework = KNArchitectureFramework() - private lazy var viewModel: LinksViewModel = { - return knArchitectureFramework.createLinksViewModel(parent: self, animated: true, completion: nil) - }() + + private lazy var navigator: ViewControllerNavigator> = BrowserNavigatorKt.BrowserNavigator(parent: self) + private lazy var viewModel: LinksViewModel = LinksViewModel(linkRepoBuilder: LinksLinksBuilder(), builder: AlertPresenter.Builder(viewController: self), navigator: navigator) private var lifecycleManager: LifecycleManager! deinit { @@ -37,7 +35,7 @@ class LinksViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [ self.viewModel.browserButtonText.observe { buttonText in self.browserButton.setTitle(buttonText as String?, for: .normal) diff --git a/example/ios/Demo/LoadingIndicator/LoadingViewController.swift b/example/ios/Demo/LoadingIndicator/LoadingViewController.swift index 8c2412492..94ce6baad 100644 --- a/example/ios/Demo/LoadingIndicator/LoadingViewController.swift +++ b/example/ios/Demo/LoadingIndicator/LoadingViewController.swift @@ -16,23 +16,23 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class LoadingViewController: UITableViewController { private lazy var viewModel = HudViewModel(builder: HUD.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { return [] } + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) switch (indexPath.row) { diff --git a/example/ios/Demo/Location/LocationViewController.swift b/example/ios/Demo/Location/LocationViewController.swift index a2632b8af..2b82f3b9c 100644 --- a/example/ios/Demo/Location/LocationViewController.swift +++ b/example/ios/Demo/Location/LocationViewController.swift @@ -18,7 +18,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands import UIKit import CoreLocation -import KotlinNativeFramework +import KalugaExampleShared class LocationViewController: UIViewController { @@ -30,9 +30,9 @@ class LocationViewController: UIViewController { @IBOutlet weak var label: UILabel! - lazy var viewModel = KNArchitectureFramework().createLocationViewModel(permission: Const.permission, repoBuilder: KNLocationFramework().getPermissionRepoBuilder()) + lazy var viewModel = LocationViewModel(permission: Const.permission) private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } @@ -40,13 +40,15 @@ class LocationViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = viewModel.addLifecycleManager(parent: self, onLifecycle: { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.location.observe(onNext: { (location) in - self?.label.text = location as? String ?? "" - })] - }) + return [ + viewModel.location.observe { (location) in + self?.label.text = location as? String ?? "" + } + ] + } } } diff --git a/example/ios/Demo/Permissions/PermissionListViewController.swift b/example/ios/Demo/Permissions/PermissionListViewController.swift index 08b634940..2fd828e7c 100644 --- a/example/ios/Demo/Permissions/PermissionListViewController.swift +++ b/example/ios/Demo/Permissions/PermissionListViewController.swift @@ -16,51 +16,61 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared class PermissionListViewController : UITableViewController { - - private lazy var viewModel: PermissionsListViewModel = KNArchitectureFramework().createPermissionListViewModel(parent: self) { (permission) -> UIViewController in - return PermissionViewController.create(permission: permission) + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Push(animated: true) { + guard let permission = action.value?.permission else { + return UIViewController() + } + + return PermissionViewController.create(permission: permission) + } } + private lazy var viewModel: PermissionsListViewModel = PermissionsListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! private var permissions = [PermissionView]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? = nil + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.observePermissions { (permissionViews: [PermissionView], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.permissions = permissionViews - self?.onSelected = onSelected - self?.tableView.reloadData() - }] + return [ + viewModel.permissions.observeInitialized { next in + let permissions = next?.compactMap { $0 as? PermissionView } ?? [] + self?.permissions = permissions + self?.onSelected = { (index: Int) in viewModel.onPermissionPressed(permissionView: permissions[index]) } + self?.tableView.reloadData() + } + ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return permissions.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: PermissionsListCell.Const.identifier, for: indexPath) as! PermissionsListCell cell.label.text = permissions[indexPath.row].title return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } diff --git a/example/ios/Demo/Permissions/PermissionViewController.swift b/example/ios/Demo/Permissions/PermissionViewController.swift index 29ce5c847..4bca12051 100644 --- a/example/ios/Demo/Permissions/PermissionViewController.swift +++ b/example/ios/Demo/Permissions/PermissionViewController.swift @@ -17,20 +17,18 @@ import UIKit import Foundation -import KotlinNativeFramework +import KalugaExampleShared class PermissionViewController: UIViewController { private struct Const { static let storyboard = UIStoryboard(name: "Main", bundle: nil) static let permissionVc = "Permission" - - static let permissions = KNPermissionsFramework().getPermissions() } static func create(permission: Permission) -> PermissionViewController { let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.permissionVc) as! PermissionViewController - vc.viewModel = KNArchitectureFramework().createPermissionViewModel(permissions: Const.permissions, permission: permission) + vc.viewModel = PermissionViewModel(permission: permission) return vc } @@ -39,47 +37,47 @@ class PermissionViewController: UIViewController { var viewModel: PermissionViewModel! private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - + requestPermissionButton.setTitle(NSLocalizedString("permission_request", comment: ""), for: .normal) - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in - + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } - + return [ - viewModel.permissionStateMessage.observe(onNext: { (message) in + viewModel.permissionStateMessage.observe { message in self?.permissionStateLabel.text = NSLocalizedString(message as? String ?? "", comment: "") - - }), - - viewModel.requestMessage.observe(onNext: { (optionalMessage) in + + }, + + viewModel.requestMessage.observe { optionalMessage in guard let message = optionalMessage as? String else { return } - + let alert = UIAlertController(title: NSLocalizedString("permission_request", comment: ""), message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) self?.present(alert, animated: true, completion: nil) - - }), - - viewModel.showPermissionButton.observe(onNext: { (show) in + + }, + + viewModel.showPermissionButton.observe { show in self?.requestPermissionButton.isHidden = !(show as? Bool ?? false) - }) + } ] - }) + } } - - + + @IBAction func requestPermission(sender: Any?) { viewModel.requestPermission() } diff --git a/example/ios/Demo/Resources/ButtonViewController.swift b/example/ios/Demo/Resources/ButtonViewController.swift index 68871ce6f..f00e435ff 100644 --- a/example/ios/Demo/Resources/ButtonViewController.swift +++ b/example/ios/Demo/Resources/ButtonViewController.swift @@ -15,11 +15,11 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ButtonViewController : UITableViewController { - private lazy var viewModel: ButtonViewModel = KNArchitectureFramework().createButtonViewModel(parent: self) + private lazy var viewModel: ButtonViewModel = ButtonViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider(), alertPresenterBuilder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! private var buttons = [KalugaButton]() @@ -31,7 +31,7 @@ class ButtonViewController : UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.allowsSelection = false - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ viewModel.buttons.observe { labels in diff --git a/example/ios/Demo/Resources/ColorViewController.swift b/example/ios/Demo/Resources/ColorViewController.swift index 4f2ce7a2f..c8412766a 100644 --- a/example/ios/Demo/Resources/ColorViewController.swift +++ b/example/ios/Demo/Resources/ColorViewController.swift @@ -15,7 +15,7 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ColorViewController : UIViewController { @@ -43,7 +43,7 @@ class ColorViewController : UIViewController { private var blendedLightenedColors: [BackgroundStyle] = [] private var blendedDarkenedColors: [BackgroundStyle] = [] - private lazy var viewModel: ColorViewModel = KNArchitectureFramework().createColorViewModel(parent: self) + private lazy var viewModel: ColorViewModel = ColorViewModel(alertPresenterBuilder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! deinit { @@ -58,7 +58,7 @@ class ColorViewController : UIViewController { sourceDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) blendedLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) blendedDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ diff --git a/example/ios/Demo/Resources/LabelViewController.swift b/example/ios/Demo/Resources/LabelViewController.swift index 055c3ba09..dd8695a79 100644 --- a/example/ios/Demo/Resources/LabelViewController.swift +++ b/example/ios/Demo/Resources/LabelViewController.swift @@ -15,11 +15,11 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class LabelViewController : UITableViewController { - - private lazy var viewModel: LabelViewModel = KNArchitectureFramework().createLabelViewModel() + + private var viewModel: LabelViewModel = LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider()) private var lifecycleManager: LifecycleManager! private var labels = [KalugaLabel]() @@ -32,11 +32,11 @@ class LabelViewController : UITableViewController { super.viewDidLoad() tableView.allowsSelection = false - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ viewModel.labels.observe { labels in - self?.labels = labels?.compactMap { $0 as? KalugaLabel } ?? [] + self?.labels = labels?.compactMap { $0 as? KalugaLabel } ?? [] self?.tableView.reloadData() } ] diff --git a/example/ios/Demo/Resources/ResourcesListViewController.swift b/example/ios/Demo/Resources/ResourcesListViewController.swift index 906c7c6e7..40d7799a8 100644 --- a/example/ios/Demo/Resources/ResourcesListViewController.swift +++ b/example/ios/Demo/Resources/ResourcesListViewController.swift @@ -15,49 +15,56 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class ResourcesListViewController : UITableViewController { + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Segue(identifier: action.segueKey) + } - private lazy var viewModel: ResourcesListViewModel = KNArchitectureFramework().createResourcesViewModel(parent: self) + private lazy var viewModel: ResourcesListViewModel = ResourcesListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! private var resources = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? = nil + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } - return [viewModel.observeResources() { (resources: [String], onSelected: @escaping (KotlinInt) -> KotlinUnit) in - self?.resources = resources - self?.onSelected = onSelected - self?.tableView.reloadData() - }] + return [ + viewModel.resources.observeInitialized { next in + let resources = next ?? [] + self?.resources = resources.map { ($0 as! Resource).title } + self?.onSelected = { (index: Int) in viewModel.onResourceSelected(resource: resources[index] as! Resource) } + self?.tableView.reloadData() + } + ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return resources.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: ResourcesListCell.Const.identifier, for: indexPath) as! ResourcesListCell cell.label.text = resources[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } @@ -72,3 +79,16 @@ class ResourcesListCell : UITableViewCell { @IBOutlet weak var label: UILabel! } + +private extension ResourcesListNavigationAction { + var segueKey: String { + get { + switch self { + case is ResourcesListNavigationAction.Button: return "showButton" + case is ResourcesListNavigationAction.Color: return "showColor" + case is ResourcesListNavigationAction.Label: return "showLabel" + default: return "" + } + } + } +} diff --git a/example/ios/Demo/System/Network/NetworkViewController.swift b/example/ios/Demo/System/Network/NetworkViewController.swift index a3c6c7cef..ead546b08 100644 --- a/example/ios/Demo/System/Network/NetworkViewController.swift +++ b/example/ios/Demo/System/Network/NetworkViewController.swift @@ -17,11 +17,10 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared class NetworkViewController : UIViewController { - - private let knArchitectureFramework = KNArchitectureFramework() + @IBOutlet weak var networkStateText: UILabel! private var lifecycleManager: LifecycleManager! @@ -34,12 +33,12 @@ class NetworkViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { [weak self] in + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ - viewModel.networkState.observe { value in - self?.networkStateText.text = value as? String + viewModel.networkState.observe { next in + self?.networkStateText.text = next as? String } ] } diff --git a/example/ios/Demo/System/SystemViewController.swift b/example/ios/Demo/System/SystemViewController.swift index 9945dfd0d..782e14c5b 100644 --- a/example/ios/Demo/System/SystemViewController.swift +++ b/example/ios/Demo/System/SystemViewController.swift @@ -17,56 +17,60 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared class SystemViewController : UITableViewController { - - private let knArchitectureFramework = KNArchitectureFramework() - private lazy var viewModel: SystemViewModel = { - return self.knArchitectureFramework.createSystemViewModel(parent: self) - }() - + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + switch action { + case is SystemNavigationActions.Network: return NavigationSpec.Segue(identifier: "showNetwork") + default: return NavigationSpec.Segue(identifier: "") + } + } + private lazy var viewModel: SystemViewModel = SystemViewModel(navigator: navigator) + private var modules = [String]() - private var onModuleTapped: ((KotlinInt) -> KotlinUnit)? = nil + private var onModuleTapped: ((Int) -> Void)? = nil private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { [weak self] in + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ - viewModel.observeModules { (modules: [String], onButtonTapped: @escaping (KotlinInt) -> KotlinUnit) in - self?.modules = modules - self?.onModuleTapped = onButtonTapped + viewModel.modules.observeInitialized { next in + let modules = next?.compactMap { $0 as? SystemFeatures } ?? [] + self?.modules = modules.map{ $0.name } + self?.onModuleTapped = { (index: Int) in viewModel.onButtonTapped(systemFeatures: modules[index]) } self?.tableView.reloadData() } ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return modules.count } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: SystemListCell.Const.identifier, for: indexPath) as! SystemListCell cell.label.text = modules[indexPath.row] return cell } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onModuleTapped?(KotlinInt.init(int: Int32(indexPath.row))) + let _ = onModuleTapped?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } diff --git a/example/ios/DemoTests/DemoTests.swift b/example/ios/DemoTests/DemoTests.swift index 2aa6b9b30..dbe30118f 100644 --- a/example/ios/DemoTests/DemoTests.swift +++ b/example/ios/DemoTests/DemoTests.swift @@ -18,7 +18,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands import XCTest @testable import Demo -import KotlinNativeFramework +import KalugaExampleShared class DemoTests: XCTestCase { diff --git a/example/ios/KotlinNativeFramework/Info.plist b/example/ios/KotlinNativeFramework/Info.plist deleted file mode 100644 index e1fe4cfb7..000000000 --- a/example/ios/KotlinNativeFramework/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/example/ios/KotlinNativeFramework/build.gradle.kts b/example/ios/KotlinNativeFramework/build.gradle.kts deleted file mode 100644 index af1919ff5..000000000 --- a/example/ios/KotlinNativeFramework/build.gradle.kts +++ /dev/null @@ -1,52 +0,0 @@ -buildscript { - - repositories { - mavenCentral() - google() - gradlePluginPortal() - } -} - -plugins { - kotlin("multiplatform") - id("org.jlleitschuh.gradle.ktlint") -} - -kotlin { - - listOf( - iosX64(), - iosArm64(), - iosSimulatorArm64() - ).forEach { - it.binaries.framework { - baseName = "KotlinNativeFramework" - export(project(":shared")) - transitiveExport = true - } - } - - sourceSets { - val commonMain by getting { - dependencies { - api(project(":shared")) - } - } - - val iosX64Main by getting - val iosArm64Main by getting - val iosSimulatorArm64Main by getting - val iosMain by creating { - dependsOn(commonMain) - iosX64Main.dependsOn(this) - iosArm64Main.dependsOn(this) - iosSimulatorArm64Main.dependsOn(this) - } - } -} - -tasks.create("cleanKotlinNativeFrameworkTest") { - delete = setOf( - "build" - ) -} diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt deleted file mode 100644 index 07fbb39f8..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - -*/ - -import io.github.aakira.napier.DebugAntilog -import io.github.aakira.napier.Antilog as NapierLog - -class KotlinNativeFramework { - - fun hello() = com.splendo.kaluga.example.shared.helloCommon() - - // expose a dependency to Swift as an example - fun logger(): NapierLog = DebugAntilog() -} diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt deleted file mode 100644 index ea305d5d6..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 architecture - -/* ktlint-disable no-wildcard-imports */ -import com.splendo.kaluga.alerts.AlertPresenter -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator -import com.splendo.kaluga.architecture.observable.Disposable -import com.splendo.kaluga.architecture.observable.DisposeBag -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.architecture.viewmodel.LifecycleManager -import com.splendo.kaluga.architecture.viewmodel.addLifecycleManager -import com.splendo.kaluga.architecture.viewmodel.onLifeCycleChanged -import com.splendo.kaluga.bluetooth.Bluetooth -import com.splendo.kaluga.bluetooth.beacons.Beacons -import com.splendo.kaluga.bluetooth.device.Identifier -import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation -import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails -import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpecRow -import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel -import com.splendo.kaluga.example.shared.viewmodel.info.* -import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow -import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel -import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpecRow -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel -import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel -import com.splendo.kaluga.keyboard.FocusHandler -import com.splendo.kaluga.keyboard.KeyboardManager -import com.splendo.kaluga.links.LinksBuilder -import com.splendo.kaluga.location.LocationStateRepoBuilder -import com.splendo.kaluga.permissions.base.Permission -import com.splendo.kaluga.permissions.base.Permissions -import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission -import com.splendo.kaluga.permissions.calendar.CalendarPermission -import com.splendo.kaluga.permissions.camera.CameraPermission -import com.splendo.kaluga.permissions.contacts.ContactsPermission -import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.microphone.MicrophonePermission -import com.splendo.kaluga.permissions.notifications.* -import com.splendo.kaluga.permissions.storage.StoragePermission -import com.splendo.kaluga.resources.StyledStringBuilder -import com.splendo.kaluga.review.ReviewManager -import platform.Foundation.NSURL -import platform.Foundation.NSUUID -import platform.UIKit.* -import platform.UserNotifications.UNAuthorizationOptionAlert -import platform.UserNotifications.UNAuthorizationOptionSound - -class KNArchitectureFramework { - - fun createPermissionListViewModel(parent: UIViewController, createPermissionViewController: (Permission) -> UIViewController): PermissionsListViewModel { - return PermissionsListViewModel( - ViewControllerNavigator(parent) { action -> - NavigationSpec.Push( - push = { - val bundle = action.bundle ?: return@Push UIViewController() - val permission = when (bundle.get(PermissionNavigationBundleSpecRow)) { - PermissionView.Bluetooth -> BluetoothPermission - PermissionView.Calendar -> CalendarPermission() - PermissionView.Camera -> CameraPermission - PermissionView.Contacts -> ContactsPermission() - PermissionView.Location -> LocationPermission( - background = true, - precise = true - ) - PermissionView.Microphone -> MicrophonePermission - PermissionView.Notifications -> NotificationsPermission( - NotificationOptions( - UNAuthorizationOptionAlert or UNAuthorizationOptionSound - ) - ) - PermissionView.Storage -> StoragePermission() - } - createPermissionViewController(permission) - } - ) - } - ) - } - - fun createPermissionViewModel(permissions: Permissions, permission: Permission): PermissionViewModel { - return PermissionViewModel(permissions, permission) - } - - fun createLocationViewModel(permission: LocationPermission, repoBuilder: LocationStateRepoBuilder): LocationViewModel { - return LocationViewModel(permission, repoBuilder) - } - - fun createKeyboardViewModel(focusHandler: FocusHandler): KeyboardViewModel { - return KeyboardViewModel(KeyboardManager.Builder(), focusHandler) - } -} - -fun InfoViewModel.observeButtons(onInfoButtonsChanged: (List, (Int) -> Unit) -> Unit): Disposable { - return buttons.observeInitialized { buttons -> - val titles = buttons.map { button -> button.title } - onInfoButtonsChanged(titles) { index -> this.onButtonPressed(buttons[index]) } - } -} - -fun PermissionsListViewModel.observePermissions(onPermissionsChanged: (List, (Int) -> Unit) -> Unit): Disposable { - return permissions.observeInitialized { permissions -> - onPermissionsChanged(permissions) { index -> this.onPermissionPressed(permissions[index]) } - } -} diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt deleted file mode 100644 index 579ad06ea..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.ios.beacons - -import com.splendo.kaluga.base.singleThreadDispatcher -import com.splendo.kaluga.bluetooth.BluetoothBuilder -import com.splendo.kaluga.bluetooth.beacons.Beacons -import com.splendo.kaluga.bluetooth.device.ConnectionSettings -import com.splendo.kaluga.bluetooth.scanner.BaseScanner -import kotlin.time.Duration.Companion.minutes -import kotlinx.coroutines.MainScope -import permissions.KNPermissionsFramework - -class KNBeaconsFramework { - private val mainScope = MainScope() - val service = Beacons( - BluetoothBuilder().create( - { BaseScanner.Settings(KNPermissionsFramework().getPermissions()) }, - ConnectionSettings(), - singleThreadDispatcher("Bluetooth") - ), - timeout = 1.minutes - ) -} diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt deleted file mode 100644 index 9e55dda3e..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.ios.bluetooth - -import com.splendo.kaluga.base.singleThreadDispatcher -import com.splendo.kaluga.bluetooth.BluetoothBuilder -import com.splendo.kaluga.bluetooth.device.ConnectionSettings -import com.splendo.kaluga.bluetooth.scanner.BaseScanner -import kotlinx.coroutines.MainScope -import permissions.KNPermissionsFramework - -object KNBluetoothFramework { - val mainScope = MainScope() - val bluetooth = BluetoothBuilder().create( - { BaseScanner.Settings(KNPermissionsFramework().getPermissions()) }, - ConnectionSettings(), - singleThreadDispatcher("Bluetooth") - ) -} diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/location/KNLocationFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/location/KNLocationFramework.kt deleted file mode 100644 index 103c44292..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/location/KNLocationFramework.kt +++ /dev/null @@ -1,7 +0,0 @@ -package location - -import com.splendo.kaluga.location.LocationStateRepoBuilder - -class KNLocationFramework { - fun getPermissionRepoBuilder(): LocationStateRepoBuilder { return LocationStateRepoBuilder() } -} diff --git a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/permissions/KNPermissionsFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/permissions/KNPermissionsFramework.kt deleted file mode 100644 index 34995c691..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/permissions/KNPermissionsFramework.kt +++ /dev/null @@ -1,29 +0,0 @@ -package permissions - -import com.splendo.kaluga.permissions.base.Permissions -import com.splendo.kaluga.permissions.base.PermissionsBuilder -import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission -import com.splendo.kaluga.permissions.calendar.registerCalendarPermission -import com.splendo.kaluga.permissions.camera.registerCameraPermission -import com.splendo.kaluga.permissions.contacts.registerContactsPermission -import com.splendo.kaluga.permissions.location.registerLocationPermission -import com.splendo.kaluga.permissions.microphone.registerMicrophonePermission -import com.splendo.kaluga.permissions.notifications.registerNotificationsPermission -import com.splendo.kaluga.permissions.storage.registerStoragePermission - -class KNPermissionsFramework { - fun getPermissions(): Permissions { - return Permissions( - PermissionsBuilder().apply { - registerBluetoothPermission() - registerCameraPermission() - registerStoragePermission() - registerLocationPermission() - registerNotificationsPermission() - registerContactsPermission() - registerMicrophonePermission() - registerCalendarPermission() - } - ) - } -} diff --git a/example/ios/Supporting Files/.idea/codeStyles/Project.xml b/example/ios/Supporting Files/.idea/codeStyles/Project.xml deleted file mode 100644 index a700adc5c..000000000 --- a/example/ios/Supporting Files/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - -

- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
- - - - - - - \ No newline at end of file diff --git a/example/ios/Supporting Files/.idea/codeStyles/codeStyleConfig.xml b/example/ios/Supporting Files/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c2..000000000 --- a/example/ios/Supporting Files/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/example/ios/Supporting Files/.idea/copyright/apache.xml b/example/ios/Supporting Files/.idea/copyright/apache.xml deleted file mode 100644 index dbcade1d8..000000000 --- a/example/ios/Supporting Files/.idea/copyright/apache.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/example/ios/Supporting Files/.idea/copyright/profiles_settings.xml b/example/ios/Supporting Files/.idea/copyright/profiles_settings.xml deleted file mode 100644 index c553753a7..000000000 --- a/example/ios/Supporting Files/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/example/ios/Supporting Files/.run/Ktlint Check.run.xml b/example/ios/Supporting Files/.run/Ktlint Check.run.xml deleted file mode 100644 index fe4732494..000000000 --- a/example/ios/Supporting Files/.run/Ktlint Check.run.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - true - true - false - - - \ No newline at end of file diff --git a/example/ios/Supporting Files/.run/Ktlint Format.run.xml b/example/ios/Supporting Files/.run/Ktlint Format.run.xml deleted file mode 100644 index 630b7c720..000000000 --- a/example/ios/Supporting Files/.run/Ktlint Format.run.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - true - true - false - - - \ No newline at end of file diff --git a/example/ios/Supporting Files/build.gradle.kts b/example/ios/Supporting Files/build.gradle.kts deleted file mode 100644 index 126204c81..000000000 --- a/example/ios/Supporting Files/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -// TODO: To be removed once we will migrate to kotlin version 1.6.20 -// https://youtrack.jetbrains.com/issue/KT-49109#focus=Comments-27-5667134.0-0 -rootProject.plugins.withType { - rootProject.the().nodeVersion = "16.13.2" -} - -buildscript { - repositories { - mavenCentral() - google() - gradlePluginPortal() - } - - dependencies { - val kalugaAndroidGradlePluginVersion = project.extra["kaluga.androidGradlePluginVersion"] - // mostly migrated to new style plugin declarations, but some cross plugin interaction still requires this - classpath("com.android.tools.build:gradle:${kalugaAndroidGradlePluginVersion}") - } -} - -plugins { - kotlin("multiplatform") apply false -} - -val ext = (gradle as ExtensionAware).extra -val repo = ext["example_maven_repo"] -logger.lifecycle("Using repo: $repo for resolving dependencies") - -allprojects { - - repositories { - when(repo) { - null, "", "local" -> mavenLocal() - "none" -> {/* noop */} - else -> - maven(repo) - } - mavenCentral() - google() - } -} diff --git a/example/ios/Supporting Files/gradle.properties b/example/ios/Supporting Files/gradle.properties deleted file mode 100644 index 14bb99596..000000000 --- a/example/ios/Supporting Files/gradle.properties +++ /dev/null @@ -1,21 +0,0 @@ -## ## keep in sync with gradle.properties in root ## ## - -kotlin.code.style=official -kotlin.mpp.stability.nowarn=true -android.useAndroidX=true - -kaluga.androidGradlePluginVersion=7.3.0 -kaluga.kotlinVersion=1.6.10 -kaluga.googleServicesGradlePluginVersion=4.3.10 -kaluga.ktLintGradlePluginVersion=10.1.0 -org.gradle.jvmargs=-Xmx3048M -Dkotlin.daemon.jvm.options\="-Xmx3048M" - -#MPP -kotlin.mpp.enableGranularSourceSetsMetadata=true -kotlin.native.enableDependencyPropagation=false -kotlin.mpp.enableCInteropCommonization=true - -## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## - -## extra properties only for example -xcodeproj=./.. diff --git a/example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.jar b/example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 91ca28c8b802289c3a438766657a5e98f20eff03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54413 zcmafaV|Zr4wq`oEZQHiZj%|LijZQlLf{tz5M#r{o+fI6V=G-$g=gzrzeyqLskF}nv zRZs0&c;EUi2L_G~0s;*U0szbK}f6%Pvi zRZ#mYf6f1oqJoH`jHHCB8l!^by~4z}yc`4LEP@;Z?bO6{g9`Hk+s@(L1jC5Tq{1Yf z4E;CQvrx0-gF+peRxFC*gF=&$zNYk(w0q}U=WqXMz`tYs@0o%B{dRD+{C_6(f9t^g zhmNJQv6-#;f2)f2uc{u-#*U8W&i{|ewYN^n_1~cv|1J!}zc&$eaBy{T{cEpa46s*q zHFkD2cV;xTHFj}{*3kBt*FgS4A5SI|$F%$gB@It9FlC}D3y`sbZG{2P6gGwC$U`6O zb_cId9AhQl#A<&=x>-xDD%=Ppt$;y71@Lwsl{x943#T@8*?cbR<~d`@@}4V${+r$jICUIOzgZJy_9I zu*eA(F)$~J07zX%tmQN}1^wj+RM|9bbwhQA=xrPE*{vB_P!pPYT5{Or^m*;Qz#@Bl zRywCG_RDyM6bf~=xn}FtiFAw|rrUxa1+z^H`j6e|GwKDuq}P)z&@J>MEhsVBvnF|O zOEm)dADU1wi8~mX(j_8`DwMT_OUAnjbWYer;P*^Uku_qMu3}qJU zTAkza-K9aj&wcsGuhQ>RQoD?gz~L8RwCHOZDzhBD$az*$TQ3!uygnx_rsXG`#_x5t zn*lb(%JI3%G^MpYp-Y(KI4@_!&kBRa3q z|Fzn&3R%ZsoMNEn4pN3-BSw2S_{IB8RzRv(eQ1X zyBQZHJ<(~PfUZ~EoI!Aj`9k<+Cy z2DtI<+9sXQu!6&-Sk4SW3oz}?Q~mFvy(urUy<)x!KQ>#7yIPC)(ORhKl7k)4eSy~} z7#H3KG<|lt68$tk^`=yjev%^usOfpQ#+Tqyx|b#dVA(>fPlGuS@9ydo z!Cs#hse9nUETfGX-7lg;F>9)+ml@M8OO^q|W~NiysX2N|2dH>qj%NM`=*d3GvES_# zyLEHw&1Fx<-dYxCQbk_wk^CI?W44%Q9!!9aJKZW-bGVhK?N;q`+Cgc*WqyXcxZ%U5QXKu!Xn)u_dxeQ z;uw9Vysk!3OFzUmVoe)qt3ifPin0h25TU zrG*03L~0|aaBg7^YPEW^Yq3>mSNQgk-o^CEH?wXZ^QiPiuH}jGk;75PUMNquJjm$3 zLcXN*uDRf$Jukqg3;046b;3s8zkxa_6yAlG{+7{81O3w96i_A$KcJhD&+oz1<>?lun#C3+X0q zO4JxN{qZ!e#FCl@e_3G?0I^$CX6e$cy7$BL#4<`AA)Lw+k`^15pmb-447~5lkSMZ` z>Ce|adKhb-F%yy!vx>yQbXFgHyl(an=x^zi(!-~|k;G1=E(e@JgqbAF{;nv`3i)oi zDeT*Q+Mp{+NkURoabYb9@#Bi5FMQnBFEU?H{~9c;g3K%m{+^hNe}(MdpPb?j9`?2l z#%AO!|2QxGq7-2Jn2|%atvGb(+?j&lmP509i5y87`9*BSY++<%%DXb)kaqG0(4Eft zj|2!Od~2TfVTi^0dazAIeVe&b#{J4DjN6;4W;M{yWj7#+oLhJyqeRaO;>?%mX>Ec{Mp~;`bo}p;`)@5dA8fNQ38FyMf;wUPOdZS{U*8SN6xa z-kq3>*Zos!2`FMA7qjhw-`^3ci%c91Lh`;h{qX1r;x1}eW2hYaE*3lTk4GwenoxQ1kHt1Lw!*N8Z%DdZSGg5~Bw}+L!1#d$u+S=Bzo7gi zqGsBV29i)Jw(vix>De)H&PC; z-t2OX_ak#~eSJ?Xq=q9A#0oaP*dO7*MqV;dJv|aUG00UX=cIhdaet|YEIhv6AUuyM zH1h7fK9-AV)k8sr#POIhl+?Z^r?wI^GE)ZI=H!WR<|UI(3_YUaD#TYV$Fxd015^mT zpy&#-IK>ahfBlJm-J(n(A%cKV;)8&Y{P!E|AHPtRHk=XqvYUX?+9po4B$0-6t74UUef${01V{QLEE8gzw* z5nFnvJ|T4dlRiW9;Ed_yB{R@)fC=zo4hCtD?TPW*WJmMXYxN_&@YQYg zBQ$XRHa&EE;YJrS{bn7q?}Y&DH*h;){5MmE(9A6aSU|W?{3Ox%5fHLFScv7O-txuRbPG1KQtI`Oay=IcEG=+hPhlnYC;`wSHeo|XGio0aTS6&W($E$ z?N&?TK*l8;Y^-xPl-WVZwrfdiQv10KdsAb9u-*1co*0-Z(h#H)k{Vc5CT!708cs%sExvPC+7-^UY~jTfFq=cj z!Dmy<+NtKp&}}$}rD{l?%MwHdpE(cPCd;-QFPk1`E5EVNY2i6E`;^aBlx4}h*l42z zpY#2cYzC1l6EDrOY*ccb%kP;k8LHE3tP>l3iK?XZ%FI<3666yPw1rM%>eCgnv^JS_ zK7c~;g7yXt9fz@(49}Dj7VO%+P!eEm& z;z8UXs%NsQ%@2S5nve)@;yT^61BpVlc}=+i6{ZZ9r7<({yUYqe==9*Z+HguP3`sA& z{`inI4G)eLieUQ*pH9M@)u7yVnWTQva;|xq&-B<>MoP(|xP(HqeCk1&h>DHNLT>Zi zQ$uH%s6GoPAi0~)sC;`;ngsk+StYL9NFzhFEoT&Hzfma1f|tEnL0 zMWdX4(@Y*?*tM2@H<#^_l}BC&;PYJl%~E#veQ61{wG6!~nyop<^e)scV5#VkGjYc2 z$u)AW-NmMm%T7WschOnQ!Hbbw&?`oMZrJ&%dVlN3VNra1d0TKfbOz{dHfrCmJ2Jj= zS#Gr}JQcVD?S9X!u|oQ7LZ+qcq{$40 ziG5=X^+WqeqxU00YuftU7o;db=K+Tq!y^daCZgQ)O=M} zK>j*<3oxs=Rcr&W2h%w?0Cn3);~vqG>JO_tTOzuom^g&^vzlEjkx>Sv!@NNX%_C!v zaMpB>%yVb}&ND9b*O>?HxQ$5-%@xMGe4XKjWh7X>CYoRI2^JIwi&3Q5UM)?G^k8;8 zmY$u;(KjZx>vb3fe2zgD7V;T2_|1KZQW$Yq%y5Ioxmna9#xktcgVitv7Sb3SlLd6D zfmBM9Vs4rt1s0M}c_&%iP5O{Dnyp|g1(cLYz^qLqTfN6`+o}59Zlu%~oR3Q3?{Bnr zkx+wTpeag^G12fb_%SghFcl|p2~<)Av?Agumf@v7y-)ecVs`US=q~=QG%(_RTsqQi z%B&JdbOBOmoywgDW|DKR5>l$1^FPhxsBrja<&}*pfvE|5dQ7j-wV|ur%QUCRCzBR3q*X`05O3U@?#$<>@e+Zh&Z&`KfuM!0XL& zI$gc@ZpM4o>d&5)mg7+-Mmp98K^b*28(|Ew8kW}XEV7k^vnX-$onm9OtaO@NU9a|as7iA%5Wrw9*%UtJYacltplA5}gx^YQM` zVkn`TIw~avq)mIQO0F0xg)w$c)=8~6Jl|gdqnO6<5XD)&e7z7ypd3HOIR+ss0ikSVrWar?548HFQ*+hC)NPCq*;cG#B$7 z!n?{e9`&Nh-y}v=nK&PR>PFdut*q&i81Id`Z<0vXUPEbbJ|<~_D!)DJMqSF~ly$tN zygoa)um~xdYT<7%%m!K8+V(&%83{758b0}`b&=`))Tuv_)OL6pf=XOdFk&Mfx9y{! z6nL>V?t=#eFfM$GgGT8DgbGRCF@0ZcWaNs_#yl+6&sK~(JFwJmN-aHX{#Xkpmg;!} zgNyYYrtZdLzW1tN#QZAh!z5>h|At3m+ryJ-DFl%V>w?cmVTxt^DsCi1ZwPaCe*D{) z?#AZV6Debz{*D#C2>44Czy^yT3y92AYDcIXtZrK{L-XacVl$4i=X2|K=Fy5vAzhk{ zu3qG=qSb_YYh^HirWf~n!_Hn;TwV8FU9H8+=BO)XVFV`nt)b>5yACVr!b98QlLOBDY=^KS<*m9@_h3;64VhBQzb_QI)gbM zSDto2i*iFrvxSmAIrePB3i`Ib>LdM8wXq8(R{-)P6DjUi{2;?}9S7l7bND4w%L2!; zUh~sJ(?Yp}o!q6)2CwG*mgUUWlZ;xJZo`U`tiqa)H4j>QVC_dE7ha0)nP5mWGB268 zn~MVG<#fP#R%F=Ic@(&Va4dMk$ysM$^Avr1&hS!p=-7F>UMzd(M^N9Ijb|364}qcj zcIIh7suk$fQE3?Z^W4XKIPh~|+3(@{8*dSo&+Kr(J4^VtC{z*_{2}ld<`+mDE2)S| zQ}G#Q0@ffZCw!%ZGc@kNoMIdQ?1db%N1O0{IPPesUHI;(h8I}ETudk5ESK#boZgln z(0kvE`&6z1xH!s&={%wQe;{^&5e@N0s7IqR?L*x%iXM_czI5R1aU?!bA7)#c4UN2u zc_LZU+@elD5iZ=4*X&8%7~mA;SA$SJ-8q^tL6y)d150iM)!-ry@TI<=cnS#$kJAS# zq%eK**T*Wi2OlJ#w+d_}4=VN^A%1O+{?`BK00wkm)g8;u?vM;RR+F1G?}({ENT3i= zQsjJkp-dmJ&3-jMNo)wrz0!g*1z!V7D(StmL(A}gr^H-CZ~G9u?*Uhcx|x7rb`v^X z9~QGx;wdF4VcxCmEBp$F#sms@MR?CF67)rlpMxvwhEZLgp2?wQq|ci#rLtrYRV~iR zN?UrkDDTu114&d~Utjcyh#tXE_1x%!dY?G>qb81pWWH)Ku@Kxbnq0=zL#x@sCB(gs zm}COI(!{6-XO5li0>1n}Wz?w7AT-Sp+=NQ1aV@fM$`PGZjs*L+H^EW&s!XafStI!S zzgdntht=*p#R*o8-ZiSb5zf6z?TZr$^BtmIfGAGK;cdg=EyEG)fc*E<*T=#a?l=R5 zv#J;6C(umoSfc)W*EODW4z6czg3tXIm?x8{+8i^b;$|w~k)KLhJQnNW7kWXcR^sol z1GYOp?)a+}9Dg*nJ4fy*_riThdkbHO37^csfZRGN;CvQOtRacu6uoh^gg%_oEZKDd z?X_k67s$`|Q&huidfEonytrq!wOg07H&z@`&BU6D114p!rtT2|iukF}>k?71-3Hk< zs6yvmsMRO%KBQ44X4_FEYW~$yx@Y9tKrQ|rC1%W$6w}-9!2%4Zk%NycTzCB=nb)r6*92_Dg+c0;a%l1 zsJ$X)iyYR2iSh|%pIzYV1OUWER&np{w1+RXb~ zMUMRymjAw*{M)UtbT)T!kq5ZAn%n=gq3ssk3mYViE^$paZ;c^7{vXDJ`)q<}QKd2?{r9`X3mpZ{AW^UaRe2^wWxIZ$tuyKzp#!X-hXkHwfD zj@2tA--vFi3o_6B?|I%uwD~emwn0a z+?2Lc1xs(`H{Xu>IHXpz=@-84uw%dNV;{|c&ub|nFz(=W-t4|MME(dE4tZQi?0CE|4_?O_dyZj1)r zBcqB8I^Lt*#)ABdw#yq{OtNgf240Jvjm8^zdSf40 z;H)cp*rj>WhGSy|RC5A@mwnmQ`y4{O*SJ&S@UFbvLWyPdh)QnM=(+m3p;0&$^ysbZ zJt!ZkNQ%3hOY*sF2_~-*`aP|3Jq7_<18PX*MEUH*)t{eIx%#ibC|d&^L5FwoBN}Oe z?!)9RS@Zz%X1mqpHgym75{_BM4g)k1!L{$r4(2kL<#Oh$Ei7koqoccI3(MN1+6cDJ zp=xQhmilz1?+ZjkX%kfn4{_6K_D{wb~rdbkh!!k!Z@cE z^&jz55*QtsuNSlGPrU=R?}{*_8?4L7(+?>?(^3Ss)f!ou&{6<9QgH>#2$?-HfmDPN z6oIJ$lRbDZb)h-fFEm^1-v?Slb8udG{7GhbaGD_JJ8a9f{6{TqQN;m@$&)t81k77A z?{{)61za|e2GEq2)-OqcEjP`fhIlUs_Es-dfgX-3{S08g`w=wGj2{?`k^GD8d$}6Z zBT0T1lNw~fuwjO5BurKM593NGYGWAK%UCYiq{$p^GoYz^Uq0$YQ$j5CBXyog8(p_E znTC+$D`*^PFNc3Ih3b!2Lu|OOH6@46D)bbvaZHy%-9=$cz}V^|VPBpmPB6Ivzlu&c zPq6s7(2c4=1M;xlr}bkSmo9P`DAF>?Y*K%VPsY`cVZ{mN&0I=jagJ?GA!I;R)i&@{ z0Gl^%TLf_N`)`WKs?zlWolWvEM_?{vVyo(!taG$`FH2bqB`(o50pA=W34kl-qI62lt z1~4LG_j%sR2tBFteI{&mOTRVU7AH>>-4ZCD_p6;-J<=qrod`YFBwJz(Siu(`S}&}1 z6&OVJS@(O!=HKr-Xyzuhi;swJYK*ums~y1ePdX#~*04=b9)UqHHg;*XJOxnS6XK#j zG|O$>^2eW2ZVczP8#$C`EpcWwPFX4^}$omn{;P(fL z>J~%-r5}*D3$Kii z34r@JmMW2XEa~UV{bYP=F;Y5=9miJ+Jw6tjkR+cUD5+5TuKI`mSnEaYE2=usXNBs9 zac}V13%|q&Yg6**?H9D620qj62dM+&&1&a{NjF}JqmIP1I1RGppZ|oIfR}l1>itC% zl>ed${{_}8^}m2^br*AIX$L!Vc?Sm@H^=|LnpJg`a7EC+B;)j#9#tx-o0_e4!F5-4 zF4gA;#>*qrpow9W%tBzQ89U6hZ9g=-$gQpCh6Nv_I0X7t=th2ajJ8dBbh{i)Ok4{I z`Gacpl?N$LjC$tp&}7Sm(?A;;Nb0>rAWPN~@3sZ~0_j5bR+dz;Qs|R|k%LdreS3Nn zp*36^t#&ASm=jT)PIjNqaSe4mTjAzlAFr*@nQ~F+Xdh$VjHWZMKaI+s#FF#zjx)BJ zufxkW_JQcPcHa9PviuAu$lhwPR{R{7CzMUi49=MaOA%ElpK;A)6Sgsl7lw)D$8FwE zi(O6g;m*86kcJQ{KIT-Rv&cbv_SY4 zpm1|lSL*o_1LGOlBK0KuU2?vWcEcQ6f4;&K=&?|f`~X+s8H)se?|~2HcJo{M?Ity) zE9U!EKGz2^NgB6Ud;?GcV*1xC^1RYIp&0fr;DrqWLi_Kts()-#&3|wz{wFQsKfnnsC||T?oIgUp z{O(?Df7&vW!i#_~*@naguLLjDAz+)~*_xV2iz2?(N|0y8DMneikrT*dG`mu6vdK`% z=&nX5{F-V!Reau}+w_V3)4?}h@A@O)6GCY7eXC{p-5~p8x{cH=hNR;Sb{*XloSZ_%0ZKYG=w<|!vy?spR4!6mF!sXMUB5S9o_lh^g0!=2m55hGR; z-&*BZ*&;YSo474=SAM!WzrvjmNtq17L`kxbrZ8RN419e=5CiQ-bP1j-C#@@-&5*(8 zRQdU~+e(teUf}I3tu%PB1@Tr{r=?@0KOi3+Dy8}+y#bvgeY(FdN!!`Kb>-nM;7u=6 z;0yBwOJ6OdWn0gnuM{0`*fd=C(f8ASnH5aNYJjpbY1apTAY$-%)uDi$%2)lpH=#)=HH z<9JaYwPKil@QbfGOWvJ?cN6RPBr`f+jBC|-dO|W@x_Vv~)bmY(U(!cs6cnhe0z31O z>yTtL4@KJ*ac85u9|=LFST22~!lb>n7IeHs)_(P_gU}|8G>{D_fJX)8BJ;Se? z67QTTlTzZykb^4!{xF!=C}VeFd@n!9E)JAK4|vWVwWop5vSWcD<;2!88v-lS&ve7C zuYRH^85#hGKX(Mrk};f$j_V&`Nb}MZy1mmfz(e`nnI4Vpq(R}26pZx?fq%^|(n~>* z5a5OFtFJJfrZmgjyHbj1`9||Yp?~`p2?4NCwu_!!*4w8K`&G7U_|np&g7oY*-i;sI zu)~kYH;FddS{7Ri#Z5)U&X3h1$Mj{{yk1Q6bh4!7!)r&rqO6K~{afz@bis?*a56i& zxi#(Ss6tkU5hDQJ0{4sKfM*ah0f$>WvuRL zunQ-eOqa3&(rv4kiQ(N4`FO6w+nko_HggKFWx@5aYr}<~8wuEbD(Icvyl~9QL^MBt zSvD)*C#{2}!Z55k1ukV$kcJLtW2d~%z$t0qMe(%2qG`iF9K_Gsae7OO%Tf8E>ooch ztAw01`WVv6?*14e1w%Wovtj7jz_)4bGAqqo zvTD|B4)Ls8x7-yr6%tYp)A7|A)x{WcI&|&DTQR&2ir(KGR7~_RhNOft)wS<+vQ*|sf;d>s zEfl&B^*ZJp$|N`w**cXOza8(ARhJT{O3np#OlfxP9Nnle4Sto)Fv{w6ifKIN^f1qO*m8+MOgA1^Du!=(@MAh8)@wU8t=Ymh!iuT_lzfm za~xEazL-0xwy9$48!+?^lBwMV{!Gx)N>}CDi?Jwax^YX@_bxl*+4itP;DrTswv~n{ zZ0P>@EB({J9ZJ(^|ptn4ks^Z2UI&87d~J_^z0&vD2yb%*H^AE!w= zm&FiH*c%vvm{v&i3S>_hacFH${|(2+q!`X~zn4$aJDAry>=n|{C7le(0a)nyV{kAD zlud4-6X>1@-XZd`3SKKHm*XNn_zCyKHmf*`C_O509$iy$Wj`Sm3y?nWLCDy>MUx1x zl-sz7^{m(&NUk*%_0(G^>wLDnXW90FzNi$Tu6* z<+{ePBD`%IByu977rI^x;gO5M)Tfa-l*A2mU-#IL2?+NXK-?np<&2rlF;5kaGGrx2 zy8Xrz`kHtTVlSSlC=nlV4_oCsbwyVHG4@Adb6RWzd|Otr!LU=% zEjM5sZ#Ib4#jF(l!)8Na%$5VK#tzS>=05GpV?&o* z3goH1co0YR=)98rPJ~PuHvkA59KUi#i(Mq_$rApn1o&n1mUuZfFLjx@3;h`0^|S##QiTP8rD`r8P+#D@gvDJh>amMIl065I)PxT6Hg(lJ?X7*|XF2Le zv36p8dWHCo)f#C&(|@i1RAag->5ch8TY!LJ3(+KBmLxyMA%8*X%_ARR*!$AL66nF= z=D}uH)D)dKGZ5AG)8N-;Il*-QJ&d8u30&$_Q0n1B58S0ykyDAyGa+BZ>FkiOHm1*& zNOVH;#>Hg5p?3f(7#q*dL74;$4!t?a#6cfy#}9H3IFGiCmevir5@zXQj6~)@zYrWZ zRl*e66rjwksx-)Flr|Kzd#Bg>We+a&E{h7bKSae9P~ z(g|zuXmZ zD?R*MlmoZ##+0c|cJ(O{*h(JtRdA#lChYhfsx25(Z`@AK?Q-S8_PQqk z>|Z@Ki1=wL1_c6giS%E4YVYD|Y-{^ZzFwB*yN8-4#+TxeQ`jhks7|SBu7X|g=!_XL z`mY=0^chZfXm%2DYHJ4z#soO7=NONxn^K3WX={dV>$CTWSZe@<81-8DVtJEw#Uhd3 zxZx+($6%4a&y_rD8a&E`4$pD6-_zZJ%LEE*1|!9uOm!kYXW< zOBXZAowsX-&$5C`xgWkC43GcnY)UQt2Qkib4!!8Mh-Q!_M%5{EC=Gim@_;0+lP%O^ zG~Q$QmatQk{Mu&l{q~#kOD;T-{b1P5u7)o-QPPnqi?7~5?7%IIFKdj{;3~Hu#iS|j z)Zoo2wjf%+rRj?vzWz(6JU`=7H}WxLF*|?WE)ci7aK?SCmd}pMW<{#1Z!_7BmVP{w zSrG>?t}yNyCR%ZFP?;}e8_ zRy67~&u11TN4UlopWGj6IokS{vB!v!n~TJYD6k?~XQkpiPMUGLG2j;lh>Eb5bLTkX zx>CZlXdoJsiPx=E48a4Fkla>8dZYB%^;Xkd(BZK$z3J&@({A`aspC6$qnK`BWL;*O z-nRF{XRS`3Y&b+}G&|pE1K-Ll_NpT!%4@7~l=-TtYRW0JJ!s2C-_UsRBQ=v@VQ+4> z*6jF0;R@5XLHO^&PFyaMDvyo?-lAD(@H61l-No#t@at@Le9xOgTFqkc%07KL^&iss z!S2Ghm)u#26D(e1Q7E;L`rxOy-N{kJ zTgfw}az9=9Su?NEMMtpRlYwDxUAUr8F+P=+9pkX4%iA4&&D<|=B|~s*-U+q6cq`y* zIE+;2rD7&D5X;VAv=5rC5&nP$E9Z3HKTqIFCEV%V;b)Y|dY?8ySn|FD?s3IO>VZ&&f)idp_7AGnwVd1Z znBUOBA}~wogNpEWTt^1Rm-(YLftB=SU|#o&pT7vTr`bQo;=ZqJHIj2MP{JuXQPV7% z0k$5Ha6##aGly<}u>d&d{Hkpu?ZQeL_*M%A8IaXq2SQl35yW9zs4^CZheVgHF`%r= zs(Z|N!gU5gj-B^5{*sF>;~fauKVTq-Ml2>t>E0xl9wywD&nVYZfs1F9Lq}(clpNLz z4O(gm_i}!k`wUoKr|H#j#@XOXQ<#eDGJ=eRJjhOUtiKOG;hym-1Hu)1JYj+Kl*To<8( za1Kf4_Y@Cy>eoC59HZ4o&xY@!G(2p^=wTCV>?rQE`Upo^pbhWdM$WP4HFdDy$HiZ~ zRUJFWTII{J$GLVWR?miDjowFk<1#foE3}C2AKTNFku+BhLUuT>?PATB?WVLzEYyu+ zM*x((pGdotzLJ{}R=OD*jUexKi`mb1MaN0Hr(Wk8-Uj0zA;^1w2rmxLI$qq68D>^$ zj@)~T1l@K|~@YJ6+@1vlWl zHg5g%F{@fW5K!u>4LX8W;ua(t6YCCO_oNu}IIvI6>Fo@MilYuwUR?9p)rKNzDmTAN zzN2d>=Za&?Z!rJFV*;mJ&-sBV80%<-HN1;ciLb*Jk^p?u<~T25%7jjFnorfr={+wm zzl5Q6O>tsN8q*?>uSU6#xG}FpAVEQ_++@}G$?;S7owlK~@trhc#C)TeIYj^N(R&a} zypm~c=fIs;M!YQrL}5{xl=tUU-Tfc0ZfhQuA-u5(*w5RXg!2kChQRd$Fa8xQ0CQIU zC`cZ*!!|O!*y1k1J^m8IIi|Sl3R}gm@CC&;4840^9_bb9%&IZTRk#=^H0w%`5pMDCUef5 zYt-KpWp2ijh+FM`!zZ35>+7eLN;s3*P!bp%-oSx34fdTZ14Tsf2v7ZrP+mitUx$rS zW(sOi^CFxe$g3$x45snQwPV5wpf}>5OB?}&Gh<~i(mU&ss#7;utaLZ!|KaTHniGO9 zVC9OTzuMKz)afey_{93x5S*Hfp$+r*W>O^$2ng|ik!<`U1pkxm3*)PH*d#>7md1y} zs7u^a8zW8bvl92iN;*hfOc-=P7{lJeJ|3=NfX{(XRXr;*W3j845SKG&%N zuBqCtDWj*>KooINK1 zFPCsCWr!-8G}G)X*QM~34R*k zmRmDGF*QE?jCeNfc?k{w<}@29e}W|qKJ1K|AX!htt2|B`nL=HkC4?1bEaHtGBg}V( zl(A`6z*tck_F$4;kz-TNF%7?=20iqQo&ohf@S{_!TTXnVh}FaW2jxAh(DI0f*SDG- z7tqf5X@p#l?7pUNI(BGi>n_phw=lDm>2OgHx-{`T>KP2YH9Gm5ma zb{>7>`tZ>0d5K$j|s2!{^sFWQo3+xDb~#=9-jp(1ydI3_&RXGB~rxWSMgDCGQG)oNoc#>)td zqE|X->35U?_M6{^lB4l(HSN|`TC2U*-`1jSQeiXPtvVXdN-?i1?d#;pw%RfQuKJ|e zjg75M+Q4F0p@8I3ECpBhGs^kK;^0;7O@MV=sX^EJLVJf>L;GmO z3}EbTcoom7QbI(N8ad!z(!6$!MzKaajSRb0c+ZDQ($kFT&&?GvXmu7+V3^_(VJx1z zP-1kW_AB&_A;cxm*g`$ z#Pl@Cg{siF0ST2-w)zJkzi@X)5i@)Z;7M5ewX+xcY36IaE0#flASPY2WmF8St0am{ zV|P|j9wqcMi%r-TaU>(l*=HxnrN?&qAyzimA@wtf;#^%{$G7i4nXu=Pp2#r@O~wi)zB>@25A*|axl zEclXBlXx1LP3x0yrSx@s-kVW4qlF+idF+{M7RG54CgA&soDU-3SfHW@-6_ z+*;{n_SixmGCeZjHmEE!IF}!#aswth_{zm5Qhj0z-@I}pR?cu=P)HJUBClC;U+9;$#@xia30o$% zDw%BgOl>%vRenxL#|M$s^9X}diJ9q7wI1-0n2#6>@q}rK@ng(4M68(t52H_Jc{f&M9NPxRr->vj-88hoI?pvpn}llcv_r0`;uN>wuE{ z&TOx_i4==o;)>V4vCqG)A!mW>dI^Ql8BmhOy$6^>OaUAnI3>mN!Zr#qo4A>BegYj` zNG_)2Nvy2Cqxs1SF9A5HHhL7sai#Umw%K@+riaF+q)7&MUJvA&;$`(w)+B@c6!kX@ zzuY;LGu6|Q2eu^06PzSLspV2v4E?IPf`?Su_g8CX!75l)PCvyWKi4YRoRThB!-BhG zubQ#<7oCvj@z`^y&mPhSlbMf0<;0D z?5&!I?nV-jh-j1g~&R(YL@c=KB_gNup$8abPzXZN`N|WLqxlN)ZJ+#k4UWq#WqvVD z^|j+8f5uxTJtgcUscKTqKcr?5g-Ih3nmbvWvvEk})u-O}h$=-p4WE^qq7Z|rLas0$ zh0j&lhm@Rk(6ZF0_6^>Rd?Ni-#u1y`;$9tS;~!ph8T7fLlYE{P=XtWfV0Ql z#z{_;A%p|8+LhbZT0D_1!b}}MBx9`R9uM|+*`4l3^O(>Mk%@ha>VDY=nZMMb2TnJ= zGlQ+#+pmE98zuFxwAQcVkH1M887y;Bz&EJ7chIQQe!pgWX>(2ruI(emhz@_6t@k8Z zqFEyJFX2PO`$gJ6p$=ku{7!vR#u+$qo|1r;orjtp9FP^o2`2_vV;W&OT)acRXLN^m zY8a;geAxg!nbVu|uS8>@Gvf@JoL&GP`2v4s$Y^5vE32&l;2)`S%e#AnFI-YY7_>d#IKJI!oL6e z_7W3e=-0iz{bmuB*HP+D{Nb;rn+RyimTFqNV9Bzpa0?l`pWmR0yQOu&9c0S*1EPr1 zdoHMYlr>BycjTm%WeVuFd|QF8I{NPT&`fm=dITj&3(M^q ze2J{_2zB;wDME%}SzVWSW6)>1QtiX)Iiy^p2eT}Ii$E9w$5m)kv(3wSCNWq=#DaKZ zs%P`#^b7F-J0DgQ1?~2M`5ClYtYN{AlU|v4pEg4z03=g6nqH`JjQuM{k`!6jaIL_F zC;sn?1x?~uMo_DFg#ypNeie{3udcm~M&bYJ1LI zE%y}P9oCX3I1Y9yhF(y9Ix_=8L(p)EYr&|XZWCOb$7f2qX|A4aJ9bl7pt40Xr zXUT#NMBB8I@xoIGSHAZkYdCj>eEd#>a;W-?v4k%CwBaR5N>e3IFLRbDQTH#m_H+4b zk2UHVymC`%IqwtHUmpS1!1p-uQB`CW1Y!+VD!N4TT}D8(V0IOL|&R&)Rwj@n8g@=`h&z9YTPDT+R9agnwPuM!JW~=_ya~% zIJ*>$Fl;y7_`B7G4*P!kcy=MnNmR`(WS5_sRsvHF42NJ;EaDram5HwQ4Aw*qbYn0j;#)bh1lyKLg#dYjN*BMlh+fxmCL~?zB;HBWho;20WA==ci0mAqMfyG>1!HW zO7rOga-I9bvut1Ke_1eFo9tbzsoPTXDW1Si4}w3fq^Z|5LGf&egnw%DV=b11$F=P~ z(aV+j8S}m=CkI*8=RcrT>GmuYifP%hCoKY22Z4 zmu}o08h3YhcXx-v-QC??8mDn<+}+*X{+gZH-I;G^|7=1fBveS?J$27H&wV5^V^P$! z84?{UeYSmZ3M!@>UFoIN?GJT@IroYr;X@H~ax*CQ>b5|Xi9FXt5j`AwUPBq`0sWEJ z3O|k+g^JKMl}L(wfCqyMdRj9yS8ncE7nI14Tv#&(?}Q7oZpti{Q{Hw&5rN-&i|=fWH`XTQSu~1jx(hqm$Ibv zRzFW9$xf@oZAxL~wpj<0ZJ3rdPAE=0B>G+495QJ7D>=A&v^zXC9)2$$EnxQJ<^WlV zYKCHb1ZzzB!mBEW2WE|QG@&k?VXarY?umPPQ|kziS4{EqlIxqYHP!HN!ncw6BKQzKjqk!M&IiOJ9M^wc~ZQ1xoaI z;4je%ern~?qi&J?eD!vTl__*kd*nFF0n6mGEwI7%dI9rzCe~8vU1=nE&n4d&8}pdL zaz`QAY?6K@{s2x%Sx%#(y+t6qLw==>2(gb>AksEebXv=@ht>NBpqw=mkJR(c?l7vo z&cV)hxNoYPGqUh9KAKT)kc(NqekzE6(wjjotP(ac?`DJF=Sb7^Xet-A3PRl%n&zKk zruT9cS~vV1{%p>OVm1-miuKr<@rotj*5gd$?K`oteNibI&K?D63RoBjw)SommJ5<4 zus$!C8aCP{JHiFn2>XpX&l&jI7E7DcTjzuLYvON2{rz<)#$HNu(;ie-5$G<%eLKnTK7QXfn(UR(n+vX%aeS6!q6kv z!3nzY76-pdJp339zsl_%EI|;ic_m56({wdc(0C5LvLULW=&tWc5PW-4;&n+hm1m`f zzQV0T>OPSTjw=Ox&UF^y< zarsYKY8}YZF+~k70=olu$b$zdLaozBE|QE@H{_R21QlD5BilYBTOyv$D5DQZ8b1r- zIpSKX!SbA0Pb5#cT)L5!KpxX+x+8DRy&`o-nj+nmgV6-Gm%Fe91R1ca3`nt*hRS|^ z<&we;TJcUuPDqkM7k0S~cR%t7a`YP#80{BI$e=E!pY}am)2v3-Iqk2qvuAa1YM>xj#bh+H2V z{b#St2<;Gg>$orQ)c2a4AwD5iPcgZ7o_}7xhO86(JSJ(q(EWKTJDl|iBjGEMbX8|P z4PQHi+n(wZ_5QrX0?X_J)e_yGcTM#E#R^u_n8pK@l5416`c9S=q-e!%0RjoPyTliO zkp{OC@Ep^#Ig-n!C)K0Cy%8~**Vci8F1U(viN{==KU0nAg2(+K+GD_Gu#Bx!{tmUm zCwTrT(tCr6X8j43_n96H9%>>?4akSGMvgd+krS4wRexwZ1JxrJy!Uhz#yt$-=aq?A z@?*)bRZxjG9OF~7d$J0cwE_^CLceRK=LvjfH-~{S><^D;6B2&p-02?cl?|$@>`Qt$ zP*iaOxg<+(rbk>34VQDQpNQ|a9*)wScu!}<{oXC87hRPqyrNWpo?#=;1%^D2n2+C* zKKQH;?rWn-@%Y9g%NHG&lHwK9pBfV1a`!TqeU_Fv8s6_(@=RHua7`VYO|!W&WL*x= zIWE9eQaPq3zMaXuf)D0$V`RIZ74f)0P73xpeyk4)-?8j;|K%pD$eq4j2%tL=;&+E91O(2p91K|85b)GQcbRe&u6Ilu@SnE={^{Ix1Eqgv8D z4=w65+&36|;5WhBm$!n*!)ACCwT9Sip#1_z&g~E1kB=AlEhO0lu`Ls@6gw*a)lzc# zKx!fFP%eSBBs)U>xIcQKF(r_$SWD3TD@^^2Ylm=kC*tR+I@X>&SoPZdJ2fT!ysjH% z-U%|SznY8Fhsq7Vau%{Ad^Pvbf3IqVk{M2oD+w>MWimJA@VSZC$QooAO3 zC=DplXdkyl>mSp^$zk7&2+eoGQ6VVh_^E#Z3>tX7Dmi<2aqlM&YBmK&U}m>a%8)LQ z8v+c}a0QtXmyd%Kc2QNGf8TK?_EK4wtRUQ*VDnf5jHa?VvH2K(FDZOjAqYufW8oIZ z31|o~MR~T;ZS!Lz%8M0*iVARJ>_G2BXEF8(}6Dmn_rFV~5NI`lJjp`Mi~g7~P%H zO`S&-)Fngo3VXDMo7ImlaZxY^s!>2|csKca6!|m7)l^M0SQT1_L~K29%x4KV8*xiu zwP=GlyIE9YPSTC0BV`6|#)30=hJ~^aYeq7d6TNfoYUkk-^k0!(3qp(7Mo-$|48d8Z2d zrsfsRM)y$5)0G`fNq!V?qQ+nh0xwFbcp{nhW%vZ?h);=LxvM(pWd9FG$Bg1;@Bv)mKDW>AP{ol zD(R~mLzdDrBv$OSi{E%OD`Ano=F^vwc)rNb*Bg3-o)bbAgYE=M7Gj2OHY{8#pM${_^ zwkU|tnTKawxUF7vqM9UfcQ`V49zg78V%W)$#5ssR}Rj7E&p(4_ib^?9luZPJ%iJTvW&-U$nFYky>KJwHpEHHx zVEC;!ETdkCnO|${Vj#CY>LLut_+c|(hpWk8HRgMGRY%E--%oKh@{KnbQ~0GZd}{b@ z`J2qHBcqqjfHk^q=uQL!>6HSSF3LXL*cCd%opM|k#=xTShX~qcxpHTW*BI!c3`)hQq{@!7^mdUaG7sFsFYnl1%blslM;?B8Q zuifKqUAmR=>33g~#>EMNfdye#rz@IHgpM$~Z7c5@bO@S>MyFE3_F}HVNLnG0TjtXU zJeRWH^j5w_qXb$IGs+E>daTa}XPtrUnnpTRO9NEx4g6uaFEfHP9gW;xZnJi{oqAH~ z5dHS(ch3^hbvkv@u3QPLuWa}ImaElDrmIc%5HN<^bwej}3+?g) z-ai7D&6Iq_P(}k`i^4l?hRLbCb>X9iq2UYMl=`9U9Rf=3Y!gnJbr?eJqy>Zpp)m>Ae zcQ4Qfs&AaE?UDTODcEj#$_n4KeERZHx-I+E5I~E#L_T3WI3cj$5EYR75H7hy%80a8Ej?Y6hv+fR6wHN%_0$-xL!eI}fdjOK7(GdFD%`f%-qY@-i@fTAS&ETI99jUVg8 zslPSl#d4zbOcrgvopvB2c2A6r^pEr&Sa5I5%@1~BpGq`Wo|x=&)WnnQjE+)$^U-wW zr2Kv?XJby(8fcn z8JgPn)2_#-OhZ+;72R6PspMfCVvtLxFHeb7d}fo(GRjm_+R(*?9QRBr+yPF(iPO~ zA4Tp1<0}#fa{v0CU6jz}q9;!3Pew>ikG1qh$5WPRTQZ~ExQH}b1hDuzRS1}65uydS z~Te*3@?o8fih=mZ`iI!hL5iv3?VUBLQv0X zLtu58MIE7Jbm?)NFUZuMN2_~eh_Sqq*56yIo!+d_zr@^c@UwR&*j!fati$W<=rGGN zD$X`$lI%8Qe+KzBU*y3O+;f-Csr4$?3_l+uJ=K@dxOfZ?3APc5_x2R=a^kLFoxt*_ z4)nvvP+(zwlT5WYi!4l7+HKqzmXKYyM9kL5wX$dTSFSN&)*-&8Q{Q$K-})rWMin8S zy*5G*tRYNqk7&+v;@+>~EIQgf_SB;VxRTQFcm5VtqtKZ)x=?-f+%OY(VLrXb^6*aP zP&0Nu@~l2L!aF8i2!N~fJiHyxRl?I1QNjB)`uP_DuaU?2W;{?0#RGKTr2qH5QqdhK zP__ojm4WV^PUgmrV)`~f>(769t3|13DrzdDeXxqN6XA|_GK*;zHU()a(20>X{y-x| z2P6Ahq;o=)Nge`l+!+xEwY`7Q(8V=93A9C+WS^W%p&yR)eiSX+lp)?*7&WSYSh4i> zJa6i5T9o;Cd5z%%?FhB?J{l+t_)c&_f86gZMU{HpOA=-KoU5lIL#*&CZ_66O5$3?# ztgjGLo`Y7bj&eYnK#5x1trB_6tpu4$EomotZLb*9l6P(JmqG`{z$?lNKgq?GAVhkA zvw!oFhLyX=$K=jTAMwDQ)E-8ZW5$X%P2$YB5aq!VAnhwGv$VR&;Ix#fu%xlG{|j_K zbEYL&bx%*YpXcaGZj<{Y{k@rsrFKh7(|saspt?OxQ~oj_6En(&!rTZPa7fLCEU~mA zB7tbVs=-;cnzv*#INgF_9f3OZhp8c5yk!Dy1+`uA7@eJfvd~g34~wKI1PW%h(y&nA zRwMni12AHEw36)C4Tr-pt6s82EJa^8N#bjy??F*rg4fS@?6^MbiY3;7x=gd~G|Hi& zwmG+pAn!aV>>nNfP7-Zn8BLbJm&7}&ZX+$|z5*5{{F}BRSxN=JKZTa#{ut$v0Z0Fs za@UjXo#3!wACv+p9k*^9^n+(0(YKIUFo`@ib@bjz?Mh8*+V$`c%`Q>mrc5bs4aEf4 zh0qtL1qNE|xQ9JrM}qE>X>Y@dQ?%` zBx(*|1FMzVY&~|dE^}gHJ37O9bjnk$d8vKipgcf+As(kt2cbxAR3^4d0?`}}hYO*O z{+L&>G>AYaauAxE8=#F&u#1YGv%`d*v+EyDcU2TnqvRE33l1r}p#Vmcl%n>NrYOqV z2Car_^^NsZ&K=a~bj%SZlfxzHAxX$>=Q|Zi;E0oyfhgGgqe1Sd5-E$8KV9=`!3jWZCb2crb;rvQ##iw}xm7Da za!H${ls5Ihwxkh^D)M<4Yy3bp<-0a+&KfV@CVd9X6Q?v)$R3*rfT@jsedSEhoV(vqv?R1E8oWV;_{l_+_6= zLjV^-bZU$D_ocfSpRxDGk*J>n4G6s-e>D8JK6-gA>aM^Hv8@)txvKMi7Pi#DS5Y?r zK0%+L;QJdrIPXS2 ztjWAxkSwt2xG$L)Zb7F??cjs!KCTF+D{mZ5e0^8bdu_NLgFHTnO*wx!_8#}NO^mu{FaYeCXGjnUgt_+B-Ru!2_Ue-0UPg2Y)K3phLmR<4 zqUCWYX!KDU!jYF6c?k;;vF@Qh^q(PWwp1ez#I+0>d7V(u_h|L+kX+MN1f5WqMLn!L z!c(pozt7tRQi&duH8n=t-|d)c^;%K~6Kpyz(o53IQ_J+aCapAif$Ek#i0F9U>i+94 zFb=OH5(fk-o`L(o|DyQ(hlozl*2cu#)Y(D*zgNMi1Z!DTex#w#)x(8A-T=S+eByJW z%-k&|XhdZOWjJ&(FTrZNWRm^pHEot_MRQ_?>tKQ&MB~g(&D_e>-)u|`Ot(4j=UT6? zQ&YMi2UnCKlBpwltP!}8a2NJ`LlfL=k8SQf69U)~=G;bq9<2GU&Q#cHwL|o4?ah1` z;fG)%t0wMC;DR?^!jCoKib_iiIjsxCSxRUgJDCE%0P;4JZhJCy)vR1%zRl>K?V6#) z2lDi*W3q9rA zo;yvMujs+)a&00~W<-MNj=dJ@4%tccwT<@+c$#CPR%#aE#Dra+-5eSDl^E>is2v^~ z8lgRwkpeU$|1LW4yFwA{PQ^A{5JY!N5PCZ=hog~|FyPPK0-i;fCl4a%1 z?&@&E-)b4cK)wjXGq|?Kqv0s7y~xqvSj-NpOImt{Riam*Z!wz-coZIMuQU>M%6ben z>P@#o^W;fizVd#?`eeEPs#Gz^ySqJn+~`Pq%-Ee6*X+E>!PJGU#rs6qu0z5{+?`-N zxf1#+JNk7e6AoJTdQwxs&GMTq?Djch_8^xL^A;9XggtGL>!@0|BRuIdE&j$tzvt7I zr@I@0<0io%lpF697s1|qNS|BsA>!>-9DVlgGgw2;;k;=7)3+&t!);W3ulPgR>#JiV zUerO;WxuJqr$ghj-veVGfKF?O7si#mzX@GVt+F&atsB@NmBoV4dK|!owGP005$7LN7AqCG(S+={YA- zn#I{UoP_$~Epc=j78{(!2NLN)3qSm-1&{F&1z4Dz&7Mj_+SdlR^Q5{J=r822d4A@?Rj~xATaWewHUOus{*C|KoH`G zHB8SUT06GpSt)}cFJ18!$Kp@r+V3tE_L^^J%9$&fcyd_AHB)WBghwqBEWW!oh@StV zDrC?ttu4#?Aun!PhC4_KF1s2#kvIh~zds!y9#PIrnk9BWkJpq}{Hlqi+xPOR&A1oP zB0~1tV$Zt1pQuHpJw1TAOS=3$Jl&n{n!a+&SgYVe%igUtvE>eHqKY0`e5lwAf}2x( zP>9Wz+9uirp7<7kK0m2&Y*mzArUx%$CkV661=AIAS=V=|xY{;$B7cS5q0)=oq0uXU z_roo90&gHSfM6@6kmB_FJZ)3y_tt0}7#PA&pWo@_qzdIMRa-;U*Dy>Oo#S_n61Fn! z%mrH%tRmvQvg%UqN_2(C#LSxgQ>m}FKLGG=uqJQuSkk=S@c~QLi4N+>lr}QcOuP&% zQCP^cRk&rk-@lpa0^Lcvdu`F*qE)-0$TnxJlwZf|dP~s8cjhL%>^+L~{umxl5Xr6@ z^7zVKiN1Xg;-h+kr4Yt2BzjZs-Mo54`pDbLc}fWq{34=6>U9@sBP~iWZE`+FhtU|x zTV}ajn*Hc}Y?3agQ+bV@oIRm=qAu%|zE;hBw7kCcDx{pm!_qCxfPX3sh5^B$k_2d` z6#rAeUZC;e-LuMZ-f?gHeZogOa*mE>ffs+waQ+fQl4YKoAyZii_!O0;h55EMzD{;) z8lSJvv((#UqgJ?SCQFqJ-UU?2(0V{;7zT3TW`u6GH6h4m3}SuAAj_K(raGBu>|S&Q zZGL?r9@caTbmRm7p=&Tv?Y1)60*9At38w)$(1c?4cpFY2RLyw9c<{OwQE{b@WI}FQ zTT<2HOF4222d%k70yL~x_d#6SNz`*%@4++8gYQ8?yq0T@w~bF@aOHL2)T4xj`AVps9k z?m;<2ClJh$B6~fOYTWIV*T9y1BpB1*C?dgE{%lVtIjw>4MK{wP6OKTb znbPWrkZjYCbr`GGa%Xo0h;iFPNJBI3fK5`wtJV?wq_G<_PZ<`eiKtvN$IKfyju*^t zXc}HNg>^PPZ16m6bfTpmaW5=qoSsj>3)HS}teRa~qj+Y}mGRE?cH!qMDBJ8 zJB!&-=MG8Tb;V4cZjI_#{>ca0VhG_P=j0kcXVX5)^Sdpk+LKNv#yhpwC$k@v^Am&! z_cz2^4Cc{_BC!K#zN!KEkPzviUFPJ^N_L-kHG6}(X#$>Q=9?!{$A(=B3)P?PkxG9gs#l! zo6TOHo$F|IvjTC3MW%XrDoc7;m-6wb9mL(^2(>PQXY53hE?%4FW$rTHtN`!VgH72U zRY)#?Y*pMA<)x3B-&fgWQ(TQ6S6nUeSY{9)XOo_k=j$<*mA=f+ghSALYwBw~!Egn!jtjubOh?6Cb-Zi3IYn*fYl()^3u zRiX0I{5QaNPJ9w{yh4(o#$geO7b5lSh<5ZaRg9_=aFdZjxjXv(_SCv^v-{ZKQFtAA}kw=GPC7l81GY zeP@0Da{aR#{6`lbI0ON0y#K=t|L*}MG_HSl$e{U;v=BSs{SU3(e*qa(l%rD;(zM^3 zrRgN3M#Sf(Cr9>v{FtB`8JBK?_zO+~{H_0$lLA!l{YOs9KQd4Zt<3*Ns7dVbT{1Ut z?N9{XkN(96?r(4BH~3qeiJ_CAt+h1}O_4IUF$S(5EyTyo=`{^16P z=VhDY!NxkDukQz>T`0*H=(D3G7Np*2P`s(6M*(*ZJa;?@JYj&_z`d5bap=KK37p3I zr5#`%aC)7fUo#;*X5k7g&gQjxlC9CF{0dz*m2&+mf$Sc1LnyXn9lpZ!!Bl!@hnsE5px};b-b-`qne0Kh;hziNC zXV|zH%+PE!2@-IrIq!HM2+ld;VyNUZiDc@Tjt|-1&kq}>muY;TA3#Oy zWdYGP3NOZWSWtx6?S6ES@>)_Yz%%nLG3P>Z7`SrhkZ?shTfrHkYI;2zAn8h65wV3r z^{4izW-c9!MTge3eN=~r5aTnz6*6l#sD68kJ7Nv2wMbL~Ojj0H;M`mAvk*`Q!`KI? z7nCYBqbu$@MSNd+O&_oWdX()8Eh|Z&v&dJPg*o-sOBb2hriny)< zd(o&&kZM^NDtV=hufp8L zCkKu7)k`+czHaAU567$?GPRGdkb4$37zlIuS&<&1pgArURzoWCbyTEl9OiXZBn4p<$48-Gekh7>e)v*?{9xBt z=|Rx!@Y3N@ffW5*5!bio$jhJ7&{!B&SkAaN`w+&3x|D^o@s{ZAuqNss8K;211tUWIi1B!%-ViYX+Ys6w)Q z^o1{V=hK#+tt&aC(g+^bt-J9zNRdv>ZYm9KV^L0y-yoY7QVZJ_ivBS02I|mGD2;9c zR%+KD&jdXjPiUv#t1VmFOM&=OUE2`SNm4jm&a<;ZH`cYqBZoAglCyixC?+I+}*ScG#;?SEAFob{v0ZKw{`zw*tX}<2k zoH(fNh!>b5w8SWSV}rQ*E24cO=_eQHWy8J!5;Y>Bh|p;|nWH|nK9+ol$k`A*u*Y^Uz^%|h4Owu}Cb$zhIxlVJ8XJ0xtrErT zcK;34CB;ohd|^NfmVIF=XlmB5raI}nXjFz;ObQ4Mpl_`$dUe7sj!P3_WIC~I`_Xy@ z>P5*QE{RSPpuV=3z4p3}dh>Dp0=We@fdaF{sJ|+_E*#jyaTrj-6Y!GfD@#y@DUa;& zu4Iqw5(5AamgF!2SI&WT$rvChhIB$RFFF|W6A>(L9XT{0%DM{L`knIQPC$4F`8FWb zGlem_>>JK-Fib;g*xd<-9^&_ue95grYH>5OvTiM;#uT^LVmNXM-n8chJBD2KeDV7t zbnv3CaiyN>w(HfGv86K5MEM{?f#BTR7**smpNZ}ftm+gafRSt=6fN$(&?#6m3hF!>e$X)hFyCF++Qvx(<~q3esTI zH#8Sv!WIl2<&~=B)#sz1x2=+KTHj=0v&}iAi8eD=M->H|a@Qm|CSSzH#eVIR3_Tvu zG8S**NFbz%*X?DbDuP(oNv2;Lo@#_y4k$W+r^#TtJ8NyL&&Rk;@Q}~24`BB)bgwcp z=a^r(K_NEukZ*|*7c2JKrm&h&NP)9<($f)eTN}3|Rt`$5uB0|!$Xr4Vn#i;muSljn zxG?zbRD(M6+8MzGhbOn%C`M#OcRK!&ZHihwl{F+OAnR>cyg~No44>vliu$8^T!>>*vYQJCJg=EF^lJ*3M^=nGCw`Yg@hCmP(Gq^=eCEE1!t-2>%Al{w@*c% zUK{maww*>K$tu;~I@ERb9*uU@LsIJ|&@qcb!&b zsWIvDo4#9Qbvc#IS%sV1_4>^`newSxEcE08c9?rHY2%TRJfK2}-I=Fq-C)jc`gzV( zCn?^noD(9pAf2MP$>ur0;da`>Hr>o>N@8M;X@&mkf;%2A*2CmQBXirsJLY zlX21ma}mKH_LgYUM-->;tt;6F?E5=fUWDwQhp*drQ%hH0<5t2m)rFP%=6aPIC0j$R znGI0hcV~}vk?^&G`v~YCKc7#DrdMM3TcPBmxx#XUC_JVEt@k=%3-+7<3*fTcQ>f~?TdLjv96nb66xj=wVQfpuCD(?kzs~dUV<}P+Fpd)BOTO^<*E#H zeE80(b~h<*Qgez(iFFOkl!G!6#9NZAnsxghe$L=Twi^(Q&48 zD0ohTj)kGLD){xu%pm|}f#ZaFPYpHtg!HB30>F1c=cP)RqzK2co`01O5qwAP zUJm0jS0#mci>|Nu4#MF@u-%-4t>oUTnn_#3K09Hrwnw13HO@9L;wFJ*Z@=gCgpA@p zMswqk;)PTXWuMC-^MQxyNu8_G-i3W9!MLd2>;cM+;Hf&w| zLv{p*hArp9+h2wsMqT5WVqkkc0>1uokMox{AgAvDG^YJebD-czexMB!lJKWllLoBI zetW2;;FKI1xNtA(ZWys!_un~+834+6y|uV&Lo%dKwhcoDzRADYM*peh{o`-tHvwWIBIXW`PKwS3|M>CW37Z2dr!uJWNFS5UwY4;I zNIy1^sr+@8Fob%DHRNa&G{lm?KWU7sV2x9(Ft5?QKsLXi!v6@n&Iyaz5&U*|hCz+d z9vu60IG<v6+^ZmBs_aN!}p|{f(ikVl&LcB+UY;PPz* zj84Tm>g5~-X=GF_4JrVmtEtm=3mMEL1#z+pc~t^Iify^ft~cE=R0TymXu*iQL+XLX zdSK$~5pglr3f@Lrcp`>==b5Z6r7c=p=@A5nXNacsPfr(5m;~ks@*Wu7A z%WyY$Pt*RAKHz_7cghHuQqdU>hq$vD?plol_1EU(Fkgyo&Q2&2e?FT3;H%!|bhU~D z>VX4-6}JLQz8g3%Bq}n^NhfJur~v5H0dbB^$~+7lY{f3ES}E?|JnoLsAG%l^%eu_PM zEl0W(sbMRB3rFeYG&tR~(i2J0)RjngE`N_Jvxx!UAA1mc7J>9)`c=`}4bVbm8&{A` z3sMPU-!r-8de=P(C@7-{GgB<5I%)x{WfzJwEvG#hn3ict8@mexdoTz*(XX!C&~}L* z^%3eYQ8{Smsmq(GIM4d5ilDUk{t@2@*-aevxhy7yk(wH?8yFz%gOAXRbCYzm)=AsM z?~+vo2;{-jkA%Pqwq&co;|m{=y}y2lN$QPK>G_+jP`&?U&Ubq~T`BzAj1TlC`%8+$ zzdwNf<3suPnbh&`AI7RAYuQ<#!sD|A=ky2?hca{uHsB|0VqShI1G3lG5g}9~WSvy4 zX3p~Us^f5AfXlBZ0hA;mR6aj~Q8yb^QDaS*LFQwg!!<|W!%WX9Yu}HThc7>oC9##H zEW`}UQ%JQ38UdsxEUBrA@=6R-v1P6IoIw8$8fw6F{OSC7`cOr*u?p_0*Jvj|S)1cd z-9T);F8F-Y_*+h-Yt9cQQq{E|y^b@r&6=Cd9j0EZL}Pj*RdyxgJentY49AyC@PM<< zl&*aq_ubX%*pqUkQ^Zsi@DqhIeR&Ad)slJ2g zmeo&+(g!tg$z1ao1a#Qq1J022mH4}y?AvWboI4H028;trScqDQrB36t!gs|uZS9}KG0}DD$ zf2xF}M*@VJSzEJ5>ucf+L_AtN-Ht=34g&C?oPP>W^bwoigIncKUyf61!ce!2zpcNT zj&;rPGI~q2!Sy>Q7_lRX*DoIs-1Cei=Cd=+Xv4=%bn#Yqo@C=V`|QwlF0Y- zONtrwpHQ##4}VCL-1ol(e<~KU9-ja^kryz!g!})y-2S5z2^gE$Isj8l{%tF=Rzy`r z^RcP7vu`jHgHLKUE957n3j+BeE(bf;f)Zw($XaU6rZ26Upl#Yv28=8Y`hew{MbH>* z-sGI6dnb5D&dUCUBS`NLAIBP!Vi!2+~=AU+)^X^IpOEAn#+ab=`7c z%7B|mZ>wU+L;^&abXKan&N)O;=XI#dTV|9OMYxYqLbtT#GY8PP$45Rm2~of+J>>HIKIVn(uQf-rp09_MwOVIp@6!8bKV(C#(KxcW z;Pesq(wSafCc>iJNV8sg&`!g&G55<06{_1pIoL`2<7hPvAzR1+>H6Rx0Ra%4j7H-<-fnivydlm{TBr06;J-Bq8GdE^Amo)ptV>kS!Kyp*`wUx=K@{3cGZnz53`+C zLco1jxLkLNgbEdU)pRKB#Pq(#(Jt>)Yh8M?j^w&RPUueC)X(6`@@2R~PV@G(8xPwO z^B8^+`qZnQr$8AJ7<06J**+T8xIs)XCV6E_3W+al18!ycMqCfV>=rW0KBRjC* zuJkvrv;t&xBpl?OB3+Li(vQsS(-TPZ)Pw2>s8(3eF3=n*i0uqv@RM^T#Ql7(Em{(~%f2Fw|Reg@eSCey~P zBQlW)_DioA*yxxDcER@_=C1MC{UswPMLr5BQ~T6AcRyt0W44ffJG#T~Fk}wU^aYoF zYTayu-s?)<`2H(w+1(6X&I4?m3&8sok^jpXBB<|ZENso#?v@R1^DdVvKoD?}3%@{}}_E7;wt9USgrfR3(wabPRhJ{#1es81yP!o4)n~CGsh2_Yj2F^z|t zk((i&%nDLA%4KFdG96pQR26W>R2^?C1X4+a*hIzL$L=n4M7r$NOTQEo+k|2~SUI{XL{ynLSCPe%gWMMPFLO{&VN2pom zBUCQ(30qj=YtD_6H0-ZrJ46~YY*A;?tmaGvHvS^H&FXUG4)%-a1K~ly6LYaIn+4lG zt=wuGLw!%h=Pyz?TP=?6O-K-sT4W%_|Nl~;k~YA^_`gqfe{Xw=PWn#9f1mNz)sFuL zJbrevo(DPgpirvGMb6ByuEPd=Rgn}fYXqeUKyM+!n(cKeo|IY%p!#va6`D8?A*{u3 zEeWw0*oylJ1X!L#OCKktX2|>-z3#>`9xr~azOH+2dXHRwdfnpri9|xmK^Q~AuY!Fg z`9Xx?hxkJge~)NVkPQ(VaW(Ce2pXEtgY*cL8i4E)mM(iz_vdm|f@%cSb*Lw{WbShh41VGuplex9E^VvW}irx|;_{VK=N_WF39^ zH4<*peWzgc)0UQi4fBk2{FEzldDh5+KlRd!$_*@eYRMMRb1gU~9lSO_>Vh-~q|NTD zL}X*~hgMj$*Gp5AEs~>Bbjjq7G>}>ki1VxA>@kIhLe+(EQS0mjNEP&eXs5)I;7m1a zmK0Ly*!d~Dk4uxRIO%iZ!1-ztZxOG#W!Q_$M7_DKND0OwI+uC;PQCbQ#k#Y=^zQve zTZVepdX>5{JSJb;DX3%3g42Wz2D@%rhIhLBaFmx#ZV8mhya}jo1u{t^tzoiQy=jJp zjY2b7D2f$ZzJx)8fknqdD6fd5-iF8e(V}(@xe)N=fvS%{X$BRvW!N3TS8jn=P%;5j zShSbzsLs3uqycFi3=iSvqH~}bQn1WQGOL4?trj(kl?+q2R23I42!ipQ&`I*&?G#i9 zWvNh8xoGKDt>%@i0+}j?Ykw&_2C4!aYEW0^7)h2Hi7$;qgF3;Go?bs=v)kHmvd|`R z%(n94LdfxxZ)zh$ET8dH1F&J#O5&IcPH3=8o;%>OIT6w$P1Yz4S!}kJHNhMQ1(prc zM-jSA-7Iq=PiqxKSWb+YbLB-)lSkD6=!`4VL~`ExISOh2ud=TI&SKfR4J08Bad&rj zcXxMpcNgOB?w$~L7l^wPcXxw$0=$oV?)`I44)}b#ChS`_lBQhvb6ks?HDr3tFgkg&td19?b8=!sETXtp=&+3T$cCwZe z0nAET-7561gsbBws$TVjP7QxY(NuBYXVn9~9%vyN-B#&tJhWgtL1B<%BTS*-2$xB` zO)cMDHoWsm%JACZF--Pa7oP;f!n%p`*trlpvZ!HKoB={l+-(8O;;eYv2A=ra z3U7rSMCkP_6wAy`l|Se(&5|AefXvV1E#XA(LT!% zjj4|~xlZ-kPLNeQLFyXb%$K}YEfCBvHA-Znw#dZSI6V%3YD{Wj2@utT5Hieyofp6Qi+lz!u)htnI1GWzvQsA)baEuw9|+&(E@p8M+#&fsX@Kf`_YQ>VM+40YLv`3-(!Z7HKYg@+l00WGr779i-%t`kid%e zDtbh8UfBVT3|=8FrNian@aR3*DTUy&u&05x%(Lm3yNoBZXMHWS7OjdqHp>cD>g!wK z#~R{1`%v$IP;rBoP0B0P><;dxN9Xr+fp*s_EK3{EZ94{AV0#Mtv?;$1YaAdEiq5)g zYME;XN9cZs$;*2p63Q9^x&>PaA1p^5m7|W?hrXp2^m;B@xg0bD?J;wIbm6O~Nq^^K z2AYQs@7k)L#tgUkTOUHsh&*6b*EjYmwngU}qesKYPWxU-z_D> zDWr|K)XLf_3#k_9Rd;(@=P^S^?Wqlwert#9(A$*Y$s-Hy)BA0U0+Y58zs~h=YtDKxY0~BO^0&9{?6Nny;3=l59(6ec9j(79M?P1cE zex!T%$Ta-KhjFZLHjmPl_D=NhJULC}i$}9Qt?nm6K6-i8&X_P+i(c*LI3mtl3 z*B+F+7pnAZ5}UU_eImDj(et;Khf-z^4uHwrA7dwAm-e4 zwP1$Ov3NP5ts+e(SvM)u!3aZMuFQq@KE-W;K6 zag=H~vzsua&4Sb$4ja>&cSJ)jjVebuj+?ivYqrwp3!5>ul`B*4hJGrF;!`FaE+wKo z#};5)euvxC1zX0-G;AV@R(ZMl=q_~u8mQ5OYl;@BAkt)~#PynFX#c1K zUQ1^_N8g+IZwUl*n0Bb-vvliVtM=zuMGU-4a8|_8f|2GEd(2zSV?aSHUN9X^GDA8M zgTZW06m*iAy@7l>F3!7+_Y3mj^vjBsAux3$%U#d$BT^fTf-7{Y z_W0l=7$ro5IDt7jp;^cWh^Zl3Ga1qFNrprdu#g=n9=KH!CjLF#ucU5gy6*uASO~|b z7gcqm90K@rqe({P>;ww_q%4}@bq`ST8!0{V08YXY)5&V!>Td)?j7#K}HVaN4FU4DZ z%|7OppQq-h`HJ;rw-BAfH* z1H$ufM~W{%+b@9NK?RAp-$(P0N=b<(;wFbBN0{u5vc+>aoZ|3&^a866X@el7E8!E7 z=9V(Ma**m_{DKZit2k;ZOINI~E$|wO99by=HO{GNc1t?nl8soP@gxk8)WfxhIoxTP zoO`RA0VCaq)&iRDN9yh_@|zqF+f07Esbhe!e-j$^PS57%mq2p=+C%0KiwV#t^%_hH zoO?{^_yk5x~S)haR6akK6d|#2TN& zfWcN zc7QAWl)E9`!KlY>7^DNw$=yYmmRto>w0L(~fe?|n6k2TBsyG@sI)goigj=mn)E)I* z4_AGyEL7?(_+2z=1N@D}9$7FYdTu;%MFGP_mEJXc2OuXEcY1-$fpt8m_r2B|<~Xfs zX@3RQi`E-1}^9N{$(|YS@#{ZWuCxo)91{k>ESD54g_LYhm~vlOK_CAJHeYFfuIVB^%cqCfvpy#sU8Do8u}# z>>%PLKOZ^+$H54o@brtL-hHorSKcsjk_ZibBKBgyHt~L z=T6?e0oLX|h!Z3lbkPMO27MM?xn|uZAJwvmX?Yvp#lE3sQFY)xqet>`S2Y@1t)Z*& z;*I3;Ha8DFhk=YBt~{zp=%%*fEC}_8?9=(-k7HfFeN^GrhNw4e?vx*#oMztnO*&zY zmRT9dGI@O)t^=Wj&Og1R3b%(m*kb&yc;i`^-tqY9(0t!eyOkH<$@~1lXmm!SJllE_ zr~{a&w|8*LI>Z^h!m%YLgKv06Js7j7RaoX}ZJGYirR<#4Mghd{#;38j3|V+&=ZUq#1$ zgZb-7kV)WJUko?{R`hpSrC;w2{qa`(Z4gM5*ZL`|#8szO=PV^vpSI-^K_*OQji^J2 zZ_1142N}zG$1E0fI%uqHOhV+7%Tp{9$bAR=kRRs4{0a`r%o%$;vu!_Xgv;go)3!B#;hC5qD-bcUrKR&Sc%Zb1Y($r78T z=eG`X#IpBzmXm(o6NVmZdCQf6wzqawqI63v@e%3TKuF!cQ#NQbZ^?6K-3`_b=?ztW zA>^?F#dvVH=H-r3;;5%6hTN_KVZ=ps4^YtRk>P1i>uLZ)Ii2G7V5vy;OJ0}0!g>j^ z&TY&E2!|BDIf1}U(+4G5L~X6sQ_e7In0qJmWYpn!5j|2V{1zhjZt9cdKm!we6|Pp$ z07E+C8=tOwF<<}11VgVMzV8tCg+cD_z?u+$sBjwPXl^(Ge7y8-=c=fgNg@FxI1i5Y-HYQMEH z_($je;nw`Otdhd1G{Vn*w*u@j8&T=xnL;X?H6;{=WaFY+NJfB2(xN`G)LW?4u39;x z6?eSh3Wc@LR&yA2tJj;0{+h6rxF zKyHo}N}@004HA(adG~0solJ(7>?LoXKoH0~bm+xItnZ;3)VJt!?ue|~2C=ylHbPP7 zv2{DH()FXXS_ho-sbto)gk|2V#;BThoE}b1EkNYGT8U#0ItdHG>vOZx8JYN*5jUh5Fdr9#12^ zsEyffqFEQD(u&76zA^9Jklbiz#S|o1EET$ujLJAVDYF znX&4%;vPm-rT<8fDutDIPC@L=zskw49`G%}q#l$1G3atT(w70lgCyfYkg7-=+r7$%E`G?1NjiH)MvnKMWo-ivPSQHbk&_l5tedNp|3NbU^wk0SSXF9ohtM zUqXiOg*8ERKx{wO%BimK)=g^?w=pxB1Vu_x<9jKOcU7N;(!o3~UxyO+*ZCw|jy2}V*Z22~KhmvxoTszc+#EMWXTM6QF*ks% zW47#2B~?wS)6>_ciKe1Fu!@Tc6oN7e+6nriSU;qT7}f@DJiDF@P2jXUv|o|Wh1QPf zLG31d>@CpThA+Ex#y)ny8wkC4x-ELYCXGm1rFI=1C4`I5qboYgDf322B_Nk@#eMZ% znluCKW2GZ{r9HR@VY`>sNgy~s+D_GkqFyz6jgXKD)U|*eKBkJRRIz{gm3tUd*yXmR z(O4&#ZA*us6!^O*TzpKAZ#}B5@}?f=vdnqnRmG}xyt=)2o%<9jj>-4wLP1X-bI{(n zD9#|rN#J;G%LJ&$+Gl2eTRPx6BQC6Uc~YK?nMmktvy^E8#Y*6ZJVZ>Y(cgsVnd!tV z!%twMNznd)?}YCWyy1-#P|2Fu%~}hcTGoy>_uawRTVl=(xo5!%F#A38L109wyh@wm zdy+S8E_&$Gjm=7va-b7@Hv=*sNo0{i8B7=n4ex-mfg`$!n#)v@xxyQCr3m&O1Jxg! z+FXX^jtlw=utuQ+>Yj$`9!E<5-c!|FX(~q`mvt6i*K!L(MHaqZBTtuSA9V~V9Q$G? zC8wAV|#XY=;TQD#H;;dcHVb9I7Vu2nI0hHo)!_{qIa@|2}9d ztpC*Q{4Py~2;~6URN^4FBCBip`QDf|O_Y%iZyA0R`^MQf$ce0JuaV(_=YA`knEMXw zP6TbjYSGXi#B4eX=QiWqb3bEw-N*a;Yg?dsVPpeYFS*&AsqtW1j2D$h$*ZOdEb$8n0 zGET4Igs^cMTXWG{2#A7w_usx=KMmNfi4oAk8!MA8Y=Rh9^*r>jEV(-{I0=rc);`Y) zm+6KHz-;MIy|@2todN&F+Yv1e&b&ZvycbTHpDoZ>FIiUn+M-=%A2C(I*^Yx@VKf(Z zxJOny&WoWcyKodkeN^5))aV|-UBFw{?AGo?;NNFFcKzk+6|gYfA#FR=y@?;3IoQ zUMI=7lwo9gV9fRvYi}Nd)&gQw7(K3=a0#p27u6Q)7JlP#A)piUUF8B3Li&38Xk$@| z9OR+tU~qgd3T3322E))eV)hAAHYIj$TmhH#R+C-&E-}5Qd{3B}gD{MXnsrS;{Erv1 z6IyQ=S2qD>Weqqj#Pd65rDSdK54%boN+a?=CkR|agnIP6;INm0A*4gF;G4PlA^3%b zN{H%#wYu|!3fl*UL1~f+Iu|;cqDax?DBkZWSUQodSDL4Es@u6zA>sIm>^Aq-&X#X8 zI=#-ucD|iAodfOIY4AaBL$cFO@s(xJ#&_@ZbtU+jjSAW^g;_w`FK%aH_hAY=!MTjI zwh_OEJ_25zTQv$#9&u0A11x_cGd92E74AbOrD`~f6Ir9ENNQAV2_J2Ig~mHWhaO5a zc>fYG$zke^S+fBupw+klDkiljJAha z6DnTemhkf>hv`8J*W_#wBj-2w(cVtXbkWWtE(3j@!A-IfF?`r$MhVknTs3D1N`rYN zKth9jZtX#>v#%U@^DVN!;ni#n1)U&H_uB{6pcq7$TqXJX!Q0P7U*JUZyclb~)l*DS zOLpoQfW_3;a0S$#V0SOwVeeqE$Hd^L`$;l_~2giLYd?7!gUYIpOs!jqSL~pI)4`YuB_692~A z^T#YYQ_W3Rakk}$SL&{`H8mc{>j+3eKprw6BK`$vSSIn;s31M~YlJLApJ)+Gi1{^- zw96WnT9M0Vr_D=e=a}${raR{(35Q!g+8`}vOFj1e&Or(_wp2U2aVQP0_jP57 z2(R4E(E$n!xl<}Zx38wO;27wuQ`P#_j!}L
2 z2qr;As4D4n2X$-Jd_-!fsbu_D(64i;c4cJnP576x_>Q4WNushFwkBV!kVd(AYFXe{ zaqO5`Qfr!#ETmE(B;u_&FITotv~W}QYFCI!&ENKIb1p4fg*Yv1)EDMb==EjHHWM#{ zGMpqb2-LXdHB@D~pE3|+B392Gh4q)y9jBd$a^&cJM60VEUnLtHQD5i-X6PVF>9m_k zDvG3P(?CzdaIrC8s4cu~N9MEb!Tt(g*GK~gIp1Gyeaw3b7#YPx_1T6i zRi#pAMr~PJKe9P~I+ARa$a!K~)t(4LaVbjva1yd;b1Yz2$7MMc`aLmMl(a^DgN(u? zq2o9&Gif@Tq~Yq+qDfx^F*nCnpuPv%hRFc$I!p74*quLt^M}D_rwl10uMTr!)(*=7 zSC5ea@#;l(h87k4T4x)(o^#l76P-GYJA(pOa&F9YT=fS<*O{4agzba^dIrh0hjls<~APlIz9{ zgRY{OMv2s|`;VCoYVj?InYoq^QWuA&*VDyOn@pPvK8l~g#1~~MGVVvtLDt}>id_Z` zn(ihfL?Y}Y4YX335m*Xx(y+bbukchHrM zycIGp#1*K3$!(tgTsMD2VyUSg^yvCwB8*V~sACE(yq2!MS6f+gsxv^GR|Q7R_euYx z&X+@@H?_oQddGxJYS&ZG-9O(X+l{wcw;W7srpYjZZvanY(>Q1utSiyuuonkjh5J0q zGz6`&meSuxixIPt{UoHVupUbFKIA+3V5(?ijn}(C(v>=v?L*lJF8|yRjl-m#^|krg zLVbFV6+VkoEGNz6he;EkP!Z6|a@n8?yCzX9>FEzLnp21JpU0x!Qee}lwVKA})LZJq zlI|C??|;gZ8#fC3`gzDU%7R87KZyd)H__0c^T^$zo@TBKTP*i{)Gp3E0TZ}s3mKSY zix@atp^j#QnSc5K&LsU38#{lUdwj%xF zcx&l^?95uq9on1m*0gp$ruu||5MQo)XaN>|ngV5Jb#^wWH^5AdYcn_1>H~XtNwJd3 zd9&?orMSSuj=lhO?6)Ay7;gdU#E}pTBa5wFu`nejq##Xd71BHzH2XqLA5 zeLEo;9$}~u0pEu@(?hXB_l;{jQ=7m?~mwj-ME~Tw-OHPrR7K2Xq9eCNwQO$hR z3_A?=`FJctNXA#yQEorVoh{RWxJbdQga zU%K##XEPgy?E|K(=o#IPgnbk7E&5%J=VHube|2%!Qp}@LznjE%VQhJ?L(XJOmFVY~ zo-az+^5!Ck7Lo<7b~XC6JFk>17*_dY;=z!<0eSdFD2L?CSp_XB+?;N+(5;@=_Ss3& zXse>@sA7hpq;IAeIp3hTe9^$DVYf&?)={zc9*hZAV)|UgKoD!1w{UVo8D)Htwi8*P z%#NAn+8sd@b{h=O)dy9EGKbpyDtl@NBZw0}+Wd=@65JyQ2QgU}q2ii;ot1OsAj zUI&+Pz+NvuRv#8ugesT<<@l4L$zso0AQMh{we$tkeG*mpLmOTiy8|dNYhsqhp+q*yfZA`Z)UC*(oxTNPfOFk3RXkbzAEPofVUy zZ3A%mO?WyTRh@WdXz+zD!ogo}gbUMV!YtTNhr zrt@3PcP%5F;_SQ>Ui`Gq-lUe&taU4*h2)6RDh@8G1$o!){k~3)DT87%tQeHYdO?B` zAmoJvG6wWS?=0(Cj?Aqj59`p(SIEvYyPGJ^reI z`Hr?3#U2zI7k0=UmqMD35l`>3xMcWlDv$oo6;b`dZq3d!~)W z=4Qk)lE8&>#HV>?kRLOHZYz83{u7?^KoXmM^pazj8`7OwQ=5I!==; zA!uN`Q#n=Drmzg}@^nG!mJp9ml3ukWk96^6*us*;&>s+7hWfLXtl?a}(|-#=P12>A zon1}yqh^?9!;on?tRd6Fk0knQSLl4vBGb87A_kJNDGyrnpmn48lz_%P{* z_G*3D#IR<2SS54L5^h*%=)4D9NPpji7DZ5&lHD|99W86QN_(|aJ<5C~PX%YB`Qt_W z>jF_Os@kI6R!ub4n-!orS(G6~mKL7()1g=Lf~{D!LR7#wRHfLxTjYr{*c{neyhz#U zbm@WBKozE+kTd+h-mgF+ELWqTKin57P;0b){ zii5=(B%S(N!Z=rAFGnM6iePtvpxB_Q9-oq_xH!URn2_d-H~i;lro8r{-g!k-Ydb6_w5K@FOV?zPF_hi z%rlxBv$lQi%bjsu^7KT~@u#*c$2-;AkuP)hVEN?W5MO8C9snj*EC&|M!aK6o12q3+ z8e?+dH17E!A$tRlbJW~GtMDkMPT=m1g-v67q{sznnWOI$`g(8E!Pf!#KpO?FETxLK z2b^8^@mE#AR1z(DT~R3!nnvq}LG2zDGoE1URR=A2SA z%lN$#V@#E&ip_KZL}Q6mvm(dsS?oHoRf8TWL~1)4^5<3JvvVbEsQqSa3(lF*_mA$g zv`LWarC79G)zR0J+#=6kB`SgjQZ2460W zN%lZt%M@=EN>Wz4I;eH>C0VnDyFe)DBS_2{h6=0ZJ*w%s)QFxLq+%L%e~UQ0mM9ud zm&|r){_<*Om%vlT(K9>dE(3AHjSYro5Y1I?ZjMqWyHzuCE0nyCn`6eq%MEt(aY=M2rIzHeMds)4^Aub^iTIT|%*izG4YH;sT`D9MR(eND-SB+e66LZT z2VX)RJsn${O{D48aUBl|(>ocol$1@glsxisc#GE*=DXHXA?|hJT#{;X{i$XibrA}X zFHJa+ssa2$F_UC(o2k2Z0vwx%Wb(<6_bdDO#=a$0gK2NoscCr;vyx?#cF)JjM%;a| z$^GIlIzvz%Hx3WVU481}_e4~aWcyC|j&BZ@uWW1`bH1y9EWXOxd~f-VE5DpueNofN zv7vZeV<*!A^|36hUE;`#x%MHhL(~?eZ5fhA9Ql3KHTWoAeO-^7&|2)$IcD1r5X#-u zN~N0$6pHPhop@t1_d`dO3#TC0>y5jm>8;$F5_A2& zt#=^IDfYv?JjPPTPNx2TL-Lrl82VClQSLWW_$3=XPbH}xM34)cyW5@lnxy=&h%eRq zv29&h^fMoxjsDnmua(>~OnX{Cq!7vM0M4Mr@_18|YuSKPBKUTV$s^So zc}JlAW&bVz|JY#Eyup6Ny{|P_s0Pq;5*tinH+>5Xa--{ z2;?2PBs((S4{g=G`S?B3Ien`o#5DmUVwzpGuABthYG~OKIY`2ms;33SN9u^I8i_H5`BQ%yOfW+N3r|ufHS_;U;TWT5z;b14n1gX%Pn`uuO z6#>Vl)L0*8yl|#mICWQUtgzeFp9$puHl~m&O+vj3Ox#SxQUa?fY*uK?A;00RiFg(G zK?g=7b5~U4QIK`C*um%=Sw=OJ1eeaV@WZ%hh-3<=lR#(Xesk%?)l4p(EpTwPvN99V@TT)!A8SeFTV+frN=r|5l?K#odjijx2nFgc3kI zC$hVs1S-!z9>xn9MZcRk0YXdYlf~8*LfH$IHKD59H&gLz%6 z#mAYSRJufbRi~LRadwM*G!O2>&U<^d`@<)otXZJJxT@G}4kTx0zPDVhVXwiU)$}5Y z`0iV`8EEh&GlUk&VY9m0Mqr*U&|^Bc?FB`<%{x-o0ATntwIA%(YDcxWs$C)%a%d_@ z?fx!Co+@3p7ha$|pWYD}p6#(PG%_h8K7sQjT_P~|3ZEH0DRxa3~bP&&lPMj3C~!H2QD zq>(f^RUFSqf6K3BMBFy$jiuoSE+DhEq$xLDb7{57 z0B|1pSjYJ5F@cHG%qDZ{ogL$P!BK&sR%zD`gbK#9gRZX17EtAJxN% zys^gb2=X9=7HP}N(iRqt(tot2yyeE%s;L}AcMh;~-W~s_eAe!gIUYdQz5j~T)0trh z>#1U$uOyyl%!Pi(gD&)uHe9Q^27_kHyFCC}n^-KL(=OxHqUfex1YS__RJh0m-S>eM zqAk`aSev*z1lI&-?CycgDm=bdQCp}RqS0_d-4Mf&>u2KyGFxKe8JM1N{GNWw0n$FL z1UDp(h0(1I2Jh9I`?IS}h4R~n zRwRz>8?$fFMB2{UPe^$Ifl;Oc>}@Q9`|8DCeR{?LUQLPfaMsxs8ps=D_aAXORZH~< zdcIOca-F;+D3~M+)Vi4h)I4O3<)$65yI)goQ_vk#fb;Uim>UI4Dv9#2b1;N_Wg>-F zNwKeMKY+su#~NL0uE%_$mw1%ddX2Qs2P!ncM+>wnz}OCQX1!q~oS?OqYU;&ESAAwP z452QWL0&u^mraF#=j_ZeBWhm&F|d!QjwRl^7=Bl7@(43=BkN=3{BRv#QHIk>Umc_w zvP>q|q{lJ=zs|W9%a@8%W>C@MYN1D5{(=Af31+pR#kB`cd0-YlQQTg}+ zL|_h=F9JQ|Gux5c0ehaffHNYLf8VwF+qnM6IjBEI_eceee;o;FY@#~FFVsZjBSp!j z8V*Bgmn{RK!!zqGc;jy)z@Zjo>5{%m1?K}fLEL$l6Dl4f=ye0wNI#)2L=^K(&18Gb zJoj8@WBB;P^T#V)I0`aDSy?$rJU{+-5472NyFp>;Vw43j@3Z=;D2eSfyw5*0Q+&ML zsV&&*3c3$pa`qcaGbEB0*CA~Wp3%PkF?B87FV&rWNb|@GU$LB;l|;YutU*k za1hjUL_BX%G^s;BuzRi4Hl?eqC2z&ZrKh1tZDwnufG$g$LX(j!h%F5(n8D@in3lnX z(*8+3ZT6TVYRcSpM1eMeCps=Fz8q%gyM&B=a7(Vf`4k3dN$IM+`BO^_7HZq4BR|7w z+5kOJ;9_$X%-~arA@qmXSzD|+NMh--%5-9u6t(M=f%&z$<_V#Y_lzn{E$MZZG)+A> zu2E`_Y(MBJ2l*AqvCUmU;yBT}#oQ{V=((mC-QGJwsCOH*a;{1JRTKv7DBNG+M!XL7(^jbv&Qy-o9HNFrmN)-`D3WFtXs>1vBOJpI(=x; zKhJlFdfMf^G#oU(w1+ucMKYPZaDp>$kt=wiYsBCjUY-uz<4JziB>6fXDSLH*2Y z&Px5y`#3!fF=c4>fCMdg-tX582pemU@ZxyFbznL8-=TTo1Sybg9>7h*J^9^~XxXJO z`k9v~=4amxl<;FCV9h2k%?^-ZUzQy^#{JleyH23o1S{r<+t#z6jKS<9rbAM96^1iY zi6{IjauB)UwBhC-_L(MzGCxhhv`?ryc zja_Uwi7$8l!}*vjJppGyp#Wz=*?;jC*xQ&J894rql5A$2giJRtV&DWQh#(+Vs3-5_ z69_tj(>8%z1VtVp>a74r5}j2rG%&;uaTQ|fr&r%ew-HO}76i8`&ki%#)~}q4Y|d$_ zfNp9uc#$#OEca>>MaY6rF`dB|5#S)bghf>>TmmE&S~IFw;PF0UztO6+R-0!TSC?QP z{b(RA_;q3QAPW^XN?qQqu{h<}Vfiv}Rr!lA$C79^1=U>+ng9Dh>v{`?AOZt>CrQ=o zI}=mSnR))8fJpO->rcX?H);oqSQUZ?sR!fH2SoFdcPm5*2y<_u;4h;BqcF*XbwWSv zcJN%!g|L(22Xp!^1?c;T&qm%rpkP&2EQC3JF+SENm$+@7#e!UKD1uQ{TDw43?!b!3 zUooS_rt=xJfa&h?c^hfV>YwQXre3qosz_^c#)FO~d!<)2o}Oxz5HWtr<)1Yw012v4 zhv0w(RfJspDnA^-6Jmr;GkWt%{mAYOm6yPb&Vl&rv@D^K&;#?=X{kaK5FhScNJ_3> z#5u(Saisq2(~pVlrfG#@kLM#Ot~5rZZc%B&h1=gen?R+#t^1bYKf zVvtefX=D$*)39e^2@!~A_}9c${Gf0?1;dk=!Itp#s%0>Io%k`9(bDeI-udd&E6Zfu zcaiv(h`DM3W3Mfda)fYwhB=8RAPkotVt5-z21Ij~Ot9A^SK-1u*zFVK&mF?q1;|wy zrF+XWs^5Q-%Z6I62gTwrRe#F>riVM#fv_TihxSJ6to1X7NVszgivoTa!fPfBBYj94 zuc2m zL_k-<1FoORng190; z+@DGs;NHgGW8%wjH$EpvQ-Hd! znZdIh#!H5nOStiOKNV8}QvY~=VMqtG&p$ByF&%pe_gR`|H5ULg47lk20(Xe=k8ptc zn%EmTI7k9gNE=!IN4WnbymtsKoHn2-cL65z^9cQOSp>XFzo;!h*x1s^0U!<{Y-VZ1 zXJ7zekkYf(`@dZ3F9|?O+*dUL4K4?0@V^>I2;k-a1%ZgY9w2|C5r0R5?80e-|&4yEwkklXmZ)!QSYG) zXBKOz|IPC2W_X!t^cgb^@D=|>r@x$f{3Y+`%NoDT^Y@JIuJ%jxe;es9vi`kJmbnPYT%X}rzs0K#=H)Q`)_L7%?KLLJP+0XJbL&JgdJE{i*){MOFSK z{7XUfXZR-Te}aE8RelNkQV0AQ7RC0TVE^o8c!~K^RQ4GY+xed`|A+zjZ(qij@~zLP zkS@Q0`rpM|UsnI6B;_+vw)^iA{n0%C7N~ql@KXNonIOUIHwgYg4Dcn>OOdc=rUl>M zVEQe|u$P=Kb)TL&-2#4t^Pg0pUQ)dj%6O)#3;zwOe~`_1$@Ef`;F+l=>NlAFFbBS0 zN))`LdKnA;OjQ{B+f;z>i|wCv-CmNs46S`8X-oKRl0V+pKZ%XJWO*6G`OMOs^xG_d zj_7-p06{fybw_P;UzX^eX5Pkcrm04%9rPFa56 zyZE \(.*\)$'` - 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="" - -# 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, switch paths to Windows format before running java -if $cygwin ; 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=$((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" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/example/ios/Supporting Files/settings.gradle.kts b/example/ios/Supporting Files/settings.gradle.kts deleted file mode 100644 index 0d17cfa52..000000000 --- a/example/ios/Supporting Files/settings.gradle.kts +++ /dev/null @@ -1,61 +0,0 @@ -/*********************************************** - * - * Changes made to this file should also be reflected in the [settings.gradle.kts] in the root of the project - * - ***********************************************/ - -pluginManagement { - - repositories { - gradlePluginPortal() - google() - } - - resolutionStrategy { - eachPlugin { - - val kalugaAndroidGradlePluginVersion = settings.extra["kaluga.androidGradlePluginVersion"] - val kalugaKotlinVersion = settings.extra["kaluga.kotlinVersion"] - val kalugaKtLintGradlePluginVersion = settings.extra["kaluga.ktLintGradlePluginVersion"] - val kalugaGoogleServicesGradlePluginVersion = settings.extra["kaluga.googleServicesGradlePluginVersion"] - - when (requested.id.id) { - "org.jetbrains.kotlin.multiplatform", - "org.jetbrains.kotlin.plugin.serialization", - "org.jetbrains.kotlin.android", - "org.jetbrains.kotlin.kapt", - -> useVersion("$kalugaKotlinVersion") - "com.android.library", - "com.android.application", - -> useVersion("$kalugaAndroidGradlePluginVersion") - "org.jlleitschuh.gradle.ktlint", - "org.jlleitschuh.gradle.ktlint-idea", - -> useVersion("$kalugaKtLintGradlePluginVersion") - "com.google.gms:google-services" - -> useVersion("com.google.gms:google-services:$kalugaGoogleServicesGradlePluginVersion") - } - } - } -} - -includeBuild("../../../convention-plugins") -apply("../../../gradle/ext.gradle.kts") - -val ext = (gradle as ExtensionAware).extra - -when (ext["example_embedding_method"] ) { - "composite" -> { - includeBuild("../../..") - } -} - -include(":android") -project(":android").projectDir = file("../../android") - -include(":KotlinNativeFramework") -project(":KotlinNativeFramework").projectDir = file("../KotlinNativeFramework") - -include(":shared") -project(":shared").projectDir = file("../../shared") - -rootProject.name = file("../..").name diff --git a/example/readme.md b/example/readme.md deleted file mode 100644 index 6d68b4953..000000000 --- a/example/readme.md +++ /dev/null @@ -1,81 +0,0 @@ -# Example project - -This example project has the following parts: -- An Android app in [android](/example/android) -- A shared code module in [shared](/example/shared). This exposes and android library, and kotlin native sourceset that depends on a common source set. These are then included in the build of the iOS and Android example apps. -- An iOS app in [ios](/example/ios) - -The example app can load the main components either through your (local) maven repository, or as gradle modules. - -The advantage of using a gradle module is that you can edit the source of components and run them in the example right away, without needing to publish them to maven first. - -By default kaluga dependency is included as a composite build. You also can put the property `kaluga.exampleEmbeddingMethod=composite` in the `local.properties` file in the root of the project (this file is already generated by your IDE normally) to specify it explicitly. To enable maven use `kaluga.exampleEmbeddingMethod=maven`. - -You can also set the `kaluga.exampleMavenRepo` property to specify which maven repo to look in. By default this is `mavenLocal()` (this can also be used by explicitly setting the value `local`). For example to set this to the sonatype snapshot repository use: - -```properties -kaluga.exampleMavenRepo=https://oss.sonatype.org/content/repositories/snapshots/ -``` - -In conjunction, you can set the `kaluga.libraryVersion` property. This will override the version of kaluga to look for. For example: - -```properties -kaluga.libraryVersion=0.2.2-SNAPSHOT -``` - -Please note that this also overrides the version when building kaluga itself. - -### Links -In order to make kaluga-links example work correctly, you must sign the app with a signing certificate beloging to our team or using the `debug.keystore` file we provide. - -#### Why? -The goal of the example is to show the usages of `links` module, but on top of that we are also showing the flow where user are redirected to the app without being asked "Open with..." dialog. -To do so the backend must contain in `https://whatever.domain/.well-known` two file named respectively `assetlinks.json` and `apple-app-site-association`. - -## Android -`assetlinks.json` should have the following format - -``` -[ - { - "relation": ["delegate_permission/common.handle_all_urls"], - "target": { - "namespace": "android_app", - "package_name": "app.package", - "sha256_cert_fingerprints": [ - , - ... - , - ] - } - } -] -``` -[More info about Android App Links](https://developer.android.com/training/app-links). - -## iOS -While `apple-app-site-association` will be - -``` -{ - "applinks": { - "apps": [], // This array MUST be empty (for more info check Universal link's doc below). --> - "details": [ - { - "appID": "appId", - "paths": ["path_1", "path_2"] // Use wildcard "*" if you accept every path. - } - ] - } -} -``` -`appId` = `teamId`.`bundleId` -[More info about iOS Universal Links](https://developer.apple.com/ios/universal-links/). - -# Using IDEs - -The iOS app can be opened by either XCode or the KMM plugin of Android Studio. - -The Android app can be opened with Android Studio, at times the latest Beta or Canary release is needed (see [DEVELOP](/DEVELOP.md)) - -This project uses a structure created by Appcode (the Kotlin/Native plugin of Appcode is now unfortunately not recent anymore) , the root `settings.gradle.kts` file is under [Supporting Files](/example/ios/Supporting%20Files/), so this is the directory to open with Android studio. diff --git a/newexample/settings.gradle.kts b/example/settings.gradle.kts similarity index 100% rename from newexample/settings.gradle.kts rename to example/settings.gradle.kts diff --git a/example/shared/build.gradle.kts b/example/shared/build.gradle.kts index be70d1e5e..fcc1ed645 100644 --- a/example/shared/build.gradle.kts +++ b/example/shared/build.gradle.kts @@ -1,36 +1,57 @@ plugins { kotlin("multiplatform") kotlin("plugin.serialization") - id("jacoco") id("com.android.library") + id("jacoco") id("org.jlleitschuh.gradle.ktlint") } -apply(from = "../../gradle/component.gradle.kts") +val libraryVersion = Library.version +val modules = listOf( + "alerts" to true, + "architecture" to true, + "base" to false, + "bluetooth" to false, + "beacons" to false, + "date-time-picker" to true, + "hud" to true, + "keyboard" to true, + "links" to false, + "location" to false, + "logging" to false, + "resources" to true, + "review" to true, + "system" to true, + "permissions" to true +) + +commonComponent { + logger.lifecycle("Configure framework") + baseName = "KalugaExampleShared" + isStatic = false + transitiveExport = true + modules.forEach { (module, isExportable) -> + if (isExportable) { + export("com.splendo.kaluga:$module:$libraryVersion") + } + } +} kotlin { sourceSets { - commonMain { - val ext = (gradle as ExtensionAware).extra - + getByName("commonMain") { dependencies { - val libraryVersion = ext["library_version"] - api("com.splendo.kaluga:alerts:$libraryVersion") - api("com.splendo.kaluga:architecture:$libraryVersion") - api("com.splendo.kaluga:base:$libraryVersion") - api("com.splendo.kaluga:bluetooth:$libraryVersion") - api("com.splendo.kaluga:beacons:$libraryVersion") - api("com.splendo.kaluga:date-time-picker:$libraryVersion") - api("com.splendo.kaluga:hud:$libraryVersion") - api("com.splendo.kaluga:keyboard:$libraryVersion") - api("com.splendo.kaluga:links:$libraryVersion") - api("com.splendo.kaluga:location:$libraryVersion") - api("com.splendo.kaluga:logging:$libraryVersion") - api("com.splendo.kaluga:resources:$libraryVersion") - api("com.splendo.kaluga:review:$libraryVersion") - api("com.splendo.kaluga:system:$libraryVersion") - api("com.splendo.kaluga:permissions:$libraryVersion") + modules.forEach { (module, _) -> + api("com.splendo.kaluga:$module:$libraryVersion") + } + expose(Dependencies.Koin.Core) } } } } + +android { + dependencies { + expose(Dependencies.Koin.Android) + } +} diff --git a/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt similarity index 100% rename from newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt rename to example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentSubPageViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentSubPageViewModel.kt deleted file mode 100644 index 12f9b5f96..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentSubPageViewModel.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class BottomSheetParentSubPageNavigation : NavigationAction(null) { - object Back : BottomSheetParentSubPageNavigation() -} - -class BottomSheetParentSubPageViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val text = "bottom_sheet_sub_page_title".localized() - - fun onBackPressed() { - navigator.navigate(BottomSheetParentSubPageNavigation.Back) - } -} diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentViewModel.kt deleted file mode 100644 index 337e623dc..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentViewModel.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class BottomSheetParentNavigation : NavigationAction(null) { - object ShowSheet : BottomSheetParentNavigation() - object SubPage : BottomSheetParentNavigation() -} - -class BottomSheetParentViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val sheetText = "bottom_sheet_show_sheet".localized() - val subPageText = "bottom_sheet_show_sub_page".localized() - - fun onShowSheetPressed() { - navigator.navigate(BottomSheetParentNavigation.ShowSheet) - } - - fun onSubPagePressed() { - navigator.navigate(BottomSheetParentNavigation.SubPage) - } -} diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetSubPageViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetSubPageViewModel.kt deleted file mode 100644 index 07ed302ca..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetSubPageViewModel.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class BottomSheetSubPageNavigation : NavigationAction(null) { - object Back : BottomSheetSubPageNavigation() - object Close : BottomSheetSubPageNavigation() -} - -class BottomSheetSubPageViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val text = "bottom_sheet_sheet_sub_page_title".localized() - - fun onBackPressed() { - navigator.navigate(BottomSheetSubPageNavigation.Back) - } - - fun onClosePressed() { - navigator.navigate(BottomSheetSubPageNavigation.Close) - } -} diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetViewModel.kt deleted file mode 100644 index bec596366..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetViewModel.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class BottomSheetNavigation : NavigationAction(null) { - object Close : BottomSheetNavigation() - object SubPage : BottomSheetNavigation() -} - -class BottomSheetViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val text = "bottom_sheet_sheet_content".localized() - val buttonText = "bottom_sheet_show_sub_page".localized() - - fun onSubPagePressed() { - navigator.navigate(BottomSheetNavigation.SubPage) - } - - fun onClosePressed() { - navigator.navigate(BottomSheetNavigation.Close) - } -} diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/model/ContactDetails.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/model/ContactDetails.kt deleted file mode 100644 index 3357616aa..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/model/ContactDetails.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.platformspecific.compose.contacts.model - -import kotlinx.serialization.Serializable - -@Serializable -data class ContactDetails(val name: String, val email: String) diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactDetailsViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactDetailsViewModel.kt deleted file mode 100644 index 7f26c543f..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactDetailsViewModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails - -sealed class ContactDetailsNavigation(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { - class SendEmail(email: String) : ContactDetailsNavigation( - email, - NavigationBundleSpecType.StringType - ) - object Close : ContactDetailsNavigation(Unit, NavigationBundleSpecType.UnitType) -} - -class ContactDetailsViewModel( - val contactDetails: ContactDetails, - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - - val sendEmailButtonText = "Send email" - - fun sendEmail() { - navigator.navigate(ContactDetailsNavigation.SendEmail(contactDetails.email)) - } - - fun back() { - navigator.navigate(ContactDetailsNavigation.Close) - } -} diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactsListViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactsListViewModel.kt deleted file mode 100644 index 5e66bd4b1..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/viewModel/ContactsListViewModel.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.observable.FlowInitializedObservable -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch - -sealed class ContactsListNavigation(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { - data class ShowContactDetails(val contactDetails: ContactDetails) : ContactsListNavigation( - contactDetails, - NavigationBundleSpecType.SerializedType(ContactDetails.serializer()) - ) -} - -class ContactsListViewModel( - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - val contacts: FlowInitializedObservable> = - MutableStateFlow(emptyList()) - .also { - coroutineScope.launch { - it.value = loadContacts() - } - }.toInitializedObservable(coroutineScope) - - private suspend fun loadContacts(): List { - // a real call shall happen here - return listOf("Alice", "Bob", "Charlie", "David").map { name -> - ContactDetails(name = name, email = "${name.lowercase()}@example.com") - } - } - - fun onContactClick(contactDetails: ContactDetails) { - navigator.navigate( - ContactsListNavigation.ShowContactDetails(contactDetails) - ) - } -} diff --git a/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt similarity index 100% rename from newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt rename to example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt diff --git a/example/shared/src/commonMain/kotlin/AlertViewModel.kt b/example/shared/src/commonMain/kotlin/AlertViewModel.kt deleted file mode 100644 index 597593330..000000000 --- a/example/shared/src/commonMain/kotlin/AlertViewModel.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared - -import com.splendo.kaluga.alerts.Alert -import com.splendo.kaluga.alerts.BaseAlertPresenter -import com.splendo.kaluga.alerts.buildActionSheet -import com.splendo.kaluga.alerts.buildAlert -import com.splendo.kaluga.alerts.buildAlertWithInput -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.logging.debug -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -class AlertViewModel(val builder: BaseAlertPresenter.Builder) : BaseLifecycleViewModel() { - - fun showAlert() { - coroutineScope.launch { - val okAction = Alert.Action("OK", Alert.Action.Style.POSITIVE) - val cancelAction = Alert.Action("Cancel", Alert.Action.Style.NEGATIVE) - val alert = builder.buildAlert(this) { - setTitle("Hello, Kaluga 🐟") - setMessage("This is sample message") - addActions(okAction, cancelAction) - } - when (alert.show()) { - okAction -> debug("OK pressed") - cancelAction -> debug("Cancel pressed") - } - } - } - - fun showAndDismissAfter(timeSecs: Long) { - coroutineScope.launch { - val job = launch { - builder.buildAlert(coroutineScope) { - setTitle("Wait for $timeSecs sec...") - setPositiveButton("OK") - }.show() - } - launch { - delay(timeSecs * 1_000) - job.cancel() - } - } - } - - fun showAlertWithInput() { - coroutineScope.launch { - val okAction = Alert.Action("OK", Alert.Action.Style.POSITIVE) - val cancelAction = Alert.Action("Cancel", Alert.Action.Style.NEGATIVE) - val alert = builder.buildAlertWithInput(this) { - setTitle("Hello, Kaluga 🐟") - setMessage("Type something!") - addActions(okAction, cancelAction) - setTextInput(placeholder = "This is a sample hint..") { - debug("Input value changed to: $it") - } - } - when (alert.show()) { - okAction -> debug("OK pressed") - cancelAction -> debug("Cancel pressed") - } - } - } - - fun showAlertWithList() { - coroutineScope.launch { - builder.buildActionSheet(this) { - setTitle("Select an option") - addActions( - Alert.Action("Option 1") { debug("Option 1") }, - Alert.Action("Option 2") { debug("Option 2") }, - Alert.Action("Option 3") { debug("Option 3") }, - Alert.Action("Option 4") { debug("Option 4") } - ) - }.show() - } - } -} diff --git a/example/shared/src/commonMain/kotlin/HelloShared.kt b/example/shared/src/commonMain/kotlin/HelloShared.kt deleted file mode 100644 index 725690c56..000000000 --- a/example/shared/src/commonMain/kotlin/HelloShared.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared - -fun helloCommon(): String { - return "Hello from the shared module common source" -} diff --git a/example/shared/src/commonMain/kotlin/HudViewModel.kt b/example/shared/src/commonMain/kotlin/HudViewModel.kt deleted file mode 100644 index 35f2cbbf3..000000000 --- a/example/shared/src/commonMain/kotlin/HudViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared - -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.hud.BaseHUD -import com.splendo.kaluga.hud.HUDStyle -import com.splendo.kaluga.hud.build -import com.splendo.kaluga.hud.dismissAfter -import com.splendo.kaluga.hud.presentDuring -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -class HudViewModel(val builder: BaseHUD.Builder) : BaseLifecycleViewModel() { - - fun onShowSystemPressed() { - // SYSTEM style by default - // No title by default - coroutineScope.launch { - builder.build(this).present().dismissAfter(3_000) - } - } - - fun onShowCustomPressed() { - coroutineScope.launch { - builder.build(this) { - setStyle(HUDStyle.CUSTOM) - setTitle("This is a custom title") - }.presentDuring { - // Simulate heavy task - delay(3_000) - } - } - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/ExampleViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/ExampleViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureDetailsViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureDetailsViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListBeaconViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListBeaconViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothServiceViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothServiceViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/datetimepicker/DateTimePickerViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/datetimepicker/DateTimePickerViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/featureList/FeatureListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/featureList/FeatureListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/featureList/PlatformSpecific.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/featureList/PlatformSpecific.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/keyboard/KeyboardViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/keyboard/KeyboardViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/resources/ButtonViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/resources/ButtonViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/resources/ColorViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/resources/ColorViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/resources/LabelViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/resources/LabelViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/viewmodel/resources/ResourcesListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt similarity index 100% rename from example/shared/src/commonMain/kotlin/viewmodel/resources/ResourcesListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt similarity index 100% rename from newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt diff --git a/example/shared/src/commonMain/kotlin/stylable/ButtonStyles.kt b/example/shared/src/commonMain/kotlin/stylable/ButtonStyles.kt deleted file mode 100644 index 07eba41b7..000000000 --- a/example/shared/src/commonMain/kotlin/stylable/ButtonStyles.kt +++ /dev/null @@ -1,224 +0,0 @@ -package com.splendo.kaluga.example.shared.stylable - -import com.splendo.kaluga.resources.DefaultColors -import com.splendo.kaluga.resources.defaultFont -import com.splendo.kaluga.resources.stylable.BackgroundStyle -import com.splendo.kaluga.resources.stylable.ButtonStateStyle -import com.splendo.kaluga.resources.stylable.ButtonStyle -import com.splendo.kaluga.resources.stylable.GradientStyle - -object ButtonStyles { - - val textButton by lazy { - ButtonStyle(TextStyles.redText) - } - val redButton by lazy { - ButtonStyle( - TextStyles.whiteText, - backgroundColor = DefaultColors.red, - pressedBackgroundColor = DefaultColors.maroon, - disabledBackgroundColor = DefaultColors.lightGray - ) - } - val roundedButton by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle(DefaultColors.white, DefaultColors.deepSkyBlue, BackgroundStyle.Shape.Rectangle(10.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT))), - pressedStyle = ButtonStateStyle(DefaultColors.azure, DefaultColors.lightSkyBlue, BackgroundStyle.Shape.Rectangle(5.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT))), - disabledStyle = ButtonStateStyle(DefaultColors.black, DefaultColors.lightGray, BackgroundStyle.Shape.Rectangle(10.0f)) - ) - } - val ovalButton by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle(DefaultColors.white, DefaultColors.deepSkyBlue, BackgroundStyle.Shape.Oval), - pressedStyle = ButtonStateStyle(DefaultColors.azure, DefaultColors.lightSkyBlue, BackgroundStyle.Shape.Oval), - disabledStyle = ButtonStateStyle(DefaultColors.black, DefaultColors.lightGray, BackgroundStyle.Shape.Oval) - ) - } - val redButtonWithStroke by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle( - DefaultColors.white, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.red), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), - BackgroundStyle.Shape.Rectangle() - ) - ), - pressedStyle = ButtonStateStyle( - DefaultColors.white, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.maroon), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), - BackgroundStyle.Shape.Rectangle() - ) - ), - disabledStyle = ButtonStateStyle( - DefaultColors.black, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.lightGray), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), - BackgroundStyle.Shape.Rectangle() - ) - ) - ) - } - val roundedButtonWithStroke by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle( - DefaultColors.white, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.deepSkyBlue), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), - BackgroundStyle.Shape.Rectangle(10.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT)) - ) - ), - pressedStyle = ButtonStateStyle( - DefaultColors.azure, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.lightSkyBlue), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.azure), - BackgroundStyle.Shape.Rectangle(5.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT)) - ) - ), - disabledStyle = ButtonStateStyle( - DefaultColors.black, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.lightGray), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), - BackgroundStyle.Shape.Rectangle(10.0f) - ) - ) - ) - } - val ovalButtonWithStroke by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle( - DefaultColors.white, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.deepSkyBlue), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), - BackgroundStyle.Shape.Oval - ) - ), - pressedStyle = ButtonStateStyle( - DefaultColors.azure, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.lightSkyBlue), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.azure), - BackgroundStyle.Shape.Oval - ) - ), - disabledStyle = ButtonStateStyle( - DefaultColors.black, - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(DefaultColors.lightGray), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), - BackgroundStyle.Shape.Oval - ) - ) - ) - } - - val linearGradientButton by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle( - DefaultColors.white, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), GradientStyle.Linear.Orientation.LEFT_RIGHT)), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), - BackgroundStyle.Shape.Rectangle() - ) - ), - pressedStyle = ButtonStateStyle( - DefaultColors.azure, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), GradientStyle.Linear.Orientation.LEFT_RIGHT)), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), - BackgroundStyle.Shape.Rectangle() - ) - ), - disabledStyle = ButtonStateStyle( - DefaultColors.black, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.lightGray, DefaultColors.gray), GradientStyle.Linear.Orientation.LEFT_RIGHT)), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), - BackgroundStyle.Shape.Rectangle() - ) - ) - ) - } - - val radialGradientButton by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle( - DefaultColors.white, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), 50.0f, GradientStyle.CenterPoint(0.3f, 0.3f))), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), - BackgroundStyle.Shape.Rectangle() - ) - ), - pressedStyle = ButtonStateStyle( - DefaultColors.azure, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), 25.0f, GradientStyle.CenterPoint(0.6f, 0.6f))), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), - BackgroundStyle.Shape.Rectangle() - ) - ), - disabledStyle = ButtonStateStyle( - DefaultColors.black, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.lightGray, DefaultColors.gray), 50.0f)), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), - BackgroundStyle.Shape.Rectangle() - ) - ) - ) - } - - val angularGradientButton by lazy { - ButtonStyle( - defaultFont, - 12.0f, - defaultStyle = ButtonStateStyle( - DefaultColors.white, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), GradientStyle.CenterPoint(0.3f, 0.3f))), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), - BackgroundStyle.Shape.Rectangle() - ) - ), - pressedStyle = ButtonStateStyle( - DefaultColors.azure, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), GradientStyle.CenterPoint(0.6f, 0.6f))), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), - BackgroundStyle.Shape.Rectangle() - ) - ), - disabledStyle = ButtonStateStyle( - DefaultColors.black, - BackgroundStyle( - BackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.lightGray, DefaultColors.gray))), - BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), - BackgroundStyle.Shape.Rectangle() - ) - ) - ) - } -} diff --git a/example/shared/src/commonMain/kotlin/stylable/TextStyles.kt b/example/shared/src/commonMain/kotlin/stylable/TextStyles.kt deleted file mode 100644 index b9d04f117..000000000 --- a/example/shared/src/commonMain/kotlin/stylable/TextStyles.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.splendo.kaluga.example.shared.stylable - -import com.splendo.kaluga.resources.DefaultColors -import com.splendo.kaluga.resources.defaultBoldFont -import com.splendo.kaluga.resources.defaultFont -import com.splendo.kaluga.resources.defaultItalicFont -import com.splendo.kaluga.resources.defaultMonospaceFont -import com.splendo.kaluga.resources.stylable.TextAlignment -import com.splendo.kaluga.resources.stylable.TextStyle - -object TextStyles { - - val defaultText by lazy { - TextStyle(defaultFont, DefaultColors.dimGray, 12.0f) - } - val defaultTitle by lazy { - TextStyle(defaultFont, DefaultColors.dimGray, 16.0f) - } - val defaultBoldText by lazy { - TextStyle(defaultBoldFont, DefaultColors.dimGray, 12.0f) - } - val defaultItalicText by lazy { - TextStyle(defaultItalicFont, DefaultColors.dimGray, 12.0f) - } - val defaultMonospaceText by lazy { - TextStyle(defaultMonospaceFont, DefaultColors.dimGray, 12.0f) - } - val whiteText by lazy { - TextStyle(defaultFont, DefaultColors.white, 12.0f) - } - val redText by lazy { - TextStyle(defaultFont, DefaultColors.red, 12.0f) - } - val oppositeText by lazy { - TextStyle(defaultFont, DefaultColors.dimGray, 12.0f, TextAlignment.END) - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt deleted file mode 100644 index 98dec51f7..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.architecture - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.observable.ObservableOptional -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.observable.toInitializedSubject -import com.splendo.kaluga.architecture.observable.toUninitializedObservable -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class InputNavigation(inputDetails: InputDetails) : SingleValueNavigationAction( - inputDetails, - NavigationBundleSpecType.SerializedType(InputDetails.serializer()) -) - -class ArchitectureInputViewModel(navigator: Navigator>) : NavigatingViewModel>(navigator) { - - val nameHeader = observableOf("Enter your Name") - val numberHeader = observableOf("Enter a Number") - - private val _nameInput = MutableStateFlow("") - val nameInput = _nameInput.toInitializedSubject(coroutineScope) - - private val _numberInput = MutableStateFlow("") - val numberInput = _numberInput.toInitializedSubject(coroutineScope) - - private val _isNameValid: Flow get() { - return _nameInput.map { - it.isNotEmpty() - } - } - val isNameValid = _isNameValid.toUninitializedObservable(coroutineScope) - - private val _isNumberValid: Flow get() { - return _numberInput.map { - it.toIntOrNull() != null - } - } - val isNumberValid = _isNumberValid.toUninitializedObservable(coroutineScope) - - private val isValid = combine(_isNameValid, _isNumberValid) { - validName, validNumber -> - validName && validNumber - } - - fun onShowDetailsPressed() { - val nameResult: ObservableOptional by nameInput - val name: String? by nameResult - val numberResult: ObservableOptional by numberInput - val number: String? by numberResult - coroutineScope.launch { - if (isValid.first()) { - navigator.navigate( - InputNavigation( - InputDetails(name ?: "", number?.toIntOrNull() ?: 0) - ) - ) - } - } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt deleted file mode 100644 index 4f752265f..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.beacons - -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.bluetooth.beacons.Beacons -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class BeaconsListViewModel(private val service: Beacons) : BaseLifecycleViewModel() { - - private val _isScanning = MutableStateFlow(false) - val isScanning = _isScanning.toInitializedObservable(coroutineScope) - - private val _beacons = MutableStateFlow>(emptyList()) - val beacons = _beacons.toInitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - - scope.launch { - service - .isMonitoring() - .collect { _isScanning.value = it } - } - - scope.launch { - service.beacons - .map { beacons -> - beacons.map { beacon -> - BeaconsListBeaconViewModel(beacon.identifier, service) - } - } - .collect { beacons -> - cleanDevices() - _beacons.value = beacons - } - } - } - - fun onScanPressed() { - if (_isScanning.value) { - service.stopMonitoring() - } else { - service.startMonitoring(coroutineScope) - } - } - - override fun onCleared() { - super.onCleared() - cleanDevices() - } - - private fun cleanDevices() { - _beacons.value.forEach { it.onCleared() } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt deleted file mode 100644 index 68a08b032..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bluetooth - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.observable.toUninitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.base.text.format -import com.splendo.kaluga.bluetooth.Bluetooth -import com.splendo.kaluga.bluetooth.device.ConnectableDeviceState -import com.splendo.kaluga.bluetooth.device.NotConnectableDeviceState -import com.splendo.kaluga.bluetooth.device.Identifier -import com.splendo.kaluga.bluetooth.device.stringValue -import com.splendo.kaluga.bluetooth.distance -import com.splendo.kaluga.bluetooth.get -import com.splendo.kaluga.bluetooth.info -import com.splendo.kaluga.bluetooth.rssi -import com.splendo.kaluga.bluetooth.services -import com.splendo.kaluga.bluetooth.state -import com.splendo.kaluga.bluetooth.updateRssi -import com.splendo.kaluga.resources.localized -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class DeviceDetailsSpec : NavigationBundleSpec>(setOf(DeviceDetailsSpecRow.UUIDRow)) - -sealed class DeviceDetailsSpecRow(associatedType: NavigationBundleSpecType) : NavigationBundleSpecRow(associatedType) { - object UUIDRow : DeviceDetailsSpecRow(NavigationBundleSpecType.StringType) -} - -class BluetoothDeviceDetailViewModel(private val bluetooth: Bluetooth, private val identifier: Identifier) : BaseLifecycleViewModel() { - - companion object { - private const val rssi_frequency = 1000L - } - - private val device = bluetooth.devices()[identifier] - - val name = device.info().map { it.name ?: "bluetooth_no_name".localized() }.toUninitializedObservable(coroutineScope) - val identifierString = identifier.stringValue - val rssi = device.rssi().map { "rssi".localized().format(it) }.toUninitializedObservable(coroutineScope) - val distance = device.distance().map { "distance".localized().format(it) }.toUninitializedObservable(coroutineScope) - val state = device.state().map { deviceState -> - when (deviceState) { - is NotConnectableDeviceState -> "" - is ConnectableDeviceState.Disconnecting -> "bluetooth_disconneting" - is ConnectableDeviceState.Disconnected -> "bluetooth_disconnected" - is ConnectableDeviceState.Connected.Discovering -> "bluetooth_discovering" - is ConnectableDeviceState.Connected -> "bluetooth_connected" - is ConnectableDeviceState.Connecting -> "bluetooth_connecting" - is ConnectableDeviceState.Reconnecting -> "bluetooth_reconnecting" - }.localized() - }.toUninitializedObservable(coroutineScope) - private val _services = MutableStateFlow( - emptyList() - ) - val services = _services.toInitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - - scope.launch { - device.services().map { services -> services.map { BluetoothServiceViewModel(bluetooth, identifier, it.uuid) } }.collect { - cleanServices() - _services.value = it - } - } - - scope.launch { - while (true) { - device.updateRssi() - delay(rssi_frequency) - } - } - } - - override fun onCleared() { - super.onCleared() - cleanServices() - } - - private fun cleanServices() { - _services.value.forEach { it.onCleared() } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt deleted file mode 100644 index f4d98b64a..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bluetooth - -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.toBundle -import com.splendo.kaluga.architecture.observable.UninitializedObservable -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.observable.toUninitializedObservable -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.base.text.format -import com.splendo.kaluga.base.utils.toHexString -import com.splendo.kaluga.bluetooth.Bluetooth -import com.splendo.kaluga.bluetooth.UUID -import com.splendo.kaluga.bluetooth.connect -import com.splendo.kaluga.bluetooth.advertisement -import com.splendo.kaluga.bluetooth.device.BaseAdvertisementData -import com.splendo.kaluga.bluetooth.device.DeviceState -import com.splendo.kaluga.bluetooth.device.ConnectableDeviceState -import com.splendo.kaluga.bluetooth.device.NotConnectableDeviceState -import com.splendo.kaluga.bluetooth.device.Identifier -import com.splendo.kaluga.bluetooth.device.stringValue -import com.splendo.kaluga.bluetooth.disconnect -import com.splendo.kaluga.bluetooth.get -import com.splendo.kaluga.bluetooth.rssi -import com.splendo.kaluga.bluetooth.state -import com.splendo.kaluga.resources.localized -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class BluetoothListDeviceViewModel(private val identifier: Identifier, bluetooth: Bluetooth, navigator: Navigator) : NavigatingViewModel(navigator) { - - enum class ConnectButtonState { - Connect, - Disconnect - } - - private val device = bluetooth.devices()[identifier] - - val name = advertisementObservable { it.name ?: "bluetooth_no_name".localized() } - val identifierString = identifier.stringValue - val rssi = device.rssi().map { "rssi".localized().format(it) }.toUninitializedObservable(coroutineScope) - val isTxPowerVisible = advertisementObservable { it.txPowerLevel != Int.MIN_VALUE } - val txPower = advertisementObservable { if (it.txPowerLevel != Int.MIN_VALUE) "txPower".localized().format(it.txPowerLevel) else "" } - - val isConnectButtonVisible = deviceStateObservable { it !is NotConnectableDeviceState } - val connectButtonState = deviceStateObservable { - when (it) { - is ConnectableDeviceState.Disconnected, is ConnectableDeviceState.Disconnecting -> ConnectButtonState.Connect - else -> ConnectButtonState.Disconnect - } - } - val isMoreButtonVisible = deviceStateObservable { it is ConnectableDeviceState.Connected } - - val status = deviceStateObservable { - when (it) { - is NotConnectableDeviceState -> "" - is ConnectableDeviceState.Disconnecting -> "bluetooth_disconneting" - is ConnectableDeviceState.Disconnected -> "bluetooth_disconnected" - is ConnectableDeviceState.Connected -> "bluetooth_connected" - is ConnectableDeviceState.Connecting -> "bluetooth_connecting" - is ConnectableDeviceState.Reconnecting -> "bluetooth_reconnecting" - }.localized() - } - val serviceUUIDs = advertisementObservable { parseServiceUUIDs(it.serviceUUIDs) } - val serviceData = advertisementObservable { parseServiceData(it.serviceData) } - val manufacturerId = advertisementObservable { "bluetooth_manufacturer_id".localized().format(it.manufacturerId ?: -1) } - val manufacturerData = advertisementObservable { "bluetooth_manufacturer_data".localized().format(it.manufacturerData?.toHexString() ?: "") } - - val _isFoldedOut = MutableStateFlow(false) - val isFoldedOut = _isFoldedOut.toInitializedObservable(coroutineScope) - - private fun deviceStateObservable(mapper: (DeviceState) -> T): UninitializedObservable = device.state().map { mapper(it) }.toUninitializedObservable(coroutineScope) - private fun advertisementObservable(mapper: (BaseAdvertisementData) -> T): UninitializedObservable = device.advertisement().map { mapper(it) }.toUninitializedObservable(coroutineScope) - - fun toggleFoldOut() = coroutineScope.launch { - _isFoldedOut.value = !_isFoldedOut.value - } - - fun onConnectPressed() = coroutineScope.launch { - device.connect() - } - - fun onDisconnectPressed() = coroutineScope.launch { - device.disconnect() - } - - fun onMorePressed() = - navigator.navigate( - BluetoothListNavigation( - DeviceDetailsSpec().toBundle { specRow -> - when (specRow) { - is DeviceDetailsSpecRow.UUIDRow -> specRow.convertValue(identifier.stringValue) - } - } - ) - ) - - private fun parseServiceUUIDs(uuids: List): String { - val uuidString = uuids.fold("") { result, next -> - if (result.isEmpty()) - next.toString() - else - "$result, $next" - } - return "bluetooth_service_uuids".localized().format(uuidString) - } - - private fun parseServiceData(data: Map): String { - val dataString = data.entries.fold("") { result, next -> - val nextString = "${next.key}: ${next.value?.toHexString() ?: ""}" - if (result.isEmpty()) - nextString - else - "$result\n$nextString" - } - return "bluetooth_service_data".localized().format(dataString) - } - - public override fun onCleared() { - super.onCleared() - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt deleted file mode 100644 index 3f87cdc8c..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bluetooth - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundle -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.bluetooth.Bluetooth -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.launch - -class BluetoothListNavigation(bundle: NavigationBundle>) : NavigationAction>(bundle) - -class BluetoothListViewModel(private val bluetooth: Bluetooth, navigator: Navigator) : NavigatingViewModel(navigator) { - - private val _isScanning = MutableStateFlow(false) - val isScanning = _isScanning.toInitializedObservable(coroutineScope) - - val title = bluetooth.isEnabled - .mapLatest { if (it) "Enabled" else "Disabled" } - .toInitializedObservable("Initializing...", coroutineScope) - - private val _devices = MutableStateFlow(emptyList()) - val devices = _devices.toInitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - - scope.launch { bluetooth.isScanning().collect { _isScanning.value = it } } - scope.launch { - bluetooth.devices().map { devices -> devices.map { BluetoothListDeviceViewModel(it.identifier, bluetooth, navigator) } }.collect { devices -> - cleanDevices() - _devices.value = devices.sortedByDescending { it.name.currentOrNull } - } - } - } - - fun onScanPressed() { - if (_isScanning.value) { - bluetooth.stopScanning() - } else { - bluetooth.startScanning() - } - } - - override fun onCleared() { - super.onCleared() - cleanDevices() - } - - private fun cleanDevices() { - _devices.value.forEach { it.onCleared() } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt deleted file mode 100644 index 843592790..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.info - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundle -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.toBundle -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.review.ReviewManager -import kotlinx.coroutines.launch - -class DialogSpec : NavigationBundleSpec(setOf(DialogSpecRow.TitleRow, DialogSpecRow.MessageRow)) - -sealed class DialogSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.StringType) { - object TitleRow : DialogSpecRow() - object MessageRow : DialogSpecRow() -} - -class LinkSpec : NavigationBundleSpec(setOf(LinkSpecRow.LinkRow)) - -sealed class LinkSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.StringType) { - object LinkRow : LinkSpecRow() -} - -class MailSpec : NavigationBundleSpec>(setOf(MailSpecRow.ToRow, MailSpecRow.SubjectRow)) - -sealed class MailSpecRow(associatedType: NavigationBundleSpecType) : NavigationBundleSpecRow(associatedType) { - object ToRow : MailSpecRow>(NavigationBundleSpecType.StringArrayType) - object SubjectRow : MailSpecRow(NavigationBundleSpecType.StringType) -} - -sealed class InfoNavigation>(bundle: NavigationBundle) : NavigationAction(bundle) { - - class Dialog(bundle: NavigationBundle) : InfoNavigation(bundle) - class Link(bundle: NavigationBundle) : InfoNavigation(bundle) - class Mail(bundle: NavigationBundle>) : InfoNavigation>(bundle) -} - -class InfoViewModel( - val reviewManagerBuilder: ReviewManager.Builder, - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - - sealed class Button(val title: String) { - object About : Button("About") - object Website : Button("Kaluga.io") - object GitHub : Button("GitHub") - object Mail : Button("Contact") - object Review : Button("Review") - } - - val reviewManager = reviewManagerBuilder.create() - val buttons = observableOf(listOf(Button.About, Button.Website, Button.GitHub, Button.Review, Button.Mail)) - - fun onButtonPressed(button: Button) { - when (button) { - is Button.About -> InfoNavigation.Dialog( - DialogSpec().toBundle { row -> - when (row) { - is DialogSpecRow.TitleRow -> row.convertValue("About Us") - is DialogSpecRow.MessageRow -> row.convertValue("Kaluga is developed by Splendo Consulting BV") - } - } - ) - is Button.Website -> InfoNavigation.Link( - LinkSpec().toBundle { row -> - when (row) { - is LinkSpecRow.LinkRow -> row.convertValue("https://kaluga.splendo.com") - } - } - ) - is Button.GitHub -> InfoNavigation.Link( - LinkSpec().toBundle { row -> - when (row) { - is LinkSpecRow.LinkRow -> row.convertValue("https://github.com/splendo/kaluga") - } - } - ) - is Button.Mail -> InfoNavigation.Mail( - MailSpec().toBundle { row -> - when (row) { - is MailSpecRow.ToRow -> row.convertValue(listOf("info@splendo.com")) - is MailSpecRow.SubjectRow -> row.convertValue("Question about Kaluga") - } - } - ) - is Button.Review -> { - coroutineScope.launch { - reviewManager.attemptToRequestReview() - } - null - } - }?.let { navigator.navigate(it) } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt deleted file mode 100644 index f11a4c15c..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.link - -import com.splendo.kaluga.alerts.Alert -import com.splendo.kaluga.alerts.AlertPresenter -import com.splendo.kaluga.alerts.buildAlert -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundle -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.toBundle -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.links.LinksBuilder -import com.splendo.kaluga.resources.localized -import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable - -@Serializable -data class Repository( - val name: String, - val type: String -) - -class BrowserSpec : NavigationBundleSpec(setOf(BrowserSpecRow.UrlSpecRow)) - -sealed class BrowserNavigationActions>(bundle: NavigationBundle?) : NavigationAction(bundle) { - class OpenWebView(bundle: NavigationBundle?) : BrowserNavigationActions(bundle) -} - -sealed class BrowserSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.StringType) { - object UrlSpecRow : BrowserSpecRow() -} - -class LinksViewModel( - linkRepoBuilder: LinksBuilder, - val builder: AlertPresenter.Builder, - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - - val browserButtonText = observableOf("browser_button_text".localized()) - val linksInstructions = observableOf("links_instructions".localized()) - - private val linksRepo = linkRepoBuilder.create() - - fun showAlert(title: String, message: String, style: Alert.Action.Style) { - coroutineScope.launch { - val action = Alert.Action("Ok", style) - val alert = builder.buildAlert(this) { - setTitle(title) - setMessage(message) - addActions(action) - } - alert.show() - } - } - - fun openWebPage() { - val result = linksRepo.validateLink("https://kaluga-links.web.app") - if (result != null) { - navigator.navigate( - BrowserNavigationActions.OpenWebView( - BrowserSpec().toBundle { row -> - when (row) { - is BrowserSpecRow.UrlSpecRow -> row.convertValue( - result - ) - } - } - ) - ) - } else { - showAlert( - "Error Alert", - "URL is invalid.", - Alert.Action.Style.NEGATIVE - ) - } - } - - fun handleIncomingLink(url: String) { - val result = linksRepo.handleIncomingLink(url, Repository.serializer()) - if (result != null) { - showAlert( - "Alert", - result.toString(), - Alert.Action.Style.POSITIVE - ) - } else { - showAlert( - "Error Alert", - "Query is invalid or empty.", - Alert.Action.Style.NEGATIVE - ) - } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt deleted file mode 100644 index 936f18a68..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.location - -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.location.Location -import com.splendo.kaluga.location.LocationStateRepoBuilder -import com.splendo.kaluga.location.location -import com.splendo.kaluga.permissions.location.LocationPermission -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class LocationViewModel(permission: LocationPermission, repoBuilder: LocationStateRepoBuilder) : BaseLifecycleViewModel() { - - private val locationStateRepo = repoBuilder.create(permission) - - private val _location = MutableStateFlow("") - val location = _location.toInitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - scope.launch { - locationStateRepo.location().map { location -> - when (location) { - is Location.KnownLocation -> "${location.latitudeDMS} ${location.longitudeDMS}" - is Location.UnknownLocation -> { - val lastKnownLocation = (location as? Location.UnknownLocation.WithLastLocation)?.let { - " Last Known Location: ${location.lastKnownLocation.latitudeDMS} ${location.lastKnownLocation.longitudeDMS}" - } - - "Unknown Location. Reason: ${location.reason.name}${ lastKnownLocation ?: "" }" - } - } - }.collect { - _location.value = it - } - } - } - - public override fun onCleared() { - super.onCleared() - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt deleted file mode 100644 index 4f799c3d7..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.permissions - -import com.splendo.kaluga.architecture.observable.InitializedObservable -import com.splendo.kaluga.architecture.observable.UninitializedObservable -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.observable.toUninitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.permissions.base.Permission -import com.splendo.kaluga.permissions.base.PermissionState -import com.splendo.kaluga.permissions.base.Permissions -import com.splendo.kaluga.resources.localized -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class PermissionViewModel(private val permissions: Permissions, private val permission: Permission) : BaseLifecycleViewModel() { - - val permissionStateMessage: UninitializedObservable = permissions[permission] - .map { permissionState -> - when (permissionState) { - is PermissionState.Allowed -> "permission_allowed".localized() - is PermissionState.Denied.Requestable -> "permission_requestable".localized() - is PermissionState.Denied.Locked -> "permission_denied".localized() - is PermissionState.Initializing, - is PermissionState.Deinitialized, - is PermissionState.Uninitialized -> "permission_unknown".localized() - } - } - .toUninitializedObservable(coroutineScope) - - val showPermissionButton: UninitializedObservable = permissions[permission] - .map { permissionState -> permissionState is PermissionState.Denied.Requestable } - .toUninitializedObservable(coroutineScope) - - private val _requestMessage = MutableSharedFlow(0) - val requestMessage: InitializedObservable = _requestMessage.toInitializedObservable(null, coroutineScope) - - fun requestPermission() { - coroutineScope.launch { - _requestMessage.emit((if (permissions.request(permission)) "permission_request_success" else "permission_request_failed").localized()) - _requestMessage.emit(null) - } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt deleted file mode 100644 index baf3c65b7..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.permissions - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.toBundle -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized -import kotlinx.serialization.Serializable - -@Serializable -enum class PermissionView(val title: String) { - Bluetooth("permissions_bluetooth".localized()), - Calendar("permissions_calendar".localized()), - Camera("permissions_camera".localized()), - Contacts("permissions_contacts".localized()), - Location("permissions_location".localized()), - Microphone("permissions_microphone".localized()), - Notifications("permissions_notifications".localized()), - Storage("permissions_storage".localized()) -} - -object PermissionNavigationBundleSpecRow : NavigationBundleSpecRow(NavigationBundleSpecType.SerializedType(PermissionView.serializer())) -class PermissionNavigationBundleSpec : NavigationBundleSpec(setOf(PermissionNavigationBundleSpecRow)) - -class PermissionsListNavigationAction(permissionView: PermissionView) : NavigationAction(PermissionNavigationBundleSpec().toBundle { spec -> spec.convertValue(permissionView) }) - -class PermissionsListViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val permissions = observableOf( - listOf( - PermissionView.Bluetooth, - PermissionView.Calendar, - PermissionView.Camera, - PermissionView.Contacts, - PermissionView.Location, - PermissionView.Microphone, - PermissionView.Notifications, - PermissionView.Storage - ) - ) - - fun onPermissionPressed(permissionView: PermissionView) { - navigator.navigate(PermissionsListNavigationAction(permissionView)) - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt deleted file mode 100644 index 1dd8689b2..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.system - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class SystemFeatures(val name: String) { - object Network : SystemFeatures("network_feature".localized()) -} - -sealed class SystemNavigationActions( - value: Value, - type: NavigationBundleSpecType -) : SingleValueNavigationAction(value, type) { - object Network : SystemNavigationActions(Unit, NavigationBundleSpecType.UnitType) -} - -class SystemViewModel( - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - - val modules = - observableOf( - listOf( - SystemFeatures.Network - ) - ) - - fun onButtonTapped(systemFeatures: SystemFeatures) { - when (systemFeatures) { - is SystemFeatures.Network -> navigator.navigate(SystemNavigationActions.Network) - } - } -} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt deleted file mode 100644 index f79552975..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.system.network - -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.system.network.Network -import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder -import com.splendo.kaluga.system.network.state.network -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -class NetworkViewModel( - networkStateRepoBuilder: NetworkStateRepoBuilder -) : BaseLifecycleViewModel() { - - private val networkRepo = networkStateRepoBuilder.create() - - private val _networkState: MutableStateFlow = MutableStateFlow(null) - val networkState = _networkState.toInitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - - scope.launch { - networkRepo.network().collect { - when (it) { - is Network.Unknown.WithoutLastNetwork -> - _networkState.value = - "Network's state is Unknown and without the last available connection." - is Network.Unknown.WithLastNetwork -> - _networkState.value = - "Network's state is Unknown and with last known connection as ${it.lastKnownNetwork}." - is Network.Known.Cellular -> - _networkState.value = - "Network's state is Available through Cellular." - is Network.Known.Wifi -> - _networkState.value = - "Network's state is Available through WIFI." - is Network.Known.Absent -> - _networkState.value = - "Network's state is Absent." - } - } - } - } -} diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt similarity index 100% rename from newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt rename to example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt similarity index 100% rename from newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt rename to example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoNavigator.kt diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt similarity index 100% rename from newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt rename to example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/BrowserNavigator.kt diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt similarity index 100% rename from newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt rename to example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt diff --git a/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt similarity index 100% rename from newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt rename to example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt diff --git a/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt similarity index 100% rename from newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt rename to example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt diff --git a/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt similarity index 100% rename from newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt rename to example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt diff --git a/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt similarity index 100% rename from newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt rename to example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt diff --git a/example/web/.firebaserc b/example/web/.firebaserc deleted file mode 100644 index 1584668b8..000000000 --- a/example/web/.firebaserc +++ /dev/null @@ -1,17 +0,0 @@ -{ - "projects": { - "default": "kaluga" - }, - "targets": { - "kaluga": { - "hosting": { - "kaluga-links-example": [ - "kaluga-links" - ], - "kaluga-links": [ - "kaluga" - ] - } - } - } -} \ No newline at end of file diff --git a/example/web/.gitignore b/example/web/.gitignore deleted file mode 100644 index dbb58ffbf..000000000 --- a/example/web/.gitignore +++ /dev/null @@ -1,66 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -firebase-debug.log* -firebase-debug.*.log* - -# Firebase cache -.firebase/ - -# Firebase config - -# Uncomment this if you'd like others to create their own Firebase project. -# For a team working on the same Firebase project(s), it is recommended to leave -# it commented so all members can deploy to the same project(s) in .firebaserc. -# .firebaserc - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env diff --git a/example/web/firebase.json b/example/web/firebase.json deleted file mode 100644 index 6684821a4..000000000 --- a/example/web/firebase.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "hosting": [ - { - "target": "kaluga-links-example", - "public": "public", - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "kaluga-links", - "public": "links", - "headers": [ - { - "source": ".well-known/assetlinks.json", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - }, - { - "source": ".well-known/apple-app-site-association", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - ] - } - ] -} diff --git a/example/web/public/.well-known/apple-app-site-association b/example/web/public/.well-known/apple-app-site-association deleted file mode 100644 index ac3cc7a35..000000000 --- a/example/web/public/.well-known/apple-app-site-association +++ /dev/null @@ -1,11 +0,0 @@ -{ - "applinks": { - "apps": [], - "details": [ - { - "appID": "2732HQPVJP.com.splendo.kaluga.example", - "paths": ["*"] - } - ] - } -} \ No newline at end of file diff --git a/example/web/public/.well-known/assetlinks.json b/example/web/public/.well-known/assetlinks.json deleted file mode 100644 index 394aec1b2..000000000 --- a/example/web/public/.well-known/assetlinks.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "relation": ["delegate_permission/common.handle_all_urls"], - "target": { - "namespace": "android_app", - "package_name": "com.splendo.kaluga.example", - "sha256_cert_fingerprints": [ - "BF:0D:B5:2E:8E:4F:9C:73:94:C7:D8:B9:27:CB:CE:AD:DA:85:45:2D:4A:E5:BF:E0:F7:17:00:18:B9:92:EA:E6" - ] - } - } -] \ No newline at end of file diff --git a/example/web/public/404.html b/example/web/public/404.html deleted file mode 100644 index 829eda8fd..000000000 --- a/example/web/public/404.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Page Not Found - - - - -
-

404

-

Page Not Found

-

The specified file was not found on this website. Please check the URL for mistakes and try again.

-

Why am I seeing this?

-

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

-
- - diff --git a/example/web/public/index.html b/example/web/public/index.html deleted file mode 100644 index 622d7d8ca..000000000 --- a/example/web/public/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - Kaluga Example Web - - - -

Kaluga Links

-

When "Open Kaluga Example" is tapped, the app will be opened using App Links (for android) or Universal Links (for ios) and passing a query with the values we want to deserialize into an object.

-

For the purpose of this demo we will pass to the app the query "name=Kaluga&type=Multiplatform Library." and we will deserialize it using kaluga-links method `handleIncomingLinks` into `Repository` class.

- - @Serializable -
- data class Repository(val name: String, val type: String) -
- -
- - diff --git a/example/web/public/style.css b/example/web/public/style.css deleted file mode 100644 index 9f3a73aa0..000000000 --- a/example/web/public/style.css +++ /dev/null @@ -1,3 +0,0 @@ -#open-app-div { - text-align: center; -} \ No newline at end of file diff --git a/newexample/android/src/main/AndroidManifest.xml b/newexample/android/src/main/AndroidManifest.xml deleted file mode 100644 index 2cd6b95de..000000000 --- a/newexample/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt deleted file mode 100644 index 2145a4a03..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example - -import android.os.Bundle -import com.google.android.material.tabs.TabLayout -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation -import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class ExampleActivity : KalugaViewModelActivity(R.layout.activity_example) { - - override val viewModel: ExampleViewModel by viewModel { - parametersOf( - ActivityNavigator { action -> - when (action) { - ExampleTabNavigation.FeatureList -> NavigationSpec.Fragment( - R.id.example_fragment, - createFragment = { FeaturesListFragment() } - ) - ExampleTabNavigation.Info -> NavigationSpec.Fragment( - R.id.example_fragment, - createFragment = { InfoFragment() } - ) - } - } - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val tabs: TabLayout = findViewById(R.id.tabs) - - viewModel.tabs.observeInitialized { exampleTabs -> - tabs.removeAllTabs() - exampleTabs.forEach { tab -> - tabs.addTab(tabs.newTab().setText(tab.title).setTag(tab)) - } - } - - tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabReselected(tab: TabLayout.Tab?) { - val exampleTab = tab?.tag as? ExampleViewModel.Tab ?: return - viewModel.tab.post(exampleTab) - } - - override fun onTabSelected(tab: TabLayout.Tab?) { - val exampleTab = tab?.tag as? ExampleViewModel.Tab ?: return - viewModel.tab.post(exampleTab) - } - - override fun onTabUnselected(tab: TabLayout.Tab?) { } - }) - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt deleted file mode 100644 index 7622e5ab7..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example - -import android.app.Application -import com.splendo.kaluga.base.ApplicationHolder -import com.splendo.kaluga.example.shared.di.initKoin - -class ExampleApplication : Application() { - - override fun onCreate() { - super.onCreate() - ApplicationHolder.application = this - - initKoin() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt deleted file mode 100644 index 9132c61e1..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment -import com.splendo.kaluga.example.alerts.AlertsActivity -import com.splendo.kaluga.example.architecture.ArchitectureInputActivity -import com.splendo.kaluga.example.beacons.BeaconsActivity -import com.splendo.kaluga.example.bluetooth.BluetoothActivity -import com.splendo.kaluga.example.databinding.ViewListButtonBinding -import com.splendo.kaluga.example.datetimepicker.DateTimePickerActivity -import com.splendo.kaluga.example.keyboard.KeyboardManagerActivity -import com.splendo.kaluga.example.link.LinksActivity -import com.splendo.kaluga.example.loading.LoadingActivity -import com.splendo.kaluga.example.location.LocationActivity -import com.splendo.kaluga.example.permissions.PermissionsDemoListActivity -import com.splendo.kaluga.example.platformspecific.PlatformSpecificActivity -import com.splendo.kaluga.example.resources.ResourcesActivity -import com.splendo.kaluga.example.shared.viewmodel.featureList.Feature -import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel -import com.splendo.kaluga.example.system.SystemActivity -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class FeaturesListFragment : KalugaViewModelFragment(R.layout.fragment_features_list) { - - override val viewModel: FeatureListViewModel by viewModel { - parametersOf( - ActivityNavigator { action -> - when (action) { - FeatureListNavigationAction.Location -> NavigationSpec.Activity() - FeatureListNavigationAction.Permissions -> NavigationSpec.Activity() - FeatureListNavigationAction.Alerts -> NavigationSpec.Activity() - FeatureListNavigationAction.DateTimePicker -> NavigationSpec.Activity() - FeatureListNavigationAction.LoadingIndicator -> NavigationSpec.Activity() - FeatureListNavigationAction.Architecture -> NavigationSpec.Activity() - FeatureListNavigationAction.Keyboard -> NavigationSpec.Activity() - FeatureListNavigationAction.Links -> NavigationSpec.Activity() - FeatureListNavigationAction.System -> NavigationSpec.Activity() - FeatureListNavigationAction.Bluetooth -> NavigationSpec.Activity() - FeatureListNavigationAction.Beacons -> NavigationSpec.Activity() - FeatureListNavigationAction.Resources -> NavigationSpec.Activity() - FeatureListNavigationAction.PlatformSpecific -> NavigationSpec.Activity() - } - } - ) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val adapter = FeaturesAdapter(viewModel).apply { - - view.findViewById(R.id.features_list).adapter = this - } - viewModel.feature.observeInitialized { adapter.features = it } - } -} - -class FeaturesAdapter(private val viewModel: FeatureListViewModel) : RecyclerView.Adapter() { - - class FeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { - val button = binding.button - } - - var features: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeatureViewHolder { - val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return FeatureViewHolder(binding) - } - - override fun getItemCount(): Int = features.size - - override fun onBindViewHolder(holder: FeatureViewHolder, position: Int) { - features.getOrNull(position)?.let { feature -> - holder.button.text = feature.title - holder.button.setOnClickListener { viewModel.onFeaturePressed(feature) } - } ?: run { - holder.button.text = null - holder.button.setOnClickListener(null) - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt deleted file mode 100644 index 7f2d19139..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.appcompat.widget.AppCompatButton -import androidx.fragment.app.DialogFragment -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment -import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpec -import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation -import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf -import java.net.URL - -class InfoFragment : KalugaViewModelFragment(R.layout.fragment_info) { - - override val viewModel: InfoViewModel by viewModel { - parametersOf( - ActivityNavigator> { action -> - when (action) { - is InfoNavigation.Dialog -> { - NavigationSpec.Dialog( - createDialog = { - InfoDialog(action.value) - } - ) - } - is InfoNavigation.Link -> NavigationSpec.Browser( - URL(action.value), - NavigationSpec.Browser.Type.Normal - ) - is InfoNavigation.Mail -> NavigationSpec.Email( - NavigationSpec.Email.EmailSettings( - to = action.value.to, - subject = action.value.subject - ) - ) - } - } - ) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - - super.onViewCreated(view, savedInstanceState) - - val adapter = InfoAdapter(viewModel).apply { - view.findViewById(R.id.info_buttons) - .adapter = this - } - viewModel.buttons.observeInitialized { adapter.buttons = it } - } -} - -class InfoAdapter(private val viewModel: InfoViewModel) : RecyclerView.Adapter() { - - class InfoViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button) - - var buttons: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InfoViewHolder { - val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton - return InfoViewHolder(button) - } - - override fun getItemCount(): Int = buttons.size - - override fun onBindViewHolder(holder: InfoViewHolder, position: Int) { - buttons.getOrNull(position)?.let { button -> - holder.button.text = button.title - holder.button.setOnClickListener { viewModel.onButtonPressed(button) } - } ?: run { - holder.button.text = null - holder.button.setOnClickListener(null) - } - } -} - -class InfoDialog(private val dialogSpec: DialogSpec) : DialogFragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val v = inflater.inflate(R.layout.dialog_info, container, false) - - v.findViewById(R.id.title).text = dialogSpec.title - v.findViewById(R.id.message).text = dialogSpec.message - - return v - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt deleted file mode 100644 index 5fa52e39d..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.alerts - -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel - -@SuppressLint("SetTextI18n") -class AlertsActivity : KalugaViewModelActivity(R.layout.activity_alerts) { - - override val viewModel: AlertViewModel by viewModel() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - findViewById(R.id.btn_simple_alert).setOnClickListener { viewModel.showAlert() } - findViewById(R.id.btn_dismissible_alert).setOnClickListener { viewModel.showAndDismissAfter(3) } - findViewById(R.id.btn_alert_list).setOnClickListener { viewModel.showAlertWithList() } - findViewById(R.id.btn_alert_input_field).setOnClickListener { viewModel.showAlertWithInput() } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt deleted file mode 100644 index 58cffa8c6..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.architecture - -import android.os.Bundle -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.toTypedProperty -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityArchitectureDetailsBinding -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.CloseDetailsNavigation -import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class ArchitectureDetailsActivity : KalugaViewModelActivity() { - - companion object { - val resultCode = 1 - } - - override val viewModel: ArchitectureDetailsViewModel by viewModel { - val type = NavigationBundleSpecType.SerializedType(InputDetails.serializer()) - intent.extras?.toTypedProperty(type)?.let { details -> - parametersOf( - details, - ActivityNavigator { - NavigationSpec.Close(resultCode) - } - ) - } ?: parametersOf("", 0) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityArchitectureDetailsBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) - binding.viewModel = viewModel - binding.lifecycleOwner = this - } - - override fun onBackPressed() { - viewModel.onClosePressed() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt deleted file mode 100644 index 3c131451e..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.architecture - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.activity.result.contract.ActivityResultContract -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.navigation.toTypedProperty -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityArchitectureInputBinding -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails -import com.splendo.kaluga.example.shared.viewmodel.architecture.InputNavigation -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class ArchitectureInputActivity : KalugaViewModelActivity() { - - inner class Contract : ActivityResultContract() { - - override fun createIntent(context: Context, input: Intent): Intent = input - - override fun parseResult( - resultCode: Int, - intent: Intent? - ): InputDetails? = intent?.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(InputDetails.serializer())) - } - - override val viewModel: ArchitectureInputViewModel by viewModel { - parametersOf( - ActivityNavigator { - NavigationSpec.Activity( - launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } - ) - } - ) - } - - private val contract = registerForActivityResult(Contract()) { inputDetails -> - inputDetails?.let { - viewModel.nameInput.post(it.name) - viewModel.numberInput.post(it.number.toString()) - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityArchitectureInputBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) - binding.viewModel = viewModel - binding.lifecycleOwner = this - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt deleted file mode 100644 index b1d59b78b..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.beacons - -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import androidx.core.view.forEach -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.databinding.ActivityBeaconsBinding -import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel -import kotlinx.coroutines.runBlocking -import org.koin.androidx.viewmodel.ext.android.viewModel - -class BeaconsActivity : KalugaViewModelActivity() { - - override val viewModel: BeaconsListViewModel by viewModel() - - private lateinit var adapter: BeaconsAdapter - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityBeaconsBinding.inflate(layoutInflater, null, false) - binding.viewModel = viewModel - binding.lifecycleOwner = this - setContentView(binding.root) - adapter = BeaconsAdapter(this) - binding.beaconsList.adapter = adapter - binding.beaconsList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) - - viewModel.isScanning.observe { - invalidateOptionsMenu() - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.bluetooth_menu, menu) - return true - } - - override fun onPrepareOptionsMenu(menu: Menu?): Boolean = runBlocking { - val scanning = viewModel.isScanning.current - menu?.forEach { item -> - when (item.itemId) { - R.id.start_scanning -> item.isVisible = !scanning - R.id.stop_scanning -> item.isVisible = scanning - } - } - super.onPrepareOptionsMenu(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.start_scanning, - R.id.stop_scanning -> { - viewModel.onScanPressed() - true - } - else -> super.onOptionsItemSelected(item) - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt deleted file mode 100644 index 1c76de087..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.beacons - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.example.databinding.BeaconItemBinding -import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconViewModel - -class BeaconsAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { - - class BeaconItemViewHolder(val binding: BeaconItemBinding) : RecyclerView.ViewHolder(binding.root) - - internal var beacons: List = emptyList() - set(value) { - field = value - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BeaconItemViewHolder { - val inflater = LayoutInflater.from(parent.context) - val binding = BeaconItemBinding.inflate(inflater, parent, false) - binding.lifecycleOwner = lifecycleOwner - return BeaconItemViewHolder(binding) - } - - override fun getItemCount(): Int { - return beacons.size - } - - override fun onBindViewHolder(holder: BeaconItemViewHolder, position: Int) { - val viewModel = beacons[position] - holder.binding.viewModel = viewModel - } - - override fun onViewAttachedToWindow(holder: BeaconItemViewHolder) { - super.onViewAttachedToWindow(holder) - - holder.binding.viewModel?.didResume() - } - - override fun onViewDetachedFromWindow(holder: BeaconItemViewHolder) { - super.onViewDetachedFromWindow(holder) - - holder.binding.viewModel?.didPause() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt deleted file mode 100644 index 74e5ad859..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.beacons - -import androidx.databinding.BindingAdapter -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconViewModel - -object BeaconsBinding { - @BindingAdapter("beacons") - @JvmStatic - fun bindBeacons(view: RecyclerView, beacons: List?) { - val beaconsAdapter = view.adapter as? BeaconsAdapter ?: return - beaconsAdapter.beacons = beacons ?: emptyList() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt deleted file mode 100644 index 35a75ea35..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothActivity.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.bluetooth - -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import androidx.core.view.forEach -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.databinding.ActivityBluetoothBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetails -import kotlinx.coroutines.runBlocking -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class BluetoothActivity : KalugaViewModelActivity() { - - override val viewModel: BluetoothListViewModel by viewModel { - parametersOf( - ActivityNavigator { - NavigationSpec.Activity() - } - ) - } - - private lateinit var adapter: BluetoothAdapter - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityBluetoothBinding.inflate(layoutInflater, null, false) - binding.viewModel = viewModel - binding.lifecycleOwner = this - setContentView(binding.root) - adapter = BluetoothAdapter(this) - binding.devicesList.adapter = adapter - binding.devicesList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) - - viewModel.isScanning.observe { - invalidateOptionsMenu() - } - - viewModel.title.observe(::setTitle) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.bluetooth_menu, menu) - return true - } - - override fun onPrepareOptionsMenu(menu: Menu?): Boolean = runBlocking { - val scanning = viewModel.isScanning.current - menu?.forEach { item -> - when (item.itemId) { - R.id.start_scanning -> item.isVisible = !scanning - R.id.stop_scanning -> item.isVisible = scanning - } - } - super.onPrepareOptionsMenu(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.start_scanning, - R.id.stop_scanning -> { - viewModel.onScanPressed() - true - } - else -> super.onOptionsItemSelected(item) - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt deleted file mode 100644 index 5010367be..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.bluetooth - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.BindingAdapter -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.example.databinding.BluetoothItemBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListDeviceViewModel - -object DevicesBinding { - - @BindingAdapter("devices") - @JvmStatic - fun bindDevices(view: RecyclerView, devices: List?) { - val bluetoothAdapter = view.adapter as? BluetoothAdapter - ?: return - bluetoothAdapter.bluetoothDevices = devices ?: emptyList() - } -} - -class BluetoothAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { - - class BluetoothItemViewHolder(val binding: BluetoothItemBinding) : RecyclerView.ViewHolder(binding.root) - - internal var bluetoothDevices: List = emptyList() - set(value) { - field = value - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BluetoothItemViewHolder { - val inflater = LayoutInflater.from(parent.context) - val binding = BluetoothItemBinding.inflate(inflater, parent, false) - binding.lifecycleOwner = lifecycleOwner - return BluetoothItemViewHolder(binding) - } - - override fun getItemCount(): Int { - return bluetoothDevices.size - } - - override fun onBindViewHolder(holder: BluetoothItemViewHolder, position: Int) { - val viewModel = bluetoothDevices[position] - holder.binding.viewModel = viewModel - } - - override fun onViewAttachedToWindow(holder: BluetoothItemViewHolder) { - super.onViewAttachedToWindow(holder) - - holder.binding.viewModel?.didResume() - } - - override fun onViewDetachedFromWindow(holder: BluetoothItemViewHolder) { - super.onViewDetachedFromWindow(holder) - - holder.binding.viewModel?.didPause() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt deleted file mode 100644 index a0b9e6322..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.bluetooth - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.BindingAdapter -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.example.databinding.BluetoothCharacteristicItemBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothCharacteristicViewModel - -object CharacteristicsBinding { - - @BindingAdapter("characteristics") - @JvmStatic - fun bindCharacteristics(view: RecyclerView, characteristics: List?) { - val characteristicAdapter = view.adapter as? BluetoothCharacteristicAdapter - ?: return - characteristicAdapter.characteristics = characteristics ?: emptyList() - } -} - -class BluetoothCharacteristicAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { - - class BluetoothCharacteristicItemViewHolder(val characteristicItem: BluetoothCharacteristicItemBinding) : RecyclerView.ViewHolder(characteristicItem.root) - - internal var characteristics = emptyList() - set(value) { - field = value - notifyDataSetChanged() - } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): BluetoothCharacteristicItemViewHolder { - val binding = BluetoothCharacteristicItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - binding.lifecycleOwner = lifecycleOwner - binding.descriptors.adapter = BluetoothDescriptorAdapter(lifecycleOwner) - return BluetoothCharacteristicItemViewHolder(binding) - } - - override fun getItemCount(): Int { - return characteristics.size - } - - override fun onBindViewHolder(holder: BluetoothCharacteristicItemViewHolder, position: Int) { - holder.characteristicItem.viewModel = characteristics[position] - } - - override fun onViewAttachedToWindow(holder: BluetoothCharacteristicItemViewHolder) { - super.onViewAttachedToWindow(holder) - - holder.characteristicItem.viewModel?.didResume() - } - - override fun onViewDetachedFromWindow(holder: BluetoothCharacteristicItemViewHolder) { - super.onViewDetachedFromWindow(holder) - - holder.characteristicItem.viewModel?.didPause() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt deleted file mode 100644 index e59ee3b49..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.bluetooth - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.BindingAdapter -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.example.databinding.BluetoothDescriptorItemBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDescriptorViewModel - -object DescriptorsBinding { - - @BindingAdapter("descriptors") - @JvmStatic - fun bindDescriptors(view: RecyclerView, descriptors: List?) { - val descriptorAdapter = view.adapter as? BluetoothDescriptorAdapter - ?: return - descriptorAdapter.descriptors = descriptors ?: emptyList() - } -} - -class BluetoothDescriptorAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { - - class BluetoothDescriptorItemViewHolder(val descriptorItem: BluetoothDescriptorItemBinding) : RecyclerView.ViewHolder(descriptorItem.root) - - var descriptors = emptyList() - set(value) { - field = value - notifyDataSetChanged() - } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): BluetoothDescriptorItemViewHolder { - val binding = BluetoothDescriptorItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - binding.lifecycleOwner = lifecycleOwner - return BluetoothDescriptorItemViewHolder(binding) - } - - override fun getItemCount(): Int { - return descriptors.size - } - - override fun onBindViewHolder(holder: BluetoothDescriptorItemViewHolder, position: Int) { - holder.descriptorItem.viewModel = descriptors[position] - } - - override fun onViewAttachedToWindow(holder: BluetoothDescriptorItemViewHolder) { - super.onViewAttachedToWindow(holder) - - holder.descriptorItem.viewModel?.didResume() - } - - override fun onViewDetachedFromWindow(holder: BluetoothDescriptorItemViewHolder) { - super.onViewDetachedFromWindow(holder) - - holder.descriptorItem.viewModel?.didPause() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt deleted file mode 100644 index 49ce564ef..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothMoreActivity.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.bluetooth - -import android.os.Bundle -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.toTypedProperty -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.bluetooth.device.SerializableIdentifier -import com.splendo.kaluga.example.databinding.ActivityBluetoothMoreBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class BluetoothMoreActivity : KalugaViewModelActivity() { - - override val viewModel: BluetoothDeviceDetailViewModel by viewModel { - parametersOf( - intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(SerializableIdentifier.serializer()))!!.identifier - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityBluetoothMoreBinding.inflate(layoutInflater, null, false) - binding.viewModel = viewModel - binding.lifecycleOwner = this - setContentView(binding.root) - - binding.serviceList.adapter = BluetoothServiceAdapter(this) - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt deleted file mode 100644 index 46711f70e..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.bluetooth - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.BindingAdapter -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.example.databinding.BluetoothServiceItemBinding -import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothServiceViewModel - -object ServicesBinding { - - @BindingAdapter("services") - @JvmStatic - fun bindServices(view: RecyclerView, services: List?) { - val serviceAdapter = view.adapter as? BluetoothServiceAdapter - ?: return - serviceAdapter.services = services ?: emptyList() - } -} - -class BluetoothServiceAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { - - class BluetoothServiceItemViewHolder(val serviceItem: BluetoothServiceItemBinding) : RecyclerView.ViewHolder(serviceItem.root) - - internal var services: List = emptyList() - set(value) { - field = value - notifyDataSetChanged() - } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): BluetoothServiceItemViewHolder { - val binding = BluetoothServiceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - binding.lifecycleOwner = lifecycleOwner - binding.characteristicsList.adapter = BluetoothCharacteristicAdapter(lifecycleOwner) - return BluetoothServiceItemViewHolder(binding) - } - - override fun getItemCount(): Int { - return services.size - } - - override fun onBindViewHolder(holder: BluetoothServiceItemViewHolder, position: Int) { - holder.serviceItem.viewModel = services[position] - } - - override fun onViewAttachedToWindow(holder: BluetoothServiceItemViewHolder) { - super.onViewAttachedToWindow(holder) - - holder.serviceItem.viewModel?.didResume() - } - - override fun onViewDetachedFromWindow(holder: BluetoothServiceItemViewHolder) { - super.onViewDetachedFromWindow(holder) - - holder.serviceItem.viewModel?.didPause() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt deleted file mode 100644 index b766243e4..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.splendo.kaluga.example.datetimepicker - -import android.os.Bundle -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityDateTimePickerBinding -import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel -import org.koin.android.ext.android.inject - -class DateTimePickerActivity : KalugaViewModelActivity() { - - override val viewModel: DateTimePickerViewModel by inject() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityDateTimePickerBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) - binding.viewModel = viewModel - binding.lifecycleOwner = this - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt deleted file mode 100644 index 15ffc09e7..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.keyboard - -import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel -import com.splendo.kaluga.keyboard.ViewFocusHandler -import com.splendo.kaluga.keyboard.keyboardManagerBuilder -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class KeyboardManagerActivity : KalugaViewModelActivity(R.layout.activity_keyboard_manager) { - - override val viewModel: KeyboardViewModel by viewModel { - parametersOf( - keyboardManagerBuilder(), - ViewFocusHandler(R.id.edit_field) - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - findViewById(R.id.btn_show_keyboard).setOnClickListener { viewModel.onShowPressed() } - findViewById(R.id.btn_hide_keyboard).setOnClickListener { viewModel.onHidePressed() } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt deleted file mode 100644 index 846c75ea4..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.link - -import android.content.Intent -import android.os.Bundle -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.databinding.ActivityLinkBinding -import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf -import java.net.URL - -class LinksActivity : KalugaViewModelActivity(R.layout.activity_link) { - - override val viewModel: LinksViewModel by viewModel { - parametersOf( - ActivityNavigator> { - when (it) { - is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser( - URL(it.value), - NavigationSpec.Browser.Type.Normal - ) - } - } - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setupBindings() - - handleAppLinks(intent) - } - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - intent?.let { - handleAppLinks(it) - } - } - - private fun handleAppLinks(intent: Intent) { - intent.let { i -> - i.data?.let { uri -> - val url = URL(uri.scheme, uri.host, uri.path) - viewModel.handleIncomingLink("$url?${uri.encodedQuery}") - } - } - } - - private fun setupBindings() { - val binding = ActivityLinkBinding.inflate(layoutInflater, null, false) - binding.viewModel = viewModel - binding.lifecycleOwner = this - setContentView(binding.root) - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt deleted file mode 100644 index 11468040e..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.loading - -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel - -@SuppressLint("SetTextI18n") -class LoadingActivity : KalugaViewModelActivity(R.layout.activity_loading) { - - override val viewModel: HudViewModel by viewModel() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - findViewById(R.id.btn_show_loading_indicator_system).setOnClickListener { - viewModel.onShowSystemPressed() - } - - findViewById(R.id.btn_show_loading_indicator_custom).setOnClickListener { - viewModel.onShowCustomPressed() - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt deleted file mode 100644 index 0ef45e6d2..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.location - -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import androidx.appcompat.widget.AppCompatTextView -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel -import com.splendo.kaluga.permissions.location.LocationPermission -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class LocationActivity : KalugaViewModelActivity(R.layout.activity_location) { - - companion object { - private val permission = LocationPermission(background = false, precise = true) - } - - override val viewModel: LocationViewModel by viewModel { - parametersOf(permission) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - findViewById(R.id.enable_background).setOnClickListener { - startService(Intent(applicationContext, LocationBackgroundService::class.java)) - } - - findViewById(R.id.disable_background).setOnClickListener { - stopService(Intent(applicationContext, LocationBackgroundService::class.java)) - } - - viewModel.location.observeInitialized { - val info = findViewById(R.id.info) - info.text = it - info.animate().withEndAction { - info.animate().setDuration(10000).alpha(0.12f).start() - }.alpha(1f).setDuration(100).start() - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt deleted file mode 100644 index 27a852cd7..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.location - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.Context -import android.content.Intent -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel -import com.splendo.kaluga.permissions.location.LocationPermission - -class LocationBackgroundService : androidx.lifecycle.LifecycleService() { - - companion object { - const val notificationId = 1 - const val channelId = "location_channel" - const val channelName = "Kaluga Location" - - private val permission = LocationPermission(background = true, precise = true) - } - - private val notificationService by lazy { applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } - - private val viewModel = LocationViewModel(permission) - - override fun onCreate() { - super.onCreate() - - viewModel.location.observeInitialized { message -> - NotificationManagerCompat.from(applicationContext).notify(notificationId, getNotification(message)) - } - - startForeground(notificationId, getNotification("")) - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - super.onStartCommand(intent, flags, startId) - viewModel.didResume() - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - viewModel.didPause() - viewModel.onCleared() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - stopForeground(STOP_FOREGROUND_REMOVE) - } else { - stopForeground(true) - } - NotificationManagerCompat.from(applicationContext).cancel(notificationId) - } - - private fun createChannelIfNeeded() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notificationService.getNotificationChannel( - channelId - ) == null - ) { - val importance = NotificationManager.IMPORTANCE_DEFAULT - val channel = NotificationChannel(channelId, channelName, importance) - channel.setSound(null, null) - channel.enableVibration(false) - channel.setShowBadge(false) - notificationService.createNotificationChannel(channel) - } - } - - private fun getNotification(message: String): Notification { - createChannelIfNeeded() - val builder = NotificationCompat.Builder(applicationContext, channelId) - .setSmallIcon(R.drawable.ic_launcher_foreground) - .setContentTitle(applicationContext.getString(R.string.location_background)) - .setContentText(message) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setSound(null) - .setVibrate(null) - .setBadgeIconType(NotificationCompat.BADGE_ICON_NONE) - val notification = builder.build() - - notification.flags = Notification.FLAG_NO_CLEAR or Notification.FLAG_ONGOING_EVENT - return notification - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt deleted file mode 100644 index aee8b70c2..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright (c) 2020. Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.permissions - -import android.os.Bundle -import android.view.View -import android.widget.TextView -import android.widget.Toast -import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.toTypedProperty -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel -import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission -import com.splendo.kaluga.permissions.calendar.CalendarPermission -import com.splendo.kaluga.permissions.camera.CameraPermission -import com.splendo.kaluga.permissions.contacts.ContactsPermission -import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.microphone.MicrophonePermission -import com.splendo.kaluga.permissions.notifications.NotificationsPermission -import com.splendo.kaluga.permissions.storage.StoragePermission -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class PermissionsDemoActivity : KalugaViewModelActivity(R.layout.activity_permissions_demo) { - - override val viewModel: PermissionViewModel by viewModel { - intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.let { permissionView -> - parametersOf(permissionView.permission) - } ?: parametersOf() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - supportActionBar?.title = intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.title - - viewModel.permissionStateMessage.observe { - findViewById(R.id.permissions_message).text = it - } - - viewModel.showPermissionButton.observe { - findViewById(R.id.btn_permissions_bluetooth_request_permissions).visibility = if (it == true) View.VISIBLE else View.GONE - } - - findViewById(R.id.btn_permissions_bluetooth_request_permissions).setOnClickListener { viewModel.requestPermission() } - - viewModel.requestMessage.observeInitialized { message -> - if (message != null) Toast.makeText(this, message, Toast.LENGTH_SHORT).show() - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt deleted file mode 100644 index 48fdd892b..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.permissions - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.appcompat.widget.AppCompatButton -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class PermissionsDemoListActivity : KalugaViewModelActivity(R.layout.activity_permissions_list) { - - override val viewModel: PermissionsListViewModel by viewModel { - parametersOf( - ActivityNavigator { - NavigationSpec.Activity() - } - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val adapter = PermissionsAdapter(viewModel).apply { - findViewById(R.id.permissions_list).adapter = this - } - viewModel.permissions.observeInitialized { adapter.permissions = it } - } -} - -class PermissionsAdapter(private val viewModel: PermissionsListViewModel) : RecyclerView.Adapter() { - - class PermissionsViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button) - - var permissions: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionsViewHolder { - val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton - return PermissionsViewHolder(button) - } - - override fun getItemCount(): Int = permissions.size - - override fun onBindViewHolder(holder: PermissionsViewHolder, position: Int) { - permissions.getOrNull(position)?.let { permission -> - holder.button.text = permission.title - holder.button.setOnClickListener { viewModel.onPermissionPressed(permission) } - } ?: run { - holder.button.text = null - holder.button.setOnClickListener(null) - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt deleted file mode 100644 index e47f80400..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.state -import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.example.bottomSheet.BottomSheetActivity -import com.splendo.kaluga.example.contacts.ContactsActivity -import com.splendo.kaluga.example.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformFeatureListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformSpecificFeaturesViewModel - -class PlatformSpecificActivity : KalugaViewModelComposeActivity() { - override val viewModel = PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper)) - - @Composable - override fun Layout(viewModel: PlatformSpecificFeaturesViewModel) { - PlatformSpecificFeaturesLayout(viewModel) - } -} - -private fun navigationMapper(action: PlatformFeatureListNavigationAction): NavigationSpec = - when (action) { - is PlatformFeatureListNavigationAction.ComposeNavigation -> NavigationSpec.Activity( - ContactsActivity::class.java) - is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity( - BottomSheetActivity::class.java) - } - -@Composable -private fun PlatformSpecificFeaturesLayout(viewModel: PlatformSpecificFeaturesViewModel) { - MdcTheme { - ViewModelComposable(viewModel) { - val features by feature.state() - LazyColumn { - items(features) { item -> - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Padding.default), - onClick = { onFeaturePressed(item) }, - ) { - Text( - text = item.title.uppercase(), - style = MaterialTheme.typography.button, - ) - } - } - } - } - } -} - -@Composable -@Preview -private fun PlatformSpecificFeaturesLayoutPreview() { - PlatformSpecificFeaturesLayout( - PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper)) - ) -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt deleted file mode 100644 index 9a38060f6..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2022 Splendo Consulting B.V. The Netherlands - * - * 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 com.splendo.kaluga.example.resources - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityResourcesBinding -import com.splendo.kaluga.example.databinding.ViewListButtonBinding -import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel -import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration -import com.splendo.kaluga.resources.dpToPixel -import com.splendo.kaluga.resources.view.KalugaButton -import com.splendo.kaluga.resources.view.bindButton -import org.koin.androidx.viewmodel.ext.android.viewModel - -class ButtonActivity : KalugaViewModelActivity() { - - override val viewModel: ButtonViewModel by viewModel() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) - - val adapter = ButtonAdapter().apply { - - binding.resourcesList.adapter = this - } - viewModel.buttons.observeInitialized { adapter.buttons = it } - binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) - } -} - -class ButtonAdapter : RecyclerView.Adapter() { - - class ButtonViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { - val button = binding.button - } - - var buttons: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ButtonViewHolder { - val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ButtonViewHolder(binding) - } - - override fun getItemCount(): Int = buttons.size - - override fun onBindViewHolder(holder: ButtonViewHolder, position: Int) { - buttons.getOrNull(position)?.let { button -> - holder.button.bindButton(button) - } ?: run { - holder.button.text = null - holder.button.background = null - holder.button.setOnClickListener(null) - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt deleted file mode 100644 index 105ffca09..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2022 Splendo Consulting B.V. The Netherlands - * - * 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 com.splendo.kaluga.example.resources - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.widget.Button -import android.widget.TextView -import androidx.databinding.BindingAdapter -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityResourcesColorBinding -import com.splendo.kaluga.example.databinding.ViewResourceListBackgroundBinding -import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel -import com.splendo.kaluga.resources.DefaultColors -import com.splendo.kaluga.resources.stylable.BackgroundStyle -import com.splendo.kaluga.resources.view.KalugaButton -import com.splendo.kaluga.resources.view.KalugaLabel -import com.splendo.kaluga.resources.view.applyBackgroundStyle -import com.splendo.kaluga.resources.view.bindButton -import com.splendo.kaluga.resources.view.bindLabel -import org.koin.androidx.viewmodel.ext.android.viewModel - -class ColorActivity : KalugaViewModelActivity() { - - override val viewModel: ColorViewModel by viewModel() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityResourcesColorBinding.inflate(layoutInflater, null, false) - binding.viewModel = viewModel - binding.lifecycleOwner = this - setContentView(binding.root) - - binding.backdropEdit.setOnEditorActionListener { v, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - viewModel.submitBackdropText(v.text.toString()) - true - } else { - false - } - } - - binding.sourceEdit.setOnEditorActionListener { v, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - viewModel.submitSourceText(v.text.toString()) - true - } else { - false - } - } - - binding.backdropLighten.adapter = BackgroundAdapter() - binding.backdropDarken.adapter = BackgroundAdapter() - binding.blendedLighten.adapter = BackgroundAdapter() - binding.blendedDarken.adapter = BackgroundAdapter() - binding.sourceLighten.adapter = BackgroundAdapter() - binding.sourceDarken.adapter = BackgroundAdapter() - } -} - -class BackgroundAdapter : RecyclerView.Adapter() { - - class BackgroundViewHolder(val binding: ViewResourceListBackgroundBinding) : RecyclerView.ViewHolder(binding.root) { - val view = binding.root - } - - var backgrounds: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackgroundViewHolder { - val binding = ViewResourceListBackgroundBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return BackgroundViewHolder(binding) - } - - override fun getItemCount(): Int = backgrounds.size - - override fun onBindViewHolder(holder: BackgroundViewHolder, position: Int) { - backgrounds.getOrNull(position)?.let { background -> - holder.view.applyBackgroundStyle(background) - } ?: run { - holder.view.setBackgroundColor(DefaultColors.clear) - } - } -} - -object ResourcesBinding { - - @BindingAdapter("button") - @JvmStatic - fun bindButton(view: Button, button: KalugaButton?) { - button?.let { - view.bindButton(it) - } - } - - @BindingAdapter("label") - @JvmStatic - fun bindLabel(view: TextView, label: KalugaLabel?) { - label?.let { - view.bindLabel(it) - } - } - - @BindingAdapter("backgroundStyle") - @JvmStatic - fun bindBackgroundStyle(view: View, backgroundStyle: BackgroundStyle?) { - backgroundStyle?.let { - view.applyBackgroundStyle(it) - } - } - - @BindingAdapter("backgrounds") - @JvmStatic - fun bindBackgrounds(recyclerView: RecyclerView, backgrounds: List?) { - val adapter = recyclerView.adapter as? BackgroundAdapter ?: return - adapter.backgrounds = backgrounds ?: emptyList() - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt deleted file mode 100644 index 9addfb939..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2022 Splendo Consulting B.V. The Netherlands - * - * 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 com.splendo.kaluga.example.resources - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityResourcesBinding -import com.splendo.kaluga.example.databinding.ViewListTextViewBinding -import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel -import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration -import com.splendo.kaluga.resources.dpToPixel -import com.splendo.kaluga.resources.view.KalugaLabel -import com.splendo.kaluga.resources.view.bindLabel -import org.koin.androidx.viewmodel.ext.android.viewModel - -class LabelActivity : KalugaViewModelActivity() { - - override val viewModel: LabelViewModel by viewModel() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) - - val adapter = LabelAdapter().apply { - - binding.resourcesList.adapter = this - } - binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) - viewModel.labels.observeInitialized { adapter.labels = it } - } -} - -class LabelAdapter : RecyclerView.Adapter() { - - class LabelViewHolder(val binding: ViewListTextViewBinding) : RecyclerView.ViewHolder(binding.root) { - val label = binding.label - } - - var labels: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LabelViewHolder { - val binding = ViewListTextViewBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return LabelViewHolder(binding) - } - - override fun getItemCount(): Int = labels.size - - override fun onBindViewHolder(holder: LabelViewHolder, position: Int) { - labels.getOrNull(position)?.let { label -> - holder.label.bindLabel(label) - } ?: run { - holder.label.text = null - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt deleted file mode 100644 index a587071fb..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.splendo.kaluga.example.resources - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityResourcesBinding -import com.splendo.kaluga.example.databinding.ViewListButtonBinding -import com.splendo.kaluga.example.shared.viewmodel.resources.Resource -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class ResourcesActivity : KalugaViewModelActivity() { - override val viewModel: ResourcesListViewModel by viewModel { - parametersOf( - ActivityNavigator { action -> - when (action) { - is ResourcesListNavigationAction.Label -> NavigationSpec.Activity() - is ResourcesListNavigationAction.Color -> NavigationSpec.Activity() - is ResourcesListNavigationAction.Button -> NavigationSpec.Activity() - } - } - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) - - val adapter = ResourcesAdapter(viewModel).apply { - - binding.resourcesList.adapter = this - } - viewModel.resources.observeInitialized { adapter.resources = it } - } -} - -class ResourcesAdapter(private val viewModel: ResourcesListViewModel) : - RecyclerView.Adapter() { - - class ResourceViewHolder(val binding: ViewListButtonBinding) : - RecyclerView.ViewHolder(binding.root) { - val button = binding.button - } - - var resources: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResourceViewHolder { - val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ResourceViewHolder(binding) - } - - override fun getItemCount(): Int = resources.size - - override fun onBindViewHolder(holder: ResourceViewHolder, position: Int) { - resources.getOrNull(position)?.let { resource -> - holder.button.text = resource.title - holder.button.setOnClickListener { viewModel.onResourceSelected(resource) } - } ?: run { - holder.button.text = null - holder.button.setOnClickListener(null) - } - } -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt deleted file mode 100644 index 4a1a63290..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.system - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.databinding.ViewListButtonBinding -import com.splendo.kaluga.example.shared.viewmodel.system.SystemFeatures -import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions -import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class SystemActivity : KalugaViewModelActivity(R.layout.activity_system) { - override val viewModel: SystemViewModel by viewModel { - parametersOf( - ActivityNavigator { action -> - when (action) { - SystemNavigationActions.Network -> NavigationSpec.Activity() - } - } - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val adapter = SystemFeatureAdapter(viewModel).apply { - findViewById(R.id.system_features_list).adapter = this - } - - viewModel.modules.observeInitialized { - adapter.modules = it - } - } -} - -class SystemFeatureAdapter( - private val viewModel: SystemViewModel -) : RecyclerView.Adapter() { - - inner class SystemFeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { - val button = binding.button - } - - var modules: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SystemFeatureViewHolder { - val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return SystemFeatureViewHolder(binding) - } - - override fun onBindViewHolder(holder: SystemFeatureViewHolder, position: Int) { - modules.getOrNull(position)?.let { feature -> - holder.button.text = feature.name - holder.button.setOnClickListener { viewModel.onButtonTapped(feature) } - } ?: run { - holder.button.text = null - holder.button.setOnClickListener(null) - } - } - - override fun getItemCount(): Int = modules.size -} diff --git a/newexample/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt b/newexample/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt deleted file mode 100644 index b7398e774..000000000 --- a/newexample/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 Splendo Consulting B.V. The Netherlands - * - * 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 com.splendo.kaluga.example.view - -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -class VerticalSpaceItemDecoration(private val verticalSpaceHeight: Int) : RecyclerView.ItemDecoration() { - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - outRect.bottom = verticalSpaceHeight - } -} - -class HorizontalSpaceItemDecoration(private val horizontalSpaceWidth: Int) : RecyclerView.ItemDecoration() { - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - outRect.right = horizontalSpaceWidth - } -} diff --git a/newexample/android/src/main/res/drawable/ic_account.xml b/newexample/android/src/main/res/drawable/ic_account.xml deleted file mode 100644 index 0e510741e..000000000 --- a/newexample/android/src/main/res/drawable/ic_account.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/newexample/android/src/main/res/drawable/ic_launcher_foreground.xml b/newexample/android/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index fffaf19df..000000000 --- a/newexample/android/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - diff --git a/newexample/android/src/main/res/drawable/ic_refresh_circle.xml b/newexample/android/src/main/res/drawable/ic_refresh_circle.xml deleted file mode 100644 index 43bf87fcd..000000000 --- a/newexample/android/src/main/res/drawable/ic_refresh_circle.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/drawable/ic_stop_circle.xml b/newexample/android/src/main/res/drawable/ic_stop_circle.xml deleted file mode 100644 index a0770a6eb..000000000 --- a/newexample/android/src/main/res/drawable/ic_stop_circle.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_alerts.xml b/newexample/android/src/main/res/layout/activity_alerts.xml deleted file mode 100644 index 74ea5dea3..000000000 --- a/newexample/android/src/main/res/layout/activity_alerts.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/newexample/android/src/main/res/layout/activity_architecture_details.xml b/newexample/android/src/main/res/layout/activity_architecture_details.xml deleted file mode 100644 index 926bd42b8..000000000 --- a/newexample/android/src/main/res/layout/activity_architecture_details.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_architecture_input.xml b/newexample/android/src/main/res/layout/activity_architecture_input.xml deleted file mode 100644 index 95c28e0ac..000000000 --- a/newexample/android/src/main/res/layout/activity_architecture_input.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_beacons.xml b/newexample/android/src/main/res/layout/activity_beacons.xml deleted file mode 100644 index 30abccc0a..000000000 --- a/newexample/android/src/main/res/layout/activity_beacons.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/newexample/android/src/main/res/layout/activity_bluetooth.xml b/newexample/android/src/main/res/layout/activity_bluetooth.xml deleted file mode 100644 index 59d229ef1..000000000 --- a/newexample/android/src/main/res/layout/activity_bluetooth.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_bluetooth_more.xml b/newexample/android/src/main/res/layout/activity_bluetooth_more.xml deleted file mode 100644 index 9a917b91e..000000000 --- a/newexample/android/src/main/res/layout/activity_bluetooth_more.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_date_time_picker.xml b/newexample/android/src/main/res/layout/activity_date_time_picker.xml deleted file mode 100644 index f92a40694..000000000 --- a/newexample/android/src/main/res/layout/activity_date_time_picker.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_example.xml b/newexample/android/src/main/res/layout/activity_example.xml deleted file mode 100644 index fecd0859d..000000000 --- a/newexample/android/src/main/res/layout/activity_example.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/newexample/android/src/main/res/layout/activity_keyboard_manager.xml b/newexample/android/src/main/res/layout/activity_keyboard_manager.xml deleted file mode 100644 index 364a8b6c4..000000000 --- a/newexample/android/src/main/res/layout/activity_keyboard_manager.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - diff --git a/newexample/android/src/main/res/layout/activity_link.xml b/newexample/android/src/main/res/layout/activity_link.xml deleted file mode 100644 index 99c88fc0b..000000000 --- a/newexample/android/src/main/res/layout/activity_link.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/newexample/ios/Demo/Beacons/BeaconsViewCell.swift b/newexample/ios/Demo/Beacons/BeaconsViewCell.swift deleted file mode 100644 index 928f6e859..000000000 --- a/newexample/ios/Demo/Beacons/BeaconsViewCell.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright 2022 Splendo Consulting B.V. The Netherlands -// -// 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. -// - -import UIKit -import KalugaExampleShared - -class BeaconsViewCell: UICollectionViewCell { - - static var identifier: String { String(describing: Self.self) } - - @IBOutlet private var namespaceLabel: UILabel! - @IBOutlet private var instanceLabel: UILabel! - @IBOutlet private var txPowerLabel: UILabel! - - private let disposeBag = DisposeBag() - private var viewModel: BeaconsListBeaconViewModel? - - func configure(with viewModel: BeaconsListBeaconViewModel) { - self.viewModel = viewModel - } - - func startMonitoring() { - viewModel?.namespace_.observe { [weak self] namespace in - self?.namespaceLabel.text = namespace as String? - }.addTo(disposeBag: disposeBag) - - viewModel?.instance.observe { [weak self] instance in - self?.instanceLabel.text = instance as String? - }.addTo(disposeBag: disposeBag) - - viewModel?.txPower.observe { [weak self] txPower in - self?.txPowerLabel.text = txPower as String? - }.addTo(disposeBag: disposeBag) - } - - func stopMonitoring() { - disposeBag.dispose() - } -} diff --git a/newexample/ios/Demo/Beacons/BeaconsViewController.swift b/newexample/ios/Demo/Beacons/BeaconsViewController.swift deleted file mode 100644 index 653f7c54a..000000000 --- a/newexample/ios/Demo/Beacons/BeaconsViewController.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// Copyright 2022 Splendo Consulting B.V. The Netherlands -// -// 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. -// - -import UIKit -import KalugaExampleShared - -class BeaconsViewController: UICollectionViewController { - - private var beacons = [BeaconsListBeaconViewModel]() { - didSet { - collectionView.reloadData() - } - } - - private var lifecycleManager: LifecycleManager! - - private lazy var flowLayout: UICollectionViewFlowLayout = { - let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() - flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) - flowLayout.minimumLineSpacing = 4 - return flowLayout - }() - - private lazy var viewModel = BeaconsListViewModel() - - deinit { - lifecycleManager.unbind() - } - - override func awakeFromNib() { - super.awakeFromNib() - - collectionView.collectionViewLayout = flowLayout - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - - return [ - viewModel.isScanning.observe { isScanning in - self?.updateNavigationItem(isScanning: isScanning as? Bool ?? false) - }, - viewModel.beacons.observe { devices in - self?.beacons = devices as? [BeaconsListBeaconViewModel] ?? [] - } - ] - } - } - - override func numberOfSections(in collectionView: UICollectionView) -> Int { - return 1 - } - - override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - beacons.count - } - - override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let beaconCell = collectionView.dequeueReusableCell(withReuseIdentifier: BeaconsViewCell.identifier, for: indexPath) as! BeaconsViewCell - beaconCell.configure(with: beacons[indexPath.row]) - return beaconCell - } - - override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - guard let cell = cell as? BeaconsViewCell else { - return - } - - cell.startMonitoring() - } - - override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - guard let cell = cell as? BeaconsViewCell else { - return - } - - cell.stopMonitoring() - } - - override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - collectionView.deselectItem(at: indexPath, animated: true) - } - - private func updateNavigationItem(isScanning: Bool) { - let title = isScanning ? "beacons_stop_monitoring" : "beacons_start_monitoring" - let item = UIBarButtonItem( - title: NSLocalizedString(title, comment: ""), - style: .plain, - target: viewModel, - action: #selector(viewModel.onScanPressed) - ) - navigationItem.setRightBarButton(item, animated: true) - } -} diff --git a/newexample/ios/Demo/Bluetooth/BluetoothCharacteristicCell.xib b/newexample/ios/Demo/Bluetooth/BluetoothCharacteristicCell.xib deleted file mode 100644 index 4708c258e..000000000 --- a/newexample/ios/Demo/Bluetooth/BluetoothCharacteristicCell.xib +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/newexample/ios/Demo/Bluetooth/BluetoothDescriptorCell.xib b/newexample/ios/Demo/Bluetooth/BluetoothDescriptorCell.xib deleted file mode 100644 index 13eae4024..000000000 --- a/newexample/ios/Demo/Bluetooth/BluetoothDescriptorCell.xib +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift b/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift deleted file mode 100644 index cb593bf85..000000000 --- a/newexample/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift +++ /dev/null @@ -1,363 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class BluetoothDeviceDetailsViewController : UIViewController { - - struct Const { - static let storyboard = UIStoryboard(name: "Main", bundle: nil) - static let storyboardId = "BluetoothDeviceDetails" - } - - static func create(identifier: UUID) -> BluetoothDeviceDetailsViewController { - let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! BluetoothDeviceDetailsViewController - if #available(iOS 13.0, *) { - vc.isModalInPresentation = true - } - vc.viewModel = BluetoothDeviceDetailViewModel(identifier: identifier) - return vc - } - - var viewModel: BluetoothDeviceDetailViewModel! - private var lifecycleManager: LifecycleManager! - - @IBOutlet var deviceName: UILabel! - @IBOutlet var deviceIdentifier: UILabel! - @IBOutlet var rssi: UILabel! - @IBOutlet var distance: UILabel! - @IBOutlet var connectionStatus: UILabel! - @IBOutlet var servicesHeader: UILabel! - @IBOutlet var servicesList: UICollectionView! - - private var services: [BluetoothServiceViewModel] = [] - - private var isInvalidating: Bool = false - - deinit { - lifecycleManager.unbind() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - servicesHeader.text = NSLocalizedString("bluetooth_services_header", comment: "") - deviceIdentifier.text = viewModel.identifierString - - let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() - flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) - flowLayout.minimumLineSpacing = 4 - servicesList.collectionViewLayout = flowLayout - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - - guard let viewModel = self?.viewModel else { return []} - - return [ - viewModel.name.observe { name in - self?.deviceName.text = name as String? - } - , - viewModel.rssi.observe { rssiValue in - self?.rssi.text = rssiValue as String? - } - , - viewModel.distance.observe { distanceValue in - self?.distance.text = distanceValue as String? - } - , - viewModel.state.observe { state in - self?.connectionStatus.text = state as String? - } - , - viewModel.services.observe { servicesList in - self?.services = servicesList as? [BluetoothServiceViewModel] ?? [] - self?.servicesList.reloadData() - self?.servicesList.layoutIfNeeded() - } - ] - } - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return services.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let serviceCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothServiceView.Companion.identifier, for: indexPath) as! BluetoothServiceView - serviceCell.parent = self - serviceCell.service = services[indexPath.row] - return serviceCell - } - - func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { - (cell as? BluetoothServiceView)?.startMonitoring() - } - } - - func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { - (cell as? BluetoothServiceView)?.stopMonitoring() - } - } - - fileprivate func updateListSize() { - isInvalidating = true - servicesList.collectionViewLayout.invalidateLayout() - servicesList.layoutIfNeeded() - isInvalidating = false - } - -} - -class BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource { - - fileprivate struct Companion { - static let identifier = "BluetoothServiceView" - } - - fileprivate weak var parent: BluetoothDeviceDetailsViewController? - fileprivate var service: BluetoothServiceViewModel? - private let disposeBag = DisposeBag() - - private var isInvalidating: Bool = false - - @IBOutlet var serviceHeader: UILabel! - @IBOutlet var serviceIdentifier: UILabel! - @IBOutlet var characteristicsHeader: UILabel! - @IBOutlet var characteristicsList: UICollectionView! - @IBOutlet var characteristicsListHeight: NSLayoutConstraint! - - private var characteristics: [BluetoothCharacteristicViewModel] = [] - - override func awakeFromNib() { - super.awakeFromNib() - - serviceHeader.text = NSLocalizedString("bluetooth_service", comment: "") - characteristicsHeader.text = NSLocalizedString("bluetooth_characteristics", comment: "") - - let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() - flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) - flowLayout.minimumLineSpacing = 4 - characteristicsList.collectionViewLayout = flowLayout - characteristicsList.dataSource = self - characteristicsList.delegate = self - characteristicsList.register(UINib(nibName: "BluetoothCharacteristicCell", bundle: nil), forCellWithReuseIdentifier: BluetoothCharacteristicView.Companion.identifier) - } - - fileprivate func startMonitoring() { - disposeBag.dispose() - guard let service = self.service else { - return - } - service.didResume() - - serviceIdentifier.text = service.uuid - service.characteristics.observe { [weak self] characteristics in - self?.characteristics = characteristics as? [BluetoothCharacteristicViewModel] ?? [] - self?.characteristicsList.reloadData() - self?.updateListSize(isInvalidating: false) - }.addTo(disposeBag: disposeBag) - } - - fileprivate func stopMonitoring() { - service?.didPause() - disposeBag.dispose() - } - - fileprivate func updateListSize(isInvalidating: Bool) { - self.isInvalidating = isInvalidating - characteristicsList.collectionViewLayout.invalidateLayout() - characteristicsList.layoutIfNeeded() - let height = characteristicsList.contentSize.height - characteristicsListHeight.constant = height - parent?.updateListSize() - self.isInvalidating = false - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return characteristics.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let characteristicCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCharacteristicView.Companion.identifier, for: indexPath) as! BluetoothCharacteristicView - characteristicCell.parent = self - characteristicCell.characteristic = characteristics[indexPath.row] - return characteristicCell - } - - func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { - (cell as? BluetoothCharacteristicView)?.startMonitoring() - } - } - - func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { - (cell as? BluetoothCharacteristicView)?.stopMonitoring() - } - } - - override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { - let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) - layoutIfNeeded() - layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) - return layoutAttributes - } - -} - -class BluetoothCharacteristicView : UICollectionViewCell { - - fileprivate struct Companion { - static let identifier = "BluetoothCharacteristicView" - } - - fileprivate weak var parent: BluetoothServiceView? - fileprivate var characteristic: BluetoothCharacteristicViewModel? - private let disposeBag = DisposeBag() - - private var isInvalidating: Bool = false - - @IBOutlet var characteristicIdentifier: UILabel! - @IBOutlet var characteristicValue: UILabel! - @IBOutlet var descriptorsHeader: UILabel! - @IBOutlet var descriptorsList: UICollectionView! - - @IBOutlet var descriptorsListHeight: NSLayoutConstraint! - - private var descriptors: [BluetoothDescriptorViewModel] = [] - - override func awakeFromNib() { - super.awakeFromNib() - - descriptorsHeader.text = NSLocalizedString("bluetooth_descriptors", comment: "") - - let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() - flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8) - flowLayout.minimumLineSpacing = 4 - descriptorsList.collectionViewLayout = flowLayout - descriptorsList.register(UINib(nibName: "BluetoothDescriptorCell", bundle: nil), forCellWithReuseIdentifier: BluetoothDescriptorView.Companion.identifier) - } - - fileprivate func startMonitoring() { - disposeBag.dispose() - guard let characteristic = self.characteristic else { - return - } - characteristic.didResume() - - characteristicIdentifier.text = characteristic.uuid - characteristic.descriptors.observe { [weak self] descriptors in - self?.descriptors = descriptors as? [BluetoothDescriptorViewModel] ?? [] - self?.descriptorsList.reloadData() - self?.updateListSize(isInvalidating: false) - }.addTo(disposeBag: disposeBag) - - characteristic.value.observe { [weak self] value in - self?.characteristicValue.text = value as String? - }.addTo(disposeBag: disposeBag) - } - - fileprivate func stopMonitoring() { - disposeBag.dispose() - characteristic?.didPause() - } - - private func updateListSize(isInvalidating: Bool) { - self.isInvalidating = isInvalidating - descriptorsList.collectionViewLayout.invalidateLayout() - descriptorsList.layoutIfNeeded() - let height = descriptorsList.contentSize.height - descriptorsListHeight.constant = height - parent?.updateListSize(isInvalidating: true) - self.isInvalidating = false - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return descriptors.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let descriptorCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothDescriptorView.Companion.identifier, for: indexPath) as! BluetoothDescriptorView - descriptorCell.descriptor = descriptors[indexPath.row] - return descriptorCell - } - - func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { - (cell as? BluetoothDescriptorView)?.startMonitoring() - } - } - - func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { - (cell as? BluetoothDescriptorView)?.stopMonitoring() - } - } - - override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { - let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) - layoutIfNeeded() - layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) - return layoutAttributes - } -} - -class BluetoothDescriptorView : UICollectionViewCell { - - fileprivate struct Companion { - static let identifier = "BluetoothDescriptorView" - } - - fileprivate var descriptor: BluetoothDescriptorViewModel? - private let disposeBag = DisposeBag() - - @IBOutlet var descriptorIdentifier: UILabel! - @IBOutlet var descriptorValue: UILabel! - - fileprivate func startMonitoring() { - disposeBag.dispose() - guard let descriptor = self.descriptor else { - return - } - descriptor.didResume() - - descriptorIdentifier.text = descriptor.uuid - - descriptor.value.observe { [weak self] value in - self?.descriptorValue.text = value as String? - }.addTo(disposeBag: disposeBag) - } - - fileprivate func stopMonitoring() { - disposeBag.dispose() - descriptor?.didResume() - } - - override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { - let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) - layoutIfNeeded() - layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) - return layoutAttributes - } -} diff --git a/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift b/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift deleted file mode 100644 index 82903a4b1..000000000 --- a/newexample/ios/Demo/Bluetooth/BluetoothViewController.swift +++ /dev/null @@ -1,233 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class BluetoothViewController : UICollectionViewController { - - lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - NavigationSpec.Push(animated: true) { - BluetoothDeviceDetailsViewController.create(identifier: action.value!.identifier) - } - } - lazy var viewModel = BluetoothListViewModel(navigator: navigator) - - private var devices: [BluetoothListDeviceViewModel] = [] - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func awakeFromNib() { - super.awakeFromNib() - - let flowLayout = FittingWidthAutomaticHeightCollectionViewFlowLayout() - flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0) - flowLayout.minimumLineSpacing = 4 - collectionView.collectionViewLayout = flowLayout - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - - return [ - viewModel.isScanning.observe { isScanning in - self?.updateNavigationItem(isScanning: isScanning as? Bool ?? false) - }, - viewModel.devices.observe { devices in - self?.devices = devices as? [BluetoothListDeviceViewModel] ?? [] - self?.collectionView?.reloadData() - self?.collectionView.layoutIfNeeded() - }, - viewModel.title.observe { title in - self?.updateTitle(title: title as String?) - } - ] - } - } - - private func updateNavigationItem(isScanning: Bool) { - if (isScanning) { - self.navigationItem.setRightBarButton(UIBarButtonItem(title: NSLocalizedString("bluetooth_stop_scanning", comment: ""), style: .plain, target: self, action: #selector(self.toggleScanning)), animated: true) - } else { - self.navigationItem.setRightBarButton(UIBarButtonItem(title: NSLocalizedString("bluetooth_start_scanning", comment: ""), style: .plain, target: self, action: #selector(self.toggleScanning)), animated: true) - } - } - - private func updateTitle(title: String?) { - self.title = title - } - - @objc private func toggleScanning() { - viewModel.onScanPressed() - } - - override func numberOfSections(in collectionView: UICollectionView) -> Int { - return 1 - } - - override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - devices.count - } - - override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let bluetoothCell = collectionView.dequeueReusableCell(withReuseIdentifier: BluetoothCell.Companion.identifier, for: indexPath) as! BluetoothCell - bluetoothCell.device = devices[indexPath.row] - return bluetoothCell - } - - override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - guard let btCell = cell as? BluetoothCell else { - return - } - - btCell.startMonitoring() - } - - override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - guard let btCell = cell as? BluetoothCell else { - return - } - - btCell.stopMonitoring() - } - - override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - devices[indexPath.row].toggleFoldOut() - let context = UICollectionViewFlowLayoutInvalidationContext() - context.invalidateItems(at: [indexPath]) - collectionView.collectionViewLayout.invalidateLayout() - } -} - -class BluetoothCell: UICollectionViewCell { - - fileprivate struct Companion { - static let identifier = "BluetoothCell" - } - - private let disposeBag = DisposeBag() - - @IBOutlet var deviceName: UILabel! - @IBOutlet var deviceIdentifier: UILabel! - @IBOutlet var rssi: UILabel! - @IBOutlet var txPower: UILabel! - @IBOutlet var buttonContainer: UIStackView! - @IBOutlet var connectButton: UIButton! - @IBOutlet var disconnectButton: UIButton! - - @IBOutlet var connectionStatus: UILabel! - @IBOutlet var foldOutMenu: UIView! - @IBOutlet var serviceId: UILabel! - @IBOutlet var serviceData: UILabel! - @IBOutlet var manufacturerId: UILabel! - @IBOutlet var manufacturerData: UILabel! - @IBOutlet var moreButtonContainer: UIView! - @IBOutlet var moreButton: UIButton! - - fileprivate var device: BluetoothListDeviceViewModel? = nil - - @IBAction func onConnectPressed() { - device?.onConnectPressed() - } - - @IBAction func onDisonnectPressed() { - device?.onDisconnectPressed() - } - - @IBAction func onMorePressed() { - device?.onMorePressed() - } - - func startMonitoring() { - disposeBag.dispose() - guard let device = device else { - return - } - device.didResume() - - deviceIdentifier.text = device.identifierString - - device.name.observe { [weak self] name in - self?.deviceName.text = name as? String - }.addTo(disposeBag: disposeBag) - - device.rssi.observe { [weak self] rssiValue in - self?.rssi.text = rssiValue as? String - }.addTo(disposeBag: disposeBag) - - device.txPower.observe { [weak self] txPowerValue in - self?.txPower.text = txPowerValue as? String - }.addTo(disposeBag: disposeBag) - - device.status.observe { [weak self] status in - self?.connectionStatus.text = status as? String - }.addTo(disposeBag: disposeBag) - - device.isConnectButtonVisible.observe { [weak self] isVisible in - self?.buttonContainer.alpha = (isVisible as? Bool ?? false) ? 1.0 : 0.0 - }.addTo(disposeBag: disposeBag) - - device.connectButtonState.observe { [weak self] connectButtonState in - let state: BluetoothListDeviceViewModel.ConnectButtonState = connectButtonState as? BluetoothListDeviceViewModel.ConnectButtonState ?? BluetoothListDeviceViewModel.ConnectButtonState.disconnect - switch state { - case BluetoothListDeviceViewModel.ConnectButtonState.connect: - self?.connectButton.isHidden = false - self?.disconnectButton.isHidden = true - case BluetoothListDeviceViewModel.ConnectButtonState.disconnect: - self?.connectButton.isHidden = true - self?.disconnectButton.isHidden = false - default: () - } - }.addTo(disposeBag: disposeBag) - - device.isFoldedOut.observe { [weak self] isFoldedOut in - self?.foldOutMenu.isHidden = !((isFoldedOut as? Bool) ?? false) - } - - device.isMoreButtonVisible.observe { [weak self] isMoreVisible in - self?.moreButtonContainer.isHidden = !(isMoreVisible as? Bool ?? false) - } - - device.serviceUUIDs.observe { [weak self] serviceUUIDS in - self?.serviceId.text = serviceUUIDS as? String - } - - device.serviceData.observe { [weak self] serviceData in - self?.serviceData.text = serviceData as? String - } - - device.manufacturerId.observe { [weak self] manufacturerId in - self?.manufacturerId.text = manufacturerId as? String - } - - device.manufacturerData.observe { [weak self] manufacturerData in - self?.manufacturerData.text = manufacturerData as? String - } - } - - func stopMonitoring() { - disposeBag.dispose() - device?.didPause() - } -} diff --git a/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift b/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift deleted file mode 100644 index 9ee2d520e..000000000 --- a/newexample/ios/Demo/DateTimePicker/DateTimePickerViewController.swift +++ /dev/null @@ -1,56 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class DateTimePickerViewController : UIViewController { - - @IBOutlet private var timeLabel: UILabel! - - lazy var viewModel = DateTimePickerViewModel(dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder(viewController: self)) - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { - return [] - } - return [ - viewModel.dateLabel.observe { (time) in - self?.timeLabel.text = time as? String - } - ] - } - } - - @IBAction - func selectDatePressed() { - viewModel.onSelectDatePressed() - } - - @IBAction - func selectTimePressed() { - viewModel.onSelectTimePressed() - } -} diff --git a/newexample/ios/Demo/Demo.entitlements b/newexample/ios/Demo/Demo.entitlements deleted file mode 100644 index 97405eaca..000000000 --- a/newexample/ios/Demo/Demo.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.developer.associated-domains - - applinks:kaluga-links.web.app - - - diff --git a/newexample/ios/Demo/ExampleViewController.swift b/newexample/ios/Demo/ExampleViewController.swift deleted file mode 100644 index d6ad2d207..000000000 --- a/newexample/ios/Demo/ExampleViewController.swift +++ /dev/null @@ -1,86 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class ExampleViewController : UIViewController { - - struct Const { - static let storyboard = UIStoryboard(name: "Main", bundle: nil) - static let featuresList = "FeaturesList" - static let infoView = "InfoViewController" - } - - @IBOutlet weak var containerView: UIView! - @IBOutlet weak var bottomView: UIStackView! - - lazy var featuresListController = Const.storyboard.instantiateViewController(withIdentifier: Const.featuresList) as! FeaturesListViewController - lazy var infoViewController = Const.storyboard.instantiateViewController(withIdentifier: Const.infoView) as! InfoViewController - - private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - NavigationSpec.Nested(type: NavigationSpec.NestedTypeReplace(tag: 1), containerView: self.containerView) { - switch action { - case is ExampleTabNavigation.FeatureList: return self.featuresListController - case is ExampleTabNavigation.Info: return self.infoViewController - default: return UIViewController() - } - } - } - lazy var viewModel: ExampleViewModel = ExampleViewModel(navigator: navigator) - var lifecycleManager: LifecycleManager! - let selectedButtonDisposeBag = DisposeBag() - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel, let bottomView = self?.bottomView else { return [] } - - return [ - viewModel.tabs.observeInitialized { tabs in - guard let disposeBag = self?.selectedButtonDisposeBag else { return } - disposeBag.dispose() - let tabs = tabs ?? [] - bottomView.arrangedSubviews.forEach { $0.removeFromSuperview() } - for tab in tabs { - guard let tab = tab as? ExampleViewModel.Tab else { return } - let button = UIButton() - button.setTitle(tab.title, for: .normal) - button.setTitleColor(UIColor.systemBlue, for: .selected) - button.setTitleColor(UIColor.systemBlue, for: .highlighted) - button.setTitleColor(UIColor.gray, for: .normal) - - viewModel.tab.observeInitialized { selectedTab in - button.isSelected = selectedTab == tab - }.addTo(disposeBag: disposeBag) - button.addAction { - viewModel.tab.post(newValue: tab) - } - bottomView.addArrangedSubview(button) - } - - } - ] - } - } - -} diff --git a/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift b/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift deleted file mode 100644 index e520eaa27..000000000 --- a/newexample/ios/Demo/FeaturesList/FeaturesListViewController.swift +++ /dev/null @@ -1,104 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class FeaturesListViewController : UITableViewController { - - private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - NavigationSpec.Segue(identifier: action.segueKey) - } - private lazy var viewModel: FeatureListViewModel = FeatureListViewModel(navigator: navigator) - private var lifecycleManager: LifecycleManager! - - private var features = [String]() - private var onSelected: ((Int) -> Void)? = nil - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - return [viewModel.feature.observeInitialized { next in - let features = next ?? [] - self?.features = features.map { ($0 as! Feature).title } - self?.onSelected = { (index: Int) in - viewModel.onFeaturePressed(feature: features[index] as! Feature) - } - self?.tableView.reloadData() - }] - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return features.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: FeaturesListCell.Const.identifier, for: indexPath) as! FeaturesListCell - cell.label.text = features[indexPath.row] - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(indexPath.row) - tableView.deselectRow(at: indexPath, animated: true) - } - -} - -class FeaturesListCell : UITableViewCell { - - struct Const { - static let identifier = "FeaturesListCell" - } - - @IBOutlet weak var label: UILabel! - -} - -private extension FeatureListNavigationAction { - var segueKey: String { - get { - switch self { - case is FeatureListNavigationAction.Alerts: return "showAlerts" - case is FeatureListNavigationAction.Architecture: return "showArchitecture" - case is FeatureListNavigationAction.Beacons: return "showBeacons" - case is FeatureListNavigationAction.Bluetooth: return "showBluetooth" - case is FeatureListNavigationAction.DateTimePicker: return "showDateTimePicker" - case is FeatureListNavigationAction.Keyboard: return "showKeyboard" - case is FeatureListNavigationAction.Links: return "showLinks" - case is FeatureListNavigationAction.LoadingIndicator: return "showHUD" - case is FeatureListNavigationAction.Location: return "showLocation" - case is FeatureListNavigationAction.Permissions: return "showPermissions" - case is FeatureListNavigationAction.PlatformSpecific: return "showPlatformSpecific" - case is FeatureListNavigationAction.Resources: return "showResources" - case is FeatureListNavigationAction.System: return "showSystem" - default: return "" - } - } - } -} diff --git a/newexample/ios/Demo/Helpers/UIControl+Closure.swift b/newexample/ios/Demo/Helpers/UIControl+Closure.swift deleted file mode 100644 index de3718ea9..000000000 --- a/newexample/ios/Demo/Helpers/UIControl+Closure.swift +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit - -@objc class UIControlClosure: NSObject { - let closure: ()->() - - init (_ closure: @escaping ()->()) { - self.closure = closure - } - - @objc func invoke () { - closure() - } -} - -extension UIControl { - func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping ()->()) { - let sleeve = UIControlClosure(closure) - addTarget(sleeve, action: #selector(UIControlClosure.invoke), for: controlEvents) - objc_setAssociatedObject(self, "[\(arc4random())]", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) - } -} diff --git a/newexample/ios/Demo/Info.plist b/newexample/ios/Demo/Info.plist deleted file mode 100644 index 51a9d709a..000000000 --- a/newexample/ios/Demo/Info.plist +++ /dev/null @@ -1,82 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - com.splendo.kaluga.example - CFBundleURLSchemes - - kalugaexample - - - - CFBundleVersion - 1 - LSRequiresIPhoneOS - - NSBluetoothAlwaysUsageDescription - Demonstrate usage of Kaluga library - NSBluetoothPeripheralUsageDescription - Demonstrate usage of Kaluga library - NSCalendarsUsageDescription - Demonstrate usage of Kaluga library - NSBluetoothAlwaysUsageDescription - Demonstrate usage of Kaluga library - NSBluetoothPeripheralUsageDescription - Demonstrate usage of Kaluga library - NSCameraUsageDescription - Demonstrate usage of Kaluga library - NSContactsUsageDescription - Demonstrate usage of Kaluga library - NSLocationAlwaysAndWhenInUseUsageDescription - Demonstrate usage of Kaluga library - NSLocationAlwaysUsageDescription - Demonstrate usage of Kaluga library - NSLocationWhenInUseUsageDescription - Demonstrate usage of Kaluga library - NSMicrophoneUsageDescription - Demonstrate usage of Kaluga library - NSPhotoLibraryUsageDescription - Demonstrate usage of Kaluga library - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/newexample/ios/Demo/Info/InfoViewController.swift b/newexample/ios/Demo/Info/InfoViewController.swift deleted file mode 100644 index a2f18dc06..000000000 --- a/newexample/ios/Demo/Info/InfoViewController.swift +++ /dev/null @@ -1,104 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import MessageUI -import KalugaExampleShared - -class InfoViewController : UITableViewController { - - private lazy var navigator = InfoNavigatorKt.InfoNavigator( - parent: self, - onDialogSpec: { dialogSpec in - NavigationSpec.Present( - animated: true, - presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), - transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue) - ) { - let alert = UIAlertController.init(title: dialogSpec.title, message: dialogSpec.message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default)) - return alert - } - }, - onLink: { link in NavigationSpec.Browser(url: URL(string: link)!, viewType: NavigationSpec.BrowserTypeNormal()) }, - onMailSpec: { mailSpec in - let settings = NavigationSpec.Email.EmailEmailSettings(type: NavigationSpec.EmailTypePlain(), to: mailSpec.to, cc: [], bcc: [], subject: mailSpec.subject, body: nil, attachments: []) - return NavigationSpec.Email(emailSettings: settings, delegate: nil, animated: true) - } - ) - private lazy var viewModel: InfoViewModel = InfoViewModel( - reviewManagerBuilder: ReviewManager.Builder(), - navigator: navigator) - - private var buttons = [String]() - private var onSelected: ((Int) -> Void)? = nil - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - - guard let viewModel = self?.viewModel else { - return [] - } - - return [ - viewModel.buttons.observeInitialized { next in - let buttons = next?.compactMap { $0 as? InfoViewModel.Button } ?? [] - self?.buttons = buttons.map { $0.title } - self?.onSelected = { (index: Int) in viewModel.onButtonPressed(button: buttons[index]) } - self?.tableView.reloadData() - } - ] - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return buttons.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: InfoButtonCell.Const.identifier, for: indexPath) as! InfoButtonCell - cell.label.text = buttons[indexPath.row] - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(indexPath.row) - tableView.deselectRow(at: indexPath, animated: true) - } - -} - -class InfoButtonCell : UITableViewCell { - - struct Const { - static let identifier = "InfoButtonCell" - } - - @IBOutlet weak var label: UILabel! - -} diff --git a/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift b/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift deleted file mode 100644 index 8e0d31743..000000000 --- a/newexample/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class KeyboardManagerViewController : UIViewController { - - @IBOutlet private var editField: UITextField! - - private lazy var editFieldFocusHandler = { - return UIKitFocusHandler(view: self.editField) - }() - lazy var viewModel = KeyboardViewModel(keyboardManagerBuilder: KeyboardManager.Builder(application: UIApplication.shared), editFieldFocusHandler: editFieldFocusHandler) - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } - } - - @IBAction - func showButtonPressed() { - viewModel.onShowPressed() - } - - @IBAction - func hideButtonPressed() { - viewModel.onHidePressed() - } -} diff --git a/newexample/ios/Demo/Links/LinksViewController.swift b/newexample/ios/Demo/Links/LinksViewController.swift deleted file mode 100644 index 34ab58446..000000000 --- a/newexample/ios/Demo/Links/LinksViewController.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import Foundation -import UIKit -import KalugaExampleShared - -class LinksViewController : UIViewController { - - @IBOutlet weak var browserButton: UIButton! - @IBOutlet weak var instructionsText: UILabel! - - private lazy var navigator: ViewControllerNavigator> = BrowserNavigatorKt.BrowserNavigator(parent: self) - private lazy var viewModel: LinksViewModel = LinksViewModel(linkRepoBuilder: LinksLinksBuilder(), builder: AlertPresenter.Builder(viewController: self), navigator: navigator) - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { - [ - self.viewModel.browserButtonText.observe { buttonText in - self.browserButton.setTitle(buttonText as String?, for: .normal) - }, - self.viewModel.linksInstructions.observe { text in - self.instructionsText.text = text as String? - } - ] - } - } - - @IBAction func onBrowserButtonTapped(_ sender: UIButton) { - self.viewModel.openWebPage() - } - - func handleIncomingLink(url: String) { - self.viewModel.handleIncomingLink(url: url) - } -} diff --git a/newexample/ios/Demo/LoadingIndicator/ActivityViewController.swift b/newexample/ios/Demo/LoadingIndicator/ActivityViewController.swift deleted file mode 100644 index 045eea92b..000000000 --- a/newexample/ios/Demo/LoadingIndicator/ActivityViewController.swift +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit - -class ActivityViewController: UIViewController { } diff --git a/newexample/ios/Demo/LoadingIndicator/ActivityViewController.xib b/newexample/ios/Demo/LoadingIndicator/ActivityViewController.xib deleted file mode 100644 index 0b785d2bd..000000000 --- a/newexample/ios/Demo/LoadingIndicator/ActivityViewController.xib +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift b/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift deleted file mode 100644 index 94ce6baad..000000000 --- a/newexample/ios/Demo/LoadingIndicator/LoadingViewController.swift +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class LoadingViewController: UITableViewController { - - private lazy var viewModel = HudViewModel(builder: HUD.Builder(viewController: self)) - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - switch (indexPath.row) { - case 0: viewModel.onShowSystemPressed() - case 1: viewModel.onShowCustomPressed() - default: () - } - } -} diff --git a/newexample/ios/Demo/Location/LocationViewController.swift b/newexample/ios/Demo/Location/LocationViewController.swift deleted file mode 100644 index 2b82f3b9c..000000000 --- a/newexample/ios/Demo/Location/LocationViewController.swift +++ /dev/null @@ -1,54 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - -*/ - -import UIKit -import CoreLocation -import KalugaExampleShared - -class LocationViewController: UIViewController { - - struct Const { - static let permission = LocationPermission(background: false, precise: true) - } - - //MARK: Properties - - @IBOutlet weak var label: UILabel! - - lazy var viewModel = LocationViewModel(permission: Const.permission) - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { - return [] - } - return [ - viewModel.location.observe { (location) in - self?.label.text = location as? String ?? "" - } - ] - } - } -} diff --git a/newexample/ios/Demo/Permissions/PermissionListViewController.swift b/newexample/ios/Demo/Permissions/PermissionListViewController.swift deleted file mode 100644 index 2fd828e7c..000000000 --- a/newexample/ios/Demo/Permissions/PermissionListViewController.swift +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import KalugaExampleShared - -class PermissionListViewController : UITableViewController { - - private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - NavigationSpec.Push(animated: true) { - guard let permission = action.value?.permission else { - return UIViewController() - } - - return PermissionViewController.create(permission: permission) - } - } - private lazy var viewModel: PermissionsListViewModel = PermissionsListViewModel(navigator: navigator) - private var lifecycleManager: LifecycleManager! - - private var permissions = [PermissionView]() - private var onSelected: ((Int) -> Void)? = nil - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - return [ - viewModel.permissions.observeInitialized { next in - let permissions = next?.compactMap { $0 as? PermissionView } ?? [] - self?.permissions = permissions - self?.onSelected = { (index: Int) in viewModel.onPermissionPressed(permissionView: permissions[index]) } - self?.tableView.reloadData() - } - ] - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return permissions.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: PermissionsListCell.Const.identifier, for: indexPath) as! PermissionsListCell - cell.label.text = permissions[indexPath.row].title - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(indexPath.row) - tableView.deselectRow(at: indexPath, animated: true) - } - -} - -class PermissionsListCell : UITableViewCell { - - struct Const { - static let identifier = "PermissionsListCell" - } - - @IBOutlet weak var label: UILabel! - -} diff --git a/newexample/ios/Demo/Permissions/PermissionViewController.swift b/newexample/ios/Demo/Permissions/PermissionViewController.swift deleted file mode 100644 index 4bca12051..000000000 --- a/newexample/ios/Demo/Permissions/PermissionViewController.swift +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit -import Foundation -import KalugaExampleShared - -class PermissionViewController: UIViewController { - - private struct Const { - static let storyboard = UIStoryboard(name: "Main", bundle: nil) - static let permissionVc = "Permission" - } - - static func create(permission: Permission) -> PermissionViewController { - let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.permissionVc) as! PermissionViewController - vc.viewModel = PermissionViewModel(permission: permission) - return vc - } - - @IBOutlet weak var permissionStateLabel: UILabel! - @IBOutlet weak var requestPermissionButton: UIButton! - - var viewModel: PermissionViewModel! - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - requestPermissionButton.setTitle(NSLocalizedString("permission_request", comment: ""), for: .normal) - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - - guard let viewModel = self?.viewModel else { - return [] - } - - return [ - viewModel.permissionStateMessage.observe { message in - self?.permissionStateLabel.text = NSLocalizedString(message as? String ?? "", comment: "") - - }, - - viewModel.requestMessage.observe { optionalMessage in - guard let message = optionalMessage as? String else { - return - } - - let alert = UIAlertController(title: NSLocalizedString("permission_request", comment: ""), message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) - self?.present(alert, animated: true, completion: nil) - - }, - - viewModel.showPermissionButton.observe { show in - self?.requestPermissionButton.isHidden = !(show as? Bool ?? false) - } - ] - } - } - - - @IBAction func requestPermission(sender: Any?) { - viewModel.requestPermission() - } - -} diff --git a/newexample/ios/Demo/Resources/ButtonViewController.swift b/newexample/ios/Demo/Resources/ButtonViewController.swift deleted file mode 100644 index f00e435ff..000000000 --- a/newexample/ios/Demo/Resources/ButtonViewController.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright 2022 Splendo Consulting B.V. The Netherlands -// -// 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. -// - -import UIKit -import KalugaExampleShared - -class ButtonViewController : UITableViewController { - - private lazy var viewModel: ButtonViewModel = ButtonViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider(), alertPresenterBuilder: AlertPresenter.Builder(viewController: self)) - private var lifecycleManager: LifecycleManager! - - private var buttons = [KalugaButton]() - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - tableView.allowsSelection = false - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - return [ - viewModel.buttons.observe { labels in - self?.buttons = labels?.compactMap { $0 as? KalugaButton } ?? [] - self?.tableView.reloadData() - } - ] - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return buttons.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: ButtonListCell.Const.identifier, for: indexPath) as! ButtonListCell - ButtonStyleKt.bindButton(cell.button, button: buttons[indexPath.row]) - return cell - } -} - -class ButtonListCell : UITableViewCell { - - struct Const { - static let identifier = "ButtonListCell" - } - - @IBOutlet weak var button: UIButton! - -} diff --git a/newexample/ios/Demo/Resources/ColorViewController.swift b/newexample/ios/Demo/Resources/ColorViewController.swift deleted file mode 100644 index c8412766a..000000000 --- a/newexample/ios/Demo/Resources/ColorViewController.swift +++ /dev/null @@ -1,202 +0,0 @@ -// -// Copyright 2022 Splendo Consulting B.V. The Netherlands -// -// 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. -// - -import UIKit -import KalugaExampleShared - -class ColorViewController : UIViewController { - - @IBOutlet var backdropColorBackground: UIView! - @IBOutlet var sourceColorBackground: UIView! - @IBOutlet var blendedColorBackground: UIView! - - @IBOutlet var backdropInputField: UITextField! - @IBOutlet var sourceInputField: UITextField! - - @IBOutlet var blendModeButton: UIButton! - @IBOutlet var flipButton: UIButton! - - @IBOutlet var backdropLightenedColorsCollectionView: UICollectionView! - @IBOutlet var backdropDarkenedColorsCollectionView: UICollectionView! - @IBOutlet var sourceLightenedColorsCollectionView: UICollectionView! - @IBOutlet var sourceDarkenedColorsCollectionView: UICollectionView! - @IBOutlet var blendedLightenedColorsCollectionView: UICollectionView! - @IBOutlet var blendedDarkenedColorsCollectionView: UICollectionView! - - private var backdropLightenedColors: [BackgroundStyle] = [] - private var backdropDarkenedColors: [BackgroundStyle] = [] - private var sourceLightenedColors: [BackgroundStyle] = [] - private var sourceDarkenedColors: [BackgroundStyle] = [] - private var blendedLightenedColors: [BackgroundStyle] = [] - private var blendedDarkenedColors: [BackgroundStyle] = [] - - private lazy var viewModel: ColorViewModel = ColorViewModel(alertPresenterBuilder: AlertPresenter.Builder(viewController: self)) - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - backdropLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - backdropDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - sourceLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - sourceDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - blendedLightenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - blendedDarkenedColorsCollectionView.register(ColorCollectionCellView.self, forCellWithReuseIdentifier: ColorCollectionCellView.Const.identifier) - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - - return [ - viewModel.backdropColorBackground.observe { next in - guard let backdropColorBackground = self?.backdropColorBackground, let backdropBackgroundStyle = next as? BackgroundStyle else { - return - } - BackgroundStyleKt.applyBackgroundStyle(backdropColorBackground, style: backdropBackgroundStyle) - }, - viewModel.sourceColorBackground.observe { next in - guard let sourceColorBackground = self?.sourceColorBackground, let sourceBackgroundStyle = next as? BackgroundStyle else { - return - } - BackgroundStyleKt.applyBackgroundStyle(sourceColorBackground, style: sourceBackgroundStyle) - }, - viewModel.blendedColorBackground.observe { next in - guard let blendedColorBackground = self?.blendedColorBackground, let blendedBackgroundStyle = next as? BackgroundStyle else { - return - } - BackgroundStyleKt.applyBackgroundStyle(blendedColorBackground, style: blendedBackgroundStyle) - }, - viewModel.backdropText.observe { text in - self?.backdropInputField.text = text as String? - }, - viewModel.sourceText.observe { text in - self?.sourceInputField.text = text as String? - }, - viewModel.blendModeButton.observe { next in - guard let blendModeButton = self?.blendModeButton, let buttonStyle = next else { - return - } - ButtonStyleKt.bindButton(blendModeButton, button: buttonStyle) - }, - viewModel.lightenBackdrops.observe { next in - self?.backdropLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] - self?.backdropLightenedColorsCollectionView?.reloadData() - }, - viewModel.darkenBackdrops.observe { next in - self?.backdropDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] - self?.backdropDarkenedColorsCollectionView?.reloadData() - }, - viewModel.lightenSource.observe { next in - self?.sourceLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] - self?.sourceLightenedColorsCollectionView?.reloadData() - }, - viewModel.darkenSource.observe { next in - self?.sourceDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] - self?.sourceDarkenedColorsCollectionView?.reloadData() - }, - viewModel.lightenBlended.observe { next in - self?.blendedLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] - self?.blendedLightenedColorsCollectionView?.reloadData() - }, - viewModel.darkenBlended.observe { next in - self?.blendedDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] - self?.blendedDarkenedColorsCollectionView?.reloadData() - }, - ] - } - - ButtonStyleKt.bindButton(flipButton, button: viewModel.flipButton) - } -} - -extension ColorViewController : UITextFieldDelegate { - func textFieldDidEndEditing(_ textField: UITextField) { - if textField == backdropInputField { - viewModel.submitBackdropText(backdropText: textField.text ?? "") - } - if textField == sourceInputField { - viewModel.submitSourceText(sourceText: textField.text ?? "") - } - } -} - -extension ColorViewController : UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - if collectionView == backdropLightenedColorsCollectionView { - return backdropLightenedColors.count - } - if collectionView == backdropDarkenedColorsCollectionView { - return backdropDarkenedColors.count - } - if collectionView == sourceLightenedColorsCollectionView { - return sourceLightenedColors.count - } - if collectionView == sourceDarkenedColorsCollectionView { - return sourceDarkenedColors.count - } - if collectionView == blendedLightenedColorsCollectionView { - return blendedLightenedColors.count - } - if collectionView == blendedDarkenedColorsCollectionView { - return blendedDarkenedColors.count - } - return 0 - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize.init(width: collectionView.frame.height, height: collectionView.frame.height) - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ColorCollectionCellView.Const.identifier, for: indexPath) as! ColorCollectionCellView - if collectionView == backdropLightenedColorsCollectionView { - BackgroundStyleKt.applyBackgroundStyle(cell, style: backdropLightenedColors[indexPath.row]) - } - if collectionView == backdropDarkenedColorsCollectionView { - BackgroundStyleKt.applyBackgroundStyle(cell, style: backdropDarkenedColors[indexPath.row]) - } - if collectionView == sourceLightenedColorsCollectionView { - BackgroundStyleKt.applyBackgroundStyle(cell, style: sourceLightenedColors[indexPath.row]) - } - if collectionView == sourceDarkenedColorsCollectionView { - BackgroundStyleKt.applyBackgroundStyle(cell, style: sourceDarkenedColors[indexPath.row]) - } - if collectionView == blendedLightenedColorsCollectionView { - BackgroundStyleKt.applyBackgroundStyle(cell, style: blendedLightenedColors[indexPath.row]) - } - if collectionView == blendedDarkenedColorsCollectionView { - BackgroundStyleKt.applyBackgroundStyle(cell, style: blendedDarkenedColors[indexPath.row]) - } - return cell - } -} - -class ColorCollectionCellView : UICollectionViewCell { - - struct Const { - static let identifier = "ColorCollectionCellView" - } - - override init(frame: CGRect) { - super.init(frame: frame) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/newexample/ios/Demo/Resources/LabelViewController.swift b/newexample/ios/Demo/Resources/LabelViewController.swift deleted file mode 100644 index dd8695a79..000000000 --- a/newexample/ios/Demo/Resources/LabelViewController.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright 2022 Splendo Consulting B.V. The Netherlands -// -// 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. -// - -import UIKit -import KalugaExampleShared - -class LabelViewController : UITableViewController { - - private var viewModel: LabelViewModel = LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider()) - private var lifecycleManager: LifecycleManager! - - private var labels = [KalugaLabel]() - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - tableView.allowsSelection = false - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - return [ - viewModel.labels.observe { labels in - self?.labels = labels?.compactMap { $0 as? KalugaLabel } ?? [] - self?.tableView.reloadData() - } - ] - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return labels.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: LabelListCell.Const.identifier, for: indexPath) as! LabelListCell - TextStyleKt.bindLabel(cell.label, label: labels[indexPath.row]) - return cell - } -} - -class LabelListCell : UITableViewCell { - - struct Const { - static let identifier = "LabelListCell" - } - - @IBOutlet weak var label: UILabel! - -} diff --git a/newexample/ios/Demo/Resources/ResourcesListViewController.swift b/newexample/ios/Demo/Resources/ResourcesListViewController.swift deleted file mode 100644 index 40d7799a8..000000000 --- a/newexample/ios/Demo/Resources/ResourcesListViewController.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright 2022 Splendo Consulting B.V. The Netherlands -// -// 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. -// - -import UIKit -import KalugaExampleShared - -class ResourcesListViewController : UITableViewController { - - private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - NavigationSpec.Segue(identifier: action.segueKey) - } - - private lazy var viewModel: ResourcesListViewModel = ResourcesListViewModel(navigator: navigator) - private var lifecycleManager: LifecycleManager! - - private var resources = [String]() - private var onSelected: ((Int) -> Void)? = nil - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - return [ - viewModel.resources.observeInitialized { next in - let resources = next ?? [] - self?.resources = resources.map { ($0 as! Resource).title } - self?.onSelected = { (index: Int) in viewModel.onResourceSelected(resource: resources[index] as! Resource) } - self?.tableView.reloadData() - } - ] - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return resources.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: ResourcesListCell.Const.identifier, for: indexPath) as! ResourcesListCell - cell.label.text = resources[indexPath.row] - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(indexPath.row) - tableView.deselectRow(at: indexPath, animated: true) - } - -} - -class ResourcesListCell : UITableViewCell { - - struct Const { - static let identifier = "ResourcesListCell" - } - - @IBOutlet weak var label: UILabel! - -} - -private extension ResourcesListNavigationAction { - var segueKey: String { - get { - switch self { - case is ResourcesListNavigationAction.Button: return "showButton" - case is ResourcesListNavigationAction.Color: return "showColor" - case is ResourcesListNavigationAction.Label: return "showLabel" - default: return "" - } - } - } -} diff --git a/newexample/ios/Demo/System/Network/NetworkViewController.swift b/newexample/ios/Demo/System/Network/NetworkViewController.swift deleted file mode 100644 index ead546b08..000000000 --- a/newexample/ios/Demo/System/Network/NetworkViewController.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import Foundation -import UIKit -import KalugaExampleShared - -class NetworkViewController : UIViewController { - - @IBOutlet weak var networkStateText: UILabel! - private var lifecycleManager: LifecycleManager! - - private lazy var viewModel: NetworkViewModel = NetworkViewModel(networkStateRepoBuilder: NetworkStateRepoBuilder()) - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { return [] } - - return [ - viewModel.networkState.observe { next in - self?.networkStateText.text = next as? String - } - ] - } - } - -} diff --git a/newexample/ios/Demo/System/SystemViewController.swift b/newexample/ios/Demo/System/SystemViewController.swift deleted file mode 100644 index 782e14c5b..000000000 --- a/newexample/ios/Demo/System/SystemViewController.swift +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import Foundation -import UIKit -import KalugaExampleShared - -class SystemViewController : UITableViewController { - - private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - switch action { - case is SystemNavigationActions.Network: return NavigationSpec.Segue(identifier: "showNetwork") - default: return NavigationSpec.Segue(identifier: "") - } - } - private lazy var viewModel: SystemViewModel = SystemViewModel(navigator: navigator) - - private var modules = [String]() - private var onModuleTapped: ((Int) -> Void)? = nil - private var lifecycleManager: LifecycleManager! - - deinit { - lifecycleManager.unbind() - } - - override func viewDidLoad() { - super.viewDidLoad() - - lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in - guard let viewModel = self?.viewModel else { - return [] - } - return [ - viewModel.modules.observeInitialized { next in - let modules = next?.compactMap { $0 as? SystemFeatures } ?? [] - self?.modules = modules.map{ $0.name } - self?.onModuleTapped = { (index: Int) in viewModel.onButtonTapped(systemFeatures: modules[index]) } - self?.tableView.reloadData() - } - ] - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return modules.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: SystemListCell.Const.identifier, for: indexPath) as! SystemListCell - cell.label.text = modules[indexPath.row] - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onModuleTapped?(indexPath.row) - tableView.deselectRow(at: indexPath, animated: true) - } - -} - -class SystemListCell : UITableViewCell { - - struct Const { - static let identifier = "SystemListCell" - } - - @IBOutlet weak var label: UILabel! - -} diff --git a/newexample/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift b/newexample/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift deleted file mode 100644 index 81fa29aa4..000000000 --- a/newexample/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - - */ - -import UIKit - -final class FittingWidthAutomaticHeightCollectionViewFlowLayout: UICollectionViewFlowLayout { - - override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { - let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes] - layoutAttributesObjects?.forEach({ layoutAttributes in - if layoutAttributes.representedElementCategory == .cell { - if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame { - layoutAttributes.frame = newFrame - } - } - }) - return layoutAttributesObjects - } - - override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { - guard let collectionView = collectionView else { - fatalError() - } - guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else { - return nil - } - - layoutAttributes.frame.origin.x = sectionInset.left - layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right - return layoutAttributes - } - - override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, - withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool { - return true - } -} diff --git a/newexample/ios/DemoTests/DemoTests.swift b/newexample/ios/DemoTests/DemoTests.swift deleted file mode 100644 index dbe30118f..000000000 --- a/newexample/ios/DemoTests/DemoTests.swift +++ /dev/null @@ -1,46 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - -*/ - -import XCTest -@testable import Demo -import KalugaExampleShared - -class DemoTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - assert(KotlinNativeFramework().hello() == "Hello from the shared module common source") - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // 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/newexample/ios/DemoTests/Info.plist b/newexample/ios/DemoTests/Info.plist deleted file mode 100644 index 6c40a6cd0..000000000 --- a/newexample/ios/DemoTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/newexample/ios/DemoUITests/DemoUITests.swift b/newexample/ios/DemoUITests/DemoUITests.swift deleted file mode 100644 index 85a5ce551..000000000 --- a/newexample/ios/DemoUITests/DemoUITests.swift +++ /dev/null @@ -1,44 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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. - -*/ - -import XCTest - -class DemoUITests: XCTestCase { - - override func setUp() { - // 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 - - // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. - XCUIApplication().launch() - - // 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 tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - -} diff --git a/newexample/ios/DemoUITests/Info.plist b/newexample/ios/DemoUITests/Info.plist deleted file mode 100644 index 6c40a6cd0..000000000 --- a/newexample/ios/DemoUITests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/newexample/keystore/debug.keystore b/newexample/keystore/debug.keystore deleted file mode 100644 index 38a87ac589bd046c4a57ae014ae5850c828d9679..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2419 zcmY+EXEYlM8^@3|kI^PKbh|DWgM4}oX(12TaScvg8R>+eJiagT$EnJE{~ zDhS52!hYe?2t35&UlGI}jEC6%!WVzddFZMCU9qz<0dw)-Lj)eYhfsjB{U85&E)3>% z?B@OVK|B0U@$6yAV8Zn7!U5Mv|M{l+g)_qs*(`Lkp21Ds(e>Wp4;ED zWwXXg(|NW^-_i`F*n$*J%$;V2X7s7w4}>nH$6fh2G!$B-XnA_q-YV8~d3q5ZKwqCu z6E`w=_#@yzO9YV_{9RWZY3|p04AA)N`mmDZP+w&0y<-wWp5rMs&AQ|b? zfj#fi*l+bo5k`DHg-cK$Y>(Yoqqvj-j6R1Sa@@q3DAH$V^A9tar+Fulv$;QuwLI1_ z@@*sDFB4vQ>tYLlA&kymU>V>I`HQPa)b0*-3;99_Tm z9|pzu?c^1Mon4%DK|V+3BQ1#~Q>MOduu^dtEz97y*ZDUA7ABX@TJAJR1}TV0)4Q1d zhyqJNcuk3ZU#@SgVaaO;+75^tMQ65l+X*c6QgwZxyzKb4&OBFP>3JC(EQH56O55Z^ zjKXFQk}u-kL=&*x#n3QjGKn$JMg_8oJ?6}@xfeml6%}%pYl# z5M0SoQ%Hu!-?nfd9_0$EAtUsst{S>Ndw5XijoD5J86V6KxpqXkR_vnMIxQ#4KEz2n z#yWs{I$>`M_p0F%8S#!3jAqw&>=un6_oA8_Cl0;BzZ{9nG?1!R3P0GnxzT@F8KnRb zhevsZOr@52UR-}9>ihJ?yxL4X2QGfN@eUP!?}-sJF>CTsC-o##rk*9?C2Neq`>sS| zsde`bTo&$3HqbSYqwzI5|M~%dcoFv9A>7A`WymB4`evPk;`2w32k;jqNr*;M-ge~N zyZM5g@~qvWo-R`+G<^GhSd-?xE0=zu%IfMmr2X|%8vb~ zUS09KE^;Ed>)?oY4thmWC^2eS8Xq*P6SY5Q_$go0=e}|b$kFuwJVgH}j_Tc;SFX6>Zm!`8i*~!wcMbxE*RmH~r<_eb zjYK2(|5ha{FOp4^1#lbS0l)$f2#J3J7?KyveaYvphd5Flsg6QwYH6WVH8l`;P{H3N zW@s)Rl<^Ct0+|56R{TE+)4%Kr{f}MU0@C{gJH*OxjuB?(v%7sJ^wzunYu89T$Xe9F zd=#s`%Rx#lGFByAP#)~!Z#PMPI;yl;)fmTOr^)qtgGcI0XhXp+d z-+D3Nw;#UQOCLzX6iy2}_3P2=4Gs@K3$5DcxI9aJk&RV|;T^lS?L{wf&0cD53$Y`i z%SYzn@|%~L`m~l$UbCwwGn4h-pYcSm&8_acFNv2X7L%zUS9M5ny{VZ};f+JA~m%>Oqy{R9QeJGvyTOhU-kokT+#>*7p|w4uA- zKr-9Vf_Sbm45V2wJ2ZaoNK;GOiH#D0tI=@8im`Mf!sKGx&K!@0rU1bsraEhrVUjJ9c_>Yw9-^)&wCXbd#If z)jTIPTwA}1bS~f2;Y;WM+n-J;Ir*ckB;(*#z76B0rW@~hmIu1AqgUK&B`Iwe3i{Kk zEUrBsrz)R`9Or7u4(?hN?JiU_c`i2Q&EBwhK@AhFk##PbGj8#g^{Tsaq_PKc!wy)v z(E2RIeS9{RHpm>`Jo*z4*-*b*ZJGTAMHtomqT72N9-5cKTD}ONG}=^PP9Y13G?v!Y z42{@zR!Rdl0@v<}3vqT#wGsN%wnd=?%NjDiR+~KU_H&nsSSM&GsTOR-a(o}!ko+>> z_4O&G?(VxU)TE#s;;ds}{rptNGO1)>&CJX-c`0MXz|A%{UYqDrASpX6;+ectOKcb} z@IdeiMERZSW)CJ&#+w<<%?d9Cf<;$mSc-XLqPbT(h9mnrGV&BUWC4pu>NaE?^MEHi zNhj%euHpu5>(mJ1?(YZ`(;F2lLoTB*RlS#a<_|Lu@(H_^DSghv(g`+Y&0YW6Q|-2v^Cn z3stlb`}{t5aB>zpyH+uZ;TZNUHP>tEw{V828#BS$D zennMYHMiLiI7X5XdI$*wD-@#02?7d10AOBy6kO2loCu7bDcmcT9y`NRxey8F - if (isExportable) { - export("com.splendo.kaluga:$module:$libraryVersion") - } - } -} - -kotlin { - sourceSets { - getByName("commonMain") { - dependencies { - modules.forEach { (module, _) -> - api("com.splendo.kaluga:$module:$libraryVersion") - } - expose(Dependencies.Koin.Core) - } - } - } -} - -android { - dependencies { - expose(Dependencies.Koin.Android) - } -} diff --git a/newexample/shared/src/androidLibMain/AndroidManifest.xml b/newexample/shared/src/androidLibMain/AndroidManifest.xml deleted file mode 100644 index f595a3b11..000000000 --- a/newexample/shared/src/androidLibMain/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt deleted file mode 100644 index 8d35888bc..000000000 --- a/newexample/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.featureList - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -actual val showPlatformSpecificFeatures: Boolean = true - -sealed class PlatformFeatureListNavigationAction : NavigationAction(null) { - object ComposeNavigation : PlatformFeatureListNavigationAction() - object ComposeBottomSheet : PlatformFeatureListNavigationAction() -} - -sealed class PlatformFeature(val title: String) { - object ComposeNavigation : PlatformFeature("feature_platform_specific_compose_navigation".localized()) - object ComposeBottomSheet : PlatformFeature("feature_platform_specific_compose_bottom_sheet".localized()) -} - -class PlatformSpecificFeaturesViewModel( - navigator: Navigator -) : NavigatingViewModel(navigator) { - - val feature = observableOf( - listOf( - PlatformFeature.ComposeNavigation, - PlatformFeature.ComposeBottomSheet - ) - ) - - fun onFeaturePressed(feature: PlatformFeature) { - navigator.navigate( - when (feature) { - is PlatformFeature.ComposeNavigation -> PlatformFeatureListNavigationAction.ComposeNavigation - is PlatformFeature.ComposeBottomSheet -> PlatformFeatureListNavigationAction.ComposeBottomSheet - } - ) - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt deleted file mode 100644 index 0d04f4d26..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.observable.toInitializedSubject -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -sealed class ExampleTabNavigation : NavigationAction(null) { - - object FeatureList : ExampleTabNavigation() - object Info : ExampleTabNavigation() -} - -class ExampleViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - sealed class Tab(val title: String) { - object FeatureList : Tab("Features") - object Info : Tab("About") - } - - val tabs = observableOf(listOf(Tab.FeatureList, Tab.Info)) - - private val _tab = MutableStateFlow(Tab.FeatureList) - val tab = _tab.toInitializedSubject(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - scope.launch(Dispatchers.Main.immediate) { - _tab.collect { currentTab -> - navigator.navigate( - when (currentTab) { - is Tab.FeatureList -> ExampleTabNavigation.FeatureList - is Tab.Info -> ExampleTabNavigation.Info - } - ) - } - } - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt deleted file mode 100644 index 89e7ca1ad..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.architecture - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.observable.InitializedObservable -import com.splendo.kaluga.architecture.observable.subjectOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import kotlinx.serialization.Serializable - -@Serializable -data class InputDetails( - val name: String, - val number: Int -) - -class CloseDetailsNavigation(inputDetails: InputDetails) : SingleValueNavigationAction( - inputDetails, - NavigationBundleSpecType.SerializedType(InputDetails.serializer()) -) - -class ArchitectureDetailsViewModel(initialDetail: InputDetails, navigator: Navigator) : NavigatingViewModel(navigator) { - - private val _name = subjectOf(initialDetail.name) - val name: InitializedObservable = _name - private val _number = subjectOf(initialDetail.number.toString()) - val number: InitializedObservable = _number - - private var nameResult: String by _name.valueDelegate - private var numberResult: String by _number.valueDelegate - - fun onInversePressed() { - nameResult = nameResult.reversed() - numberResult = numberResult.reversed() - } - - fun onClosePressed() { - navigator.navigate(CloseDetailsNavigation(InputDetails(nameResult, numberResult.toIntOrNull() ?: 0))) - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt deleted file mode 100644 index de8408407..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.beacons - -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.base.text.format -import com.splendo.kaluga.bluetooth.beacons.BeaconInfo -import com.splendo.kaluga.bluetooth.beacons.Beacons -import com.splendo.kaluga.bluetooth.beacons.get -import com.splendo.kaluga.bluetooth.device.Identifier -import com.splendo.kaluga.resources.localized -import kotlinx.coroutines.flow.map - -class BeaconsListBeaconViewModel(identifier: Identifier, service: Beacons) : BaseLifecycleViewModel() { - - private val beacon = service.beacons[identifier] - - private fun beaconInfoObservable(mapper: (BeaconInfo?) -> T) = beacon - .map { mapper(it) } - .toInitializedObservable(null, coroutineScope) - - val namespace = beaconInfoObservable { it?.beaconID?.namespace ?: "" } - val instance = beaconInfoObservable { it?.beaconID?.instance ?: "" } - val txPower = beaconInfoObservable { - it?.txPower?.let { txPower -> - "txPower".localized().format(txPower) - } ?: "" - } - - public override fun onCleared() = super.onCleared() -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt deleted file mode 100644 index 9e7ee26a8..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothCharacteristicViewModel.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bluetooth - -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.observable.toUninitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.base.utils.toHexString -import com.splendo.kaluga.bluetooth.Bluetooth -import com.splendo.kaluga.bluetooth.Characteristic -import com.splendo.kaluga.bluetooth.UUID -import com.splendo.kaluga.bluetooth.characteristics -import com.splendo.kaluga.bluetooth.descriptors -import com.splendo.kaluga.bluetooth.device.Identifier -import com.splendo.kaluga.bluetooth.get -import com.splendo.kaluga.bluetooth.services -import com.splendo.kaluga.bluetooth.uuidString -import com.splendo.kaluga.bluetooth.value -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class BluetoothCharacteristicViewModel(private val bluetooth: Bluetooth, private val deviceIdentifier: Identifier, private val serviceUUID: UUID, private val characteristicUUID: UUID) : BaseLifecycleViewModel() { - - private val characteristic: Flow get() = bluetooth.devices()[deviceIdentifier].services()[serviceUUID].characteristics()[characteristicUUID] - - val uuid = characteristicUUID.uuidString - val value = characteristic.value().map { it?.toHexString() ?: "" }.toUninitializedObservable(coroutineScope) - - private val _descriptors = MutableStateFlow(emptyList()) - val descriptors = _descriptors.toInitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - - scope.launch { characteristic.flatMapLatest { characteristic -> characteristic?.let { flowOf(it) } ?: emptyFlow() }.first().readValue() } - scope.launch { - characteristic.descriptors().map { descriptors -> descriptors.map { BluetoothDescriptorViewModel(bluetooth, deviceIdentifier, serviceUUID, characteristicUUID, it.uuid) } }.collect { - clearDescriptors() - _descriptors.value = it - } - } - } - - public override fun onCleared() { - super.onCleared() - clearDescriptors() - } - - private fun clearDescriptors() { - _descriptors.value.forEach { it.onCleared() } - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt deleted file mode 100644 index 513be4486..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDescriptorViewModel.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bluetooth - -import com.splendo.kaluga.architecture.observable.toUninitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.base.utils.toHexString -import com.splendo.kaluga.bluetooth.Bluetooth -import com.splendo.kaluga.bluetooth.Descriptor -import com.splendo.kaluga.bluetooth.UUID -import com.splendo.kaluga.bluetooth.characteristics -import com.splendo.kaluga.bluetooth.descriptors -import com.splendo.kaluga.bluetooth.device.Identifier -import com.splendo.kaluga.bluetooth.get -import com.splendo.kaluga.bluetooth.services -import com.splendo.kaluga.bluetooth.uuidString -import com.splendo.kaluga.bluetooth.value -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class BluetoothDescriptorViewModel(private val bluetooth: Bluetooth, private val deviceIdentifier: Identifier, private val serviceUUID: UUID, private val characteristicUUID: UUID, private val descriptorUUID: UUID) : BaseLifecycleViewModel() { - - private val descriptor: Flow get() = bluetooth.devices()[deviceIdentifier].services()[serviceUUID].characteristics()[characteristicUUID].descriptors()[descriptorUUID] - - val uuid = descriptorUUID.uuidString - val value = descriptor.value().map { it?.toHexString() ?: "" }.toUninitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - - scope.launch { - descriptor.flatMapLatest { descriptor -> descriptor?.let { flowOf(it) } ?: emptyFlow() }.first().readValue() - } - } - - public override fun onCleared() { - super.onCleared() - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt deleted file mode 100644 index ec7e7bb66..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothServiceViewModel.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bluetooth - -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.bluetooth.Bluetooth -import com.splendo.kaluga.bluetooth.Service -import com.splendo.kaluga.bluetooth.UUID -import com.splendo.kaluga.bluetooth.characteristics -import com.splendo.kaluga.bluetooth.device.Identifier -import com.splendo.kaluga.bluetooth.get -import com.splendo.kaluga.bluetooth.services -import com.splendo.kaluga.bluetooth.uuidString -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class BluetoothServiceViewModel(private val bluetooth: Bluetooth, private val deviceIdentifier: Identifier, private val serviceUUID: UUID) : BaseLifecycleViewModel() { - - private val service: Flow get() = bluetooth.devices()[deviceIdentifier].services()[serviceUUID] - - val uuid = serviceUUID.uuidString - private val _characteristics = MutableStateFlow(emptyList()) - val characteristics = _characteristics.toInitializedObservable(coroutineScope) - - override fun onResume(scope: CoroutineScope) { - super.onResume(scope) - - scope.launch { - service.characteristics().map { characteristics -> characteristics.map { BluetoothCharacteristicViewModel(bluetooth, deviceIdentifier, serviceUUID, it.uuid) } }.collect { - cleanCharacteristics() - _characteristics.value = it - } - } - } - - public override fun onCleared() { - super.onCleared() - cleanCharacteristics() - } - - private fun cleanCharacteristics() { - _characteristics.value.forEach { it.onCleared() } - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt deleted file mode 100644 index 0a72e95f4..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.splendo.kaluga.example.shared.viewmodel.datetimepicker - -import com.splendo.kaluga.architecture.observable.toUninitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.base.text.DateFormatStyle -import com.splendo.kaluga.base.text.KalugaDateFormatter -import com.splendo.kaluga.base.utils.DefaultKalugaDate -import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter -import com.splendo.kaluga.datetimepicker.buildDatePicker -import com.splendo.kaluga.datetimepicker.buildTimePicker -import com.splendo.kaluga.resources.localized -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -class DateTimePickerViewModel(val dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder) : BaseLifecycleViewModel() { - - companion object { - private val formatter = KalugaDateFormatter.dateTimeFormat(DateFormatStyle.Long, DateFormatStyle.Long) - } - - private val selectedDate = MutableStateFlow( - DefaultKalugaDate.now().apply { - second = 0 - millisecond = 0 - } - ) - val dateLabel = selectedDate.map { formatter.format(it) }.toUninitializedObservable(coroutineScope) - - fun onSelectDatePressed() { - coroutineScope.launch { - dateTimePickerPresenterBuilder.buildDatePicker( - this, - DefaultKalugaDate.epoch(), - DefaultKalugaDate.now() - ) { - setSelectedDate(this@DateTimePickerViewModel.selectedDate.value) - setCancelButtonTitle("cancel_selection".localized()) - setConfirmButtonTitle("confirm_selection".localized()) - }.show()?.let { - selectedDate.value = it - } - } - } - - fun onSelectTimePressed() { - coroutineScope.launch { - dateTimePickerPresenterBuilder.buildTimePicker(this) { - setSelectedDate(this@DateTimePickerViewModel.selectedDate.value) - setCancelButtonTitle("cancel_selection".localized()) - setConfirmButtonTitle("confirm_selection".localized()) - }.show()?.let { - selectedDate.value = it - } - } - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt deleted file mode 100644 index 08f4211b8..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.featureList - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class FeatureListNavigationAction : NavigationAction(null) { - object Location : FeatureListNavigationAction() - object Permissions : FeatureListNavigationAction() - object Alerts : FeatureListNavigationAction() - object DateTimePicker : FeatureListNavigationAction() - object LoadingIndicator : FeatureListNavigationAction() - object Architecture : FeatureListNavigationAction() - object Bluetooth : FeatureListNavigationAction() - object Keyboard : FeatureListNavigationAction() - object Links : FeatureListNavigationAction() - object System : FeatureListNavigationAction() - object Beacons : FeatureListNavigationAction() - object Resources : FeatureListNavigationAction() - object PlatformSpecific : FeatureListNavigationAction() -} - -sealed class Feature(val title: String) { - object Alerts : Feature("feature_alerts".localized()) - object Architecture : Feature("feature_architecture".localized()) - object Bluetooth : Feature("feature_bluetooth".localized()) - object DateTimePicker : Feature("feature_date_time_picker".localized()) - object Keyboard : Feature("feature_keyboard".localized()) - object LoadingIndicator : Feature("feature_hud".localized()) - object Location : Feature("feature_location".localized()) - object Permissions : Feature("feature_permissions".localized()) - object Links : Feature("feature_links".localized()) - object System : Feature("feature_system".localized()) - object Beacons : Feature("feature_beacons".localized()) - object Resource : Feature("feature_resources".localized()) - object PlatformSpecific : Feature("feature_platform_specific".localized()) -} - -class FeatureListViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val feature = observableOf( - listOfNotNull( - Feature.Alerts, - Feature.Architecture, - Feature.Bluetooth, - Feature.DateTimePicker, - Feature.Keyboard, - Feature.Links, - Feature.LoadingIndicator, - Feature.Location, - Feature.Permissions, - Feature.System, - Feature.Beacons, - Feature.Resource, - Feature.PlatformSpecific.takeIf { showPlatformSpecificFeatures } - ) - ) - - fun onFeaturePressed(feature: Feature) { - navigator.navigate( - when (feature) { - is Feature.Alerts -> FeatureListNavigationAction.Alerts - is Feature.Architecture -> FeatureListNavigationAction.Architecture - is Feature.Bluetooth -> FeatureListNavigationAction.Bluetooth - is Feature.DateTimePicker -> FeatureListNavigationAction.DateTimePicker - is Feature.Keyboard -> FeatureListNavigationAction.Keyboard - is Feature.Links -> FeatureListNavigationAction.Links - is Feature.LoadingIndicator -> FeatureListNavigationAction.LoadingIndicator - is Feature.Location -> FeatureListNavigationAction.Location - is Feature.Permissions -> FeatureListNavigationAction.Permissions - is Feature.System -> FeatureListNavigationAction.System - is Feature.Beacons -> FeatureListNavigationAction.Beacons - is Feature.Resource -> FeatureListNavigationAction.Resources - is Feature.PlatformSpecific -> FeatureListNavigationAction.PlatformSpecific - } - ) - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt deleted file mode 100644 index 3852fd997..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.featureList - -expect val showPlatformSpecificFeatures: Boolean diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt deleted file mode 100644 index c177e9aa4..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.keyboard - -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.keyboard.BaseKeyboardManager -import com.splendo.kaluga.keyboard.FocusHandler - -class KeyboardViewModel(keyboardManagerBuilder: BaseKeyboardManager.Builder, private val editFieldFocusHandler: FocusHandler) : BaseLifecycleViewModel() { - - private val keyboardManager: BaseKeyboardManager = keyboardManagerBuilder.create(coroutineScope) - - fun onShowPressed() { - keyboardManager.show(editFieldFocusHandler) - } - - fun onHidePressed() { - keyboardManager.hide() - } -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt deleted file mode 100644 index 82651eee9..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ButtonViewModel.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.resources - -import com.splendo.kaluga.alerts.AlertPresenter -import com.splendo.kaluga.alerts.buildAlert -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.example.shared.stylable.ButtonStyles -import com.splendo.kaluga.resources.StringStyleAttribute -import com.splendo.kaluga.resources.StyledStringBuilder -import com.splendo.kaluga.resources.stylable.ButtonStyle -import com.splendo.kaluga.resources.styled -import com.splendo.kaluga.resources.view.KalugaButton -import kotlinx.coroutines.launch - -class ButtonViewModel( - styledStringBuilderProvider: StyledStringBuilder.Provider, - val alertPresenterBuilder: AlertPresenter.Builder -) : BaseLifecycleViewModel() { - - val buttons = observableOf( - listOf( - "Text Button".toButton(ButtonStyles.textButton), - "Red Button".toButton(ButtonStyles.redButton), - "Rounded Button".toButton(ButtonStyles.roundedButton), - "Oval Button".toButton(ButtonStyles.ovalButton), - "Stroke Button".toButton(ButtonStyles.redButtonWithStroke), - "Rounded Stroke Button".toButton(ButtonStyles.roundedButtonWithStroke), - "Oval Stroke Button".toButton(ButtonStyles.ovalButtonWithStroke), - "Linear Gradient Button".toButton(ButtonStyles.linearGradientButton), - "Radial Gradient Button".toButton(ButtonStyles.radialGradientButton), - "Angular Gradient Button".toButton(ButtonStyles.angularGradientButton), - "Disabled Button".toButton(ButtonStyles.redButton, false), - listOf( - KalugaButton.Styled( - "Styled Button".styled( - styledStringBuilderProvider, - ButtonStyles.textButton.getStateTextStyle( - isEnabled = true, - isPressed = false - ), - StringStyleAttribute.CharacterStyleAttribute.Strikethrough - ), - ButtonStyles.textButton - ) { - showAlert("Styled Button") - } - ) - ).flatten() - ) - - private fun showAlert(message: String) { - coroutineScope.launch { - alertPresenterBuilder.buildAlert(this) { - setTitle("Button Pressed") - setMessage(message) - setNeutralButton("Ok") - }.show() - } - } - - private fun String.toButton( - style: ButtonStyle, - isEnabled: Boolean = true - ): List = listOf( - KalugaButton.Plain(this, style, isEnabled) { - showAlert(this) - } - ) -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt deleted file mode 100644 index bf184ac0f..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ColorViewModel.kt +++ /dev/null @@ -1,192 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.resources - -import com.splendo.kaluga.alerts.Alert -import com.splendo.kaluga.alerts.AlertPresenter -import com.splendo.kaluga.alerts.buildActionSheet -import com.splendo.kaluga.alerts.buildAlert -import com.splendo.kaluga.architecture.observable.InitializedObservable -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.example.shared.stylable.ButtonStyles -import com.splendo.kaluga.resources.DefaultColors -import com.splendo.kaluga.resources.KalugaColor -import com.splendo.kaluga.resources.burn -import com.splendo.kaluga.resources.colorBlend -import com.splendo.kaluga.resources.colorFrom -import com.splendo.kaluga.resources.darken -import com.splendo.kaluga.resources.darkenBy -import com.splendo.kaluga.resources.difference -import com.splendo.kaluga.resources.dodge -import com.splendo.kaluga.resources.exclude -import com.splendo.kaluga.resources.hardLight -import com.splendo.kaluga.resources.hexString -import com.splendo.kaluga.resources.hue -import com.splendo.kaluga.resources.lighten -import com.splendo.kaluga.resources.lightenBy -import com.splendo.kaluga.resources.luminate -import com.splendo.kaluga.resources.multiply -import com.splendo.kaluga.resources.normal -import com.splendo.kaluga.resources.overlay -import com.splendo.kaluga.resources.saturate -import com.splendo.kaluga.resources.screen -import com.splendo.kaluga.resources.softLight -import com.splendo.kaluga.resources.stylable.BackgroundStyle -import com.splendo.kaluga.resources.view.KalugaButton -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch - -enum class SelectableBlendMode { - NORMAL, - MULTIPLY, - SCREEN, - OVERLAY, - DARKEN, - LIGHTEN, - HARD_LIGHT, - SOFT_LIGHT, - DODGE, - BURN, - DIFFERENCE, - EXCLUSION, - HUE, - SATURATION, - LUMINATE, - COLOR_BLEND; -} - -class ColorViewModel( - val alertPresenterBuilder: AlertPresenter.Builder -) : BaseLifecycleViewModel() { - - private val backdropColor = MutableStateFlow(DefaultColors.mediumPurple) - private val sourceColor = MutableStateFlow(DefaultColors.darkCyan) - private val blendMode = MutableStateFlow(SelectableBlendMode.NORMAL) - private val blendedColor = combine(backdropColor, sourceColor, blendMode) { backdrop, source, blendMode -> - when (blendMode) { - SelectableBlendMode.NORMAL -> backdrop normal source - SelectableBlendMode.MULTIPLY -> backdrop multiply source - SelectableBlendMode.SCREEN -> backdrop screen source - SelectableBlendMode.OVERLAY -> backdrop overlay source - SelectableBlendMode.DARKEN -> backdrop darken source - SelectableBlendMode.LIGHTEN -> backdrop lighten source - SelectableBlendMode.HARD_LIGHT -> backdrop hardLight source - SelectableBlendMode.SOFT_LIGHT -> backdrop softLight source - SelectableBlendMode.DODGE -> backdrop dodge source - SelectableBlendMode.BURN -> backdrop burn source - SelectableBlendMode.DIFFERENCE -> backdrop difference source - SelectableBlendMode.EXCLUSION -> backdrop exclude source - SelectableBlendMode.HUE -> backdrop hue source - SelectableBlendMode.SATURATION -> backdrop saturate source - SelectableBlendMode.LUMINATE -> backdrop luminate source - SelectableBlendMode.COLOR_BLEND -> backdrop colorBlend source - } - }.stateIn(coroutineScope, SharingStarted.Lazily, DefaultColors.clear) - - val backdropColorBackground = backdropColor.backgroundStyleObservable - val sourceColorBackground = sourceColor.backgroundStyleObservable - val blendedColorBackground = blendedColor.backgroundStyleObservable - - val backdropText = backdropColor.map { it.hexString }.toInitializedObservable("", coroutineScope) - val sourceText = sourceColor.map { it.hexString }.toInitializedObservable("", coroutineScope) - - val lightenBackdrops = backdropColor.lightenList() - val darkenBackdrops = backdropColor.darkenList() - val lightenSource = sourceColor.lightenList() - val darkenSource = sourceColor.darkenList() - val lightenBlended = blendedColor.lightenList() - val darkenBlended = blendedColor.darkenList() - - private val steps = (1..10).map { it.toDouble() / 10.0 } - private fun Flow.lightenList() = map { color -> - steps.map { - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(color.lightenBy(it)) - ) - } - }.toInitializedObservable(emptyList(), coroutineScope) - private fun Flow.darkenList() = map { color -> - steps.map { - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(color.darkenBy(it)) - ) - } - }.toInitializedObservable(emptyList(), coroutineScope) - - fun submitBackdropText(backdropText: String) { - submitColorText(backdropText) { - backdropColor.value = it - } - } - - fun submitSourceText(sourceText: String) { - submitColorText(sourceText) { - sourceColor.value = it - } - } - - val blendModeButton = blendMode.map { currentlySelectedBlendMode -> - KalugaButton.Plain( - currentlySelectedBlendMode.name, - ButtonStyles.redButton - ) { - alertPresenterBuilder.buildActionSheet(coroutineScope) { - addActions( - SelectableBlendMode.values().map { selectableBlendMode -> - Alert.Action(selectableBlendMode.name) { - blendMode.value = selectableBlendMode - } - } - ) - }.showAsync() - } - }.toInitializedObservable(KalugaButton.Plain("", ButtonStyles.redButton, false) {}, coroutineScope) - - val flipButton: KalugaButton = KalugaButton.Plain( - "Flip", - ButtonStyles.textButton - ) { - val sourceColor = sourceColor.value - this.sourceColor.value = backdropColor.value - backdropColor.value = sourceColor - } - - private fun submitColorText(colorText: String, onColorParsed: (KalugaColor) -> Unit) { - colorFrom(colorText)?.let { onColorParsed(it) } ?: run { - coroutineScope.launch { - alertPresenterBuilder.buildAlert(coroutineScope) { - setTitle("Invalid color") - setMessage("The text submitted is not a color") - setNeutralButton("OK") - }.show() - } - } - } - - private val Flow.backgroundStyleObservable: InitializedObservable get() = map { - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(it) - ) - }.toInitializedObservable(BackgroundStyle(BackgroundStyle.FillStyle.Solid(DefaultColors.clear)), coroutineScope) -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt deleted file mode 100644 index 1e8fda99b..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/LabelViewModel.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.resources - -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.example.shared.stylable.TextStyles -import com.splendo.kaluga.resources.DefaultColors -import com.splendo.kaluga.resources.StringStyleAttribute -import com.splendo.kaluga.resources.StyledStringBuilder -import com.splendo.kaluga.resources.attributeSubstring -import com.splendo.kaluga.resources.defaultBoldFont -import com.splendo.kaluga.resources.stylable.TextAlignment -import com.splendo.kaluga.resources.styled -import com.splendo.kaluga.resources.view.KalugaLabel - -class LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider) : BaseLifecycleViewModel() { - - companion object { - val loremIpsumParagraph0 = listOf( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - "Etiam et est quis lectus bibendum tempor.", - "Interdum et malesuada fames ac ante ipsum primis in faucibus.", - "Donec convallis metus efficitur mattis accumsan.", - "Pellentesque efficitur elit felis, vitae dictum sem vehicula eu.", - "In hac habitasse platea dictumst. Donec sollicitudin eleifend dui, sit amet viverra ex sollicitudin nec.", - "Sed at faucibus orci.", - "Vivamus ultricies purus eu nisi blandit, nec ullamcorper felis facilisis.", - "Duis accumsan dolor dignissim massa malesuada, in blandit sapien vestibulum." - ).joinToString(" ") - val loremIpsumParagraph1 = listOf( - "Duis eget leo commodo, consequat felis sed, vehicula justo.", - "Morbi facilisis facilisis varius.", - "Donec efficitur pretium pharetra.", - "Praesent ut lorem commodo, efficitur lectus quis, dapibus augue.", - "Quisque sed maximus quam.", - "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse ullamcorper, augue id blandit lobortis, arcu enim pellentesque felis, quis ultrices urna nibh et nibh.", - "Donec et velit faucibus, luctus massa congue, ornare odio.", - "Vestibulum interdum nec metus at eleifend.", - "Mauris finibus magna eu dolor dignissim elementum at vitae enim.", - "Nunc posuere, enim eu hendrerit volutpat, sapien lacus blandit dolor, eu faucibus nisl ex sed enim.", - "Praesent in ligula eu nulla semper posuere sollicitudin et nunc." - ).joinToString(" ") - val loremIpsumParagraph2 = listOf( - "Aenean eget quam eu diam volutpat tincidunt.", - "Pellentesque maximus tristique mi, eget pellentesque risus volutpat et.", - "Duis id ultrices mi.", - "Praesent nec orci at nisl pulvinar lobortis ut sit amet purus.", - "In id fermentum mi.", - "Nunc vel nunc nisi.", - "Vestibulum malesuada ultricies dui a blandit." - ).joinToString(" ") - val loremIpsum = listOf(loremIpsumParagraph0, loremIpsumParagraph1, loremIpsumParagraph2).joinToString("\n") - } - - val labels = observableOf( - listOf( - KalugaLabel.Plain("Default Text", TextStyles.defaultText), - KalugaLabel.Plain("Title Text", TextStyles.defaultTitle), - KalugaLabel.Plain("Bold Text", TextStyles.defaultBoldText), - KalugaLabel.Plain("Italic Text", TextStyles.defaultItalicText), - KalugaLabel.Plain("Monospace Text", TextStyles.defaultMonospaceText), - KalugaLabel.Plain("Red Text", TextStyles.redText), - KalugaLabel.Plain("Opposite Aligned Text", TextStyles.oppositeText), - KalugaLabel.Styled("Foreground Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, StringStyleAttribute.CharacterStyleAttribute.ForegroundColor(DefaultColors.deepSkyBlue))), - KalugaLabel.Styled("Background Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, StringStyleAttribute.CharacterStyleAttribute.BackgroundColor(DefaultColors.deepSkyBlue))), - KalugaLabel.Styled("Partial Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.TextStyle(TextStyles.redText), IntRange(0, 13)) })), - KalugaLabel.Styled("Font Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Font(defaultBoldFont, 12.0f), IntRange(0, 10)) })), - KalugaLabel.Styled("Stroke Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Stroke(2.0f, DefaultColors.limeGreen), IntRange(0, 12)) })), - KalugaLabel.Styled("Subscript Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.SubScript, IntRange(0, 15)) })), - KalugaLabel.Styled("Superscript Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.SuperScript, IntRange(0, 17)) })), - KalugaLabel.Styled("Shadow Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Shadow(DefaultColors.dimGray, 2.0f, 2.0f, 5.0f), IntRange(0, 12)) })), - KalugaLabel.Styled("Kerning Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Kerning(0.08f), IntRange(0, 13)) })), - KalugaLabel.Styled("Underline Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Underline, IntRange(0, 15)) })), - KalugaLabel.Styled("Strikethrough Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Strikethrough, IntRange(0, 20)) })), - KalugaLabel.Styled("Link Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, DefaultColors.limeGreen, { Pair(StringStyleAttribute.Link("https://kaluga.splendo.com"), IntRange(0, 10)) })), - KalugaLabel.Styled(loremIpsum.styled(styledStringBuilderProvider, TextStyles.defaultText, StringStyleAttribute.ParagraphStyleAttribute.LineSpacing(5.0f, 10.0f, 6.0f))), - KalugaLabel.Styled( - loremIpsum.styled( - styledStringBuilderProvider, - TextStyles.defaultText, - { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent(10.0f)) } - ), - ), - KalugaLabel.Styled( - loremIpsum.styled( - styledStringBuilderProvider, - TextStyles.defaultText, - { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.Alignment(TextAlignment.END)) } - ), - ), - KalugaLabel.Styled( - loremIpsum.styled( - styledStringBuilderProvider, - TextStyles.defaultText, - { attributeSubstring(this, StringStyleAttribute.ParagraphStyleAttribute.LineSpacing(2.0f, 4.0f, 1.0f)) }, - { attributeSubstring(loremIpsumParagraph0, StringStyleAttribute.CharacterStyleAttribute.BackgroundColor(DefaultColors.deepSkyBlue)) }, - { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent(10.0f)) }, - { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.Alignment(TextAlignment.END)) }, - { attributeSubstring(loremIpsumParagraph2, StringStyleAttribute.CharacterStyleAttribute.Kerning(0.08f)) } - ), - ) - ) - ) -} diff --git a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt b/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt deleted file mode 100644 index 7162feb76..000000000 --- a/newexample/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ResourcesListViewModel.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.splendo.kaluga.example.shared.viewmodel.resources - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class ResourcesListNavigationAction : NavigationAction(null) { - - object Color : ResourcesListNavigationAction() - object Button : ResourcesListNavigationAction() - object Label : ResourcesListNavigationAction() -} - -enum class Resource(private val titleKey: String) { - COLOR("feature_resources_color"), - BUTTON("feature_resources_button"), - LABEL("feature_resources_label"); - - val title: String get() = titleKey.localized() -} - -class ResourcesListViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val resources = observableOf(Resource.values().toList()) - - fun onResourceSelected(resource: Resource) { - navigator.navigate( - when (resource) { - Resource.COLOR -> ResourcesListNavigationAction.Color - Resource.BUTTON -> ResourcesListNavigationAction.Button - Resource.LABEL -> ResourcesListNavigationAction.Label - } - ) - } -} diff --git a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt deleted file mode 100644 index f00fd3397..000000000 --- a/newexample/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.featureList - -actual val showPlatformSpecificFeatures: Boolean = false diff --git a/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt deleted file mode 100644 index f00fd3397..000000000 --- a/newexample/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.featureList - -actual val showPlatformSpecificFeatures: Boolean = false diff --git a/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt deleted file mode 100644 index f00fd3397..000000000 --- a/newexample/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.featureList - -actual val showPlatformSpecificFeatures: Boolean = false From f4c677a5b25a4acd4154b6411439a3d9afc84ef1 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 16 Nov 2022 17:44:10 +0100 Subject: [PATCH 030/227] Fix for iOS --- .../system/network/NetworkManager.kt | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt b/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt index 6db6c4028..0cb610db7 100644 --- a/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt +++ b/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt @@ -25,6 +25,7 @@ import kotlinx.cinterop.alloc import kotlinx.cinterop.asStableRef import kotlinx.cinterop.nativeHeap import kotlinx.cinterop.ptr +import kotlinx.cinterop.reinterpret import kotlinx.cinterop.staticCFunction import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -57,7 +58,16 @@ import platform.SystemConfiguration.SCNetworkReachabilitySetCallback import platform.SystemConfiguration.SCNetworkReachabilitySetDispatchQueue import platform.SystemConfiguration.kSCNetworkReachabilityFlagsIsWWAN import platform.SystemConfiguration.kSCNetworkReachabilityFlagsReachable +import platform.darwin.DISPATCH_QUEUE_PRIORITY_DEFAULT +import platform.darwin.DISPATCH_QUEUE_SERIAL +import platform.darwin.DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL import platform.darwin.dispatch_get_main_queue +import platform.darwin.dispatch_qos_class_t +import platform.darwin.dispatch_queue_attr_make_with_qos_class +import platform.darwin.dispatch_queue_attr_tVar +import platform.darwin.dispatch_queue_create +import platform.darwin.dispatch_queue_serial_t +import platform.posix.QOS_CLASS_UTILITY actual class DefaultNetworkManager internal constructor( private val appleNetworkManager: AppleNetworkManager @@ -84,20 +94,24 @@ actual class DefaultNetworkManager internal constructor( private val networkChannel = Channel(Channel.UNLIMITED) override val network: Flow = networkChannel.receiveAsFlow() + private val networkMonitor = object : nw_path_monitor_update_handler_t { + override fun invoke(network: nw_path_t) { + checkReachability(network) + } + } private val nwPathMonitor: nw_path_monitor_t = nw_path_monitor_create().apply { + val queue = dispatch_queue_create("com.splendo.kaluga.system.network", dispatch_queue_attr_make_with_qos_class( + null, + QOS_CLASS_UTILITY, + DISPATCH_QUEUE_PRIORITY_DEFAULT + )) nw_path_monitor_set_queue( this, - dispatch_get_main_queue() + queue ) nw_path_monitor_set_update_handler(this, networkMonitor) } - private val networkMonitor = object : nw_path_monitor_update_handler_t { - override fun invoke(network: nw_path_t) { - checkReachability(network) - } - } - override suspend fun startMonitoring() { nw_path_monitor_start(nwPathMonitor) } From 4f0c903bb1da9fa4a14017e1bdfff0b4f580ef11 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 18 Nov 2022 11:39:24 +0100 Subject: [PATCH 031/227] PR remarks --- DEVELOP.md | 18 ++++---- alerts/build.gradle.kts | 4 +- architecture-compose/build.gradle.kts | 6 +-- architecture/build.gradle.kts | 20 +++------ base-permissions/build.gradle.kts | 2 - base/build.gradle.kts | 11 ++--- bluetooth/build.gradle.kts | 4 +- build.gradle.kts | 6 --- calendar-permissions/build.gradle.kts | 2 - hud/build.gradle.kts | 4 +- kaluga-library-components/settings/gradle.kts | 30 ------------- .../src/main/kotlin/AndroidCommon.kt | 42 +++++++++---------- .../src/main/kotlin/AndroidCompose.kt | 10 ++--- .../src/main/kotlin/Component.kt | 14 +++---- .../src/main/kotlin/Dependencies.kt | 14 +++---- .../src/main/kotlin/Library.kt | 3 ++ keyboard-compose/build.gradle.kts | 6 +-- links/build.gradle.kts | 2 +- location-permissions/build.gradle.kts | 2 +- location/build.gradle.kts | 4 +- logging/build.gradle.kts | 8 ++-- resources-compose/build.gradle.kts | 10 ++--- resources/build.gradle.kts | 4 +- review/build.gradle.kts | 4 +- scientific/build.gradle.kts | 4 +- test-utils-base/build.gradle.kts | 10 ++--- test-utils-koin/build.gradle.kts | 3 +- 27 files changed, 99 insertions(+), 148 deletions(-) diff --git a/DEVELOP.md b/DEVELOP.md index fdca1ea6f..e454f6ce3 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -1,6 +1,6 @@ ## Build instructions -This project uses Android Studio. You might need a canary version at times. Check [gradle/ext.gradle](gradle/ext.gradle.kts) to see which Android gradle plugin is used, as this determines compatability with Android Studio versions. You can check the [Android Gradle plugin release notes](https://developer.android.com/studio/releases/gradle-plugin), the [Android Studio release notes](https://developer.android.com/studio/releases) and the [Android Studio Preview Release Updates](https://androidstudio.googleblog.com/) pages to see which version to use. +This project uses Android Studio. You might need a canary version at times. Check [`kaluga.androidGradlePluginVersion` in gradle.properties](gradle.properties) to see which Android gradle plugin is used, as this determines compatability with Android Studio versions. You can check the [Android Gradle plugin release notes](https://developer.android.com/studio/releases/gradle-plugin), the [Android Studio release notes](https://developer.android.com/studio/releases) and the [Android Studio Preview Release Updates](https://androidstudio.googleblog.com/) pages to see which version to use. You can also check the Kotlin version, and if needed align this with the Kotlin plugin version for Android Studio under `Android Studio` -> `Preferences` -> `Languages & Frameworks` -> `Kotlin`. @@ -20,7 +20,7 @@ If a purely common test does not work, you can make the test's class `open` or ` By extending `BaseTest` you can avoid a lot of boilerplate to make tests behave cross-platform (e.g. enableing the coroutines debugger). -There are specialized test classes in the [`test-utils`](test-utils/) module, e.g. for testings `Flows`, `ViewModel`s etc. +There are specialized test classes in the [`test-utils`](test-utils/) modules, e.g. for testings `Flows`, `ViewModel`s etc. ### Android tests @@ -39,7 +39,7 @@ Make sure you have the Simulator setup with a working target device. For now you The `ioTest` task supports the `--tests` flag like other Gradle tasks to filter which tests to run. -Note that for kaluga iOS tests run on a background thread, in order to have a properly working Main dispatchers (and align better with Android). How this works is described in [`test-utils`](test-utils/) +Note that for kaluga iOS tests run on a background thread, in order to have a properly working Main dispatchers (and align better with Android). How this works is described in [`test-utils-base`](test-utils-base/) ## Architecture @@ -84,14 +84,14 @@ If you want to publish with a specific version string, you can override this val kaluga.libraryVersion=0.1.0-special-build ``` -if this property is not set the version string is a combination of the version number, the current git branch (unless that branch is `master`, `main` or `develop`) and `-SNAPSHOT` (unless the `MAVEN_CENTRAL_RELEASE` environment variable is set to `true`). The exact implementation of this can be found in [gradle/gitBranch.gradle.kts](gradle/gitBranch.gradle.kts). +if this property is not set the version string is a combination of the base version number defined in [kaluga-library-components/src/main/kotlin/Library](kaluga-library-components/src/main/kotlin/Library]), the current git branch (unless that branch is `master`, `main` or `develop`) and `-SNAPSHOT` (unless the `MAVEN_CENTRAL_RELEASE` environment variable is set to `true`). The exact implementation of this can be found in [kaluga-library-components/src/main/kotlin/GitBranch.kt](kaluga-library-components/src/main/kotlin/GitBranch.kt). -For example if the version in `ext.gradle` is `1.1` and `feature/123_fix_bug` is the current branch the resulting version will be `1.1-feature-123_fix_bug-SNAPSHOT`. +For example if the base version is `1.1` and `feature/123_fix_bug` is the current branch the resulting version will be `1.1-feature-123_fix_bug-SNAPSHOT`. #### Local Testing Before doing any publishing, make sure that changes are working with the one available in [Nexus Repository Manager](`oss.sonatype.org`). -Just adding the following code inside the `local.properties` file you can test both Android and iOS example app in `kaluga/example/ios/Supporting\ Files`. +Just adding the following code inside the `local.properties` file you can test both Android and iOS example app in `kaluga/example/`. ``` kaluga.exampleEmbeddingMethod=composite kaluga.exampleMavenRepo=https://oss.sonatype.org/service/local/repositories/comsplendo-REPO_NUMBER/content/ @@ -133,10 +133,10 @@ Projects publishing to Sonatype's release repository need to be manually closed #### Increase version after publishing -In case this has not been done yet, bump the version at [gradle/ext.gradle](gradle/ext.gradle.kts) in the `develop` branch to start the next development iteration. +In case this has not been done yet, bump the base version at [kaluga-library-components/src/main/kotlin/Library](kaluga-library-components/src/main/kotlin/Library]) in the `develop` branch to start the next development iteration. -```sh -library_version = 'X.X.X' +```kotlin +private val baseVersion = "X.X.X" ``` ## Code conventions diff --git a/alerts/build.gradle.kts b/alerts/build.gradle.kts index 09602afd9..f469ee5a7 100644 --- a/alerts/build.gradle.kts +++ b/alerts/build.gradle.kts @@ -7,8 +7,8 @@ plugins { } dependencies { - implement(Dependencies.AndroidX.Fragment) - implementForTest(Dependencies.AndroidX.FragmentKtx) + implementationDependency(Dependencies.AndroidX.Fragment) + testImplementationDependency(Dependencies.AndroidX.FragmentKtx) } publishableComponent() diff --git a/architecture-compose/build.gradle.kts b/architecture-compose/build.gradle.kts index 1b311bbdb..78a53fcfa 100644 --- a/architecture-compose/build.gradle.kts +++ b/architecture-compose/build.gradle.kts @@ -29,7 +29,7 @@ composeAndroidComponent() dependencies { api(project(":base")) api(project(":architecture")) - implement(Dependencies.AndroidX.Compose.Material) - implement(Dependencies.AndroidX.Navigation.Compose) - implement(Dependencies.KotlinX.Coroutines.Core) + implementationDependency(Dependencies.AndroidX.Compose.Material) + implementationDependency(Dependencies.AndroidX.Navigation.Compose) + implementationDependency(Dependencies.KotlinX.Coroutines.Core) } diff --git a/architecture/build.gradle.kts b/architecture/build.gradle.kts index b539bf767..1a640ce64 100644 --- a/architecture/build.gradle.kts +++ b/architecture/build.gradle.kts @@ -10,28 +10,20 @@ plugins { publishableComponent() dependencies { - val ext = (gradle as ExtensionAware).extra - val kotlin_version: String by ext - val androidx_lifecycle_version: String by ext - val androidx_browser_version: String by ext - api("org.jetbrains.kotlin:kotlin-reflect:${Library.kotlinVersion}") - expose(Dependencies.AndroidX.Lifecycle.Runtime) - expose(Dependencies.AndroidX.Lifecycle.ViewModel) - expose(Dependencies.AndroidX.Lifecycle.LiveData) - implement(Dependencies.AndroidX.Browser) + apiDependency(Dependencies.AndroidX.Lifecycle.Runtime) + apiDependency(Dependencies.AndroidX.Lifecycle.ViewModel) + apiDependency(Dependencies.AndroidX.Lifecycle.LiveData) + implementationDependency(Dependencies.AndroidX.Browser) } kotlin { sourceSets { - val ext = (gradle as ExtensionAware).extra - val serialization_version: String by ext - getByName("commonMain") { dependencies { implementation(project(":base", "")) - expose(Dependencies.KotlinX.Serialization.Core) - expose(Dependencies.KotlinX.Serialization.Json) + apiDependency(Dependencies.KotlinX.Serialization.Core) + apiDependency(Dependencies.KotlinX.Serialization.Json) } } diff --git a/base-permissions/build.gradle.kts b/base-permissions/build.gradle.kts index 23ee29621..3abc214e3 100644 --- a/base-permissions/build.gradle.kts +++ b/base-permissions/build.gradle.kts @@ -11,8 +11,6 @@ publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":logging", "")) api(project(":base", "")) diff --git a/base/build.gradle.kts b/base/build.gradle.kts index f965cef72..3690f3367 100644 --- a/base/build.gradle.kts +++ b/base/build.gradle.kts @@ -10,16 +10,13 @@ publishableComponent() kotlin { sourceSets { - - val ext = (gradle as ExtensionAware).extra - getByName("commonMain") { dependencies { implementation(project(":logging", "")) - expose(Dependencies.Stately.Common) - expose(Dependencies.Stately.Isolate) - expose(Dependencies.Stately.IsoCollections) - expose(Dependencies.Stately.Concurrency) + apiDependency(Dependencies.Stately.Common) + apiDependency(Dependencies.Stately.Isolate) + apiDependency(Dependencies.Stately.IsoCollections) + apiDependency(Dependencies.Stately.Concurrency) } } getByName("jsMain") { diff --git a/bluetooth/build.gradle.kts b/bluetooth/build.gradle.kts index 837d47951..c4a447507 100644 --- a/bluetooth/build.gradle.kts +++ b/bluetooth/build.gradle.kts @@ -9,7 +9,7 @@ plugins { publishableComponent() dependencies { - implement(Dependencies.BLEScanner) + implementationDependency(Dependencies.BLEScanner) implementation(project(":location", "")) } @@ -18,7 +18,7 @@ kotlin { commonMain { dependencies { api(project(":bluetooth-permissions", "")) - implement(Dependencies.Stately.Concurrency) + implementationDependency(Dependencies.Stately.Concurrency) } } commonTest { diff --git a/build.gradle.kts b/build.gradle.kts index 8c7ea7583..717388183 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,12 +24,6 @@ plugins { id("org.jetbrains.kotlinx.binary-compatibility-validator") } -// TODO: To be removed once we will migrate to kotlin version 1.6.20 -// https://youtrack.jetbrains.com/issue/KT-49109#focus=Comments-27-5667134.0-0 -rootProject.plugins.withType() { - rootProject.extensions.getByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension::class.java).nodeVersion = "16.13.2" -} - allprojects { repositories { mavenCentral() diff --git a/calendar-permissions/build.gradle.kts b/calendar-permissions/build.gradle.kts index cbf121810..e149787d0 100644 --- a/calendar-permissions/build.gradle.kts +++ b/calendar-permissions/build.gradle.kts @@ -11,8 +11,6 @@ publishableComponent() kotlin { sourceSets { getByName("commonMain") { - val ext = (gradle as ExtensionAware).extra - dependencies { api(project(":base-permissions", "")) } diff --git a/hud/build.gradle.kts b/hud/build.gradle.kts index bc017b374..3b0898e46 100644 --- a/hud/build.gradle.kts +++ b/hud/build.gradle.kts @@ -9,8 +9,8 @@ plugins { publishableComponent() dependencies { - implement(Dependencies.AndroidX.Fragment) - implementForDebug(Dependencies.AndroidX.FragmentKtx) + implementationDependency(Dependencies.AndroidX.Fragment) + debugImplementationDependency(Dependencies.AndroidX.FragmentKtx) } kotlin { diff --git a/kaluga-library-components/settings/gradle.kts b/kaluga-library-components/settings/gradle.kts index 39e6b7b2a..ac8878b62 100644 --- a/kaluga-library-components/settings/gradle.kts +++ b/kaluga-library-components/settings/gradle.kts @@ -21,34 +21,4 @@ pluginManagement { gradlePluginPortal() google() } - - // resolutionStrategy { - // eachPlugin { - // - // val kalugaAndroidGradlePluginVersion = - // settings.extra["kaluga.androidGradlePluginVersion"] - // val kalugaKotlinVersion = settings.extra["kaluga.kotlinVersion"] - // val kalugaKtLintGradlePluginVersion = settings.extra["kaluga.ktLintGradlePluginVersion"] - // val kalugaGoogleServicesGradlePluginVersion = settings.extra["kaluga.googleServicesGradlePluginVersion"] - // val kalugaBinaryCompatibilityValidatorVersion = settings.extra["kaluga.binaryCompatibilityValidatorVersion"] - // - // when (requested.id.id) { - // "org.jetbrains.kotlin.multiplatform", - // "org.jetbrains.kotlin.plugin.serialization", - // "org.jetbrains.kotlin.android", - // "org.jetbrains.kotlin.kapt", - // -> useVersion("$kalugaKotlinVersion") - // "com.android.library", - // "com.android.application", - // -> useVersion("$kalugaAndroidGradlePluginVersion") - // "org.jlleitschuh.gradle.ktlint", - // "org.jlleitschuh.gradle.ktlint-idea", - // -> useVersion("$kalugaKtLintGradlePluginVersion") - // "com.google.gms:google-services" - // -> useVersion("com.google.gms:google-services:$kalugaGoogleServicesGradlePluginVersion") - // "org.jetbrains.kotlinx.binary-compatibility-validator" - // -> useVersion("$kalugaBinaryCompatibilityValidatorVersion") - // } - // } - // } } diff --git a/kaluga-library-components/src/main/kotlin/AndroidCommon.kt b/kaluga-library-components/src/main/kotlin/AndroidCommon.kt index 365e527c1..3691a83fd 100644 --- a/kaluga-library-components/src/main/kotlin/AndroidCommon.kt +++ b/kaluga-library-components/src/main/kotlin/AndroidCommon.kt @@ -25,27 +25,27 @@ fun org.gradle.api.Project.commonAndroidComponent(type: ComponentType = Componen } dependencies { - implement(Dependencies.KotlinX.Coroutines.Android) - implement(Dependencies.AndroidX.AppCompat) - - implementForTest(Dependencies.JUnit) - implementForTest(Dependencies.Mockito.Core) - implementForTest(Dependencies.Kotlin.Test) - implementForTest(Dependencies.Kotlin.JUnit) - - implementForAndroidTest(Dependencies.Mockito.Android) - implementForAndroidTest(Dependencies.ByteBuddy.Android) - implementForAndroidTest(Dependencies.ByteBuddy.Agent) - - implementForAndroidTest(Dependencies.AndroidX.Test.Core) - implementForAndroidTest(Dependencies.AndroidX.Test.CoreKtx) - implementForAndroidTest(Dependencies.AndroidX.Test.UIAutomator) - implementForAndroidTest(Dependencies.AndroidX.Test.Rules) - implementForAndroidTest(Dependencies.AndroidX.Test.JUnit) - implementForAndroidTest(Dependencies.AndroidX.Test.Runner) - implementForAndroidTest(Dependencies.AndroidX.Test.Espresso) - implementForAndroidTest(Dependencies.Kotlin.Test) - implementForAndroidTest(Dependencies.Kotlin.JUnit) + implementationDependency(Dependencies.KotlinX.Coroutines.Android) + implementationDependency(Dependencies.AndroidX.AppCompat) + + testImplementationDependency(Dependencies.JUnit) + testImplementationDependency(Dependencies.Mockito.Core) + testImplementationDependency(Dependencies.Kotlin.Test) + testImplementationDependency(Dependencies.Kotlin.JUnit) + + androidTestImplementationDependency(Dependencies.Mockito.Android) + androidTestImplementationDependency(Dependencies.ByteBuddy.Android) + androidTestImplementationDependency(Dependencies.ByteBuddy.Agent) + + androidTestImplementationDependency(Dependencies.AndroidX.Test.Core) + androidTestImplementationDependency(Dependencies.AndroidX.Test.CoreKtx) + androidTestImplementationDependency(Dependencies.AndroidX.Test.UIAutomator) + androidTestImplementationDependency(Dependencies.AndroidX.Test.Rules) + androidTestImplementationDependency(Dependencies.AndroidX.Test.JUnit) + androidTestImplementationDependency(Dependencies.AndroidX.Test.Runner) + androidTestImplementationDependency(Dependencies.AndroidX.Test.Espresso) + androidTestImplementationDependency(Dependencies.Kotlin.Test) + androidTestImplementationDependency(Dependencies.Kotlin.JUnit) } } diff --git a/kaluga-library-components/src/main/kotlin/AndroidCompose.kt b/kaluga-library-components/src/main/kotlin/AndroidCompose.kt index 2f3997033..1112b7c80 100644 --- a/kaluga-library-components/src/main/kotlin/AndroidCompose.kt +++ b/kaluga-library-components/src/main/kotlin/AndroidCompose.kt @@ -22,11 +22,11 @@ fun org.gradle.api.Project.composeAndroidComponent(type: ComponentType.Compose = version = Library.version commonAndroidComponent(type) dependencies { - implement(Dependencies.AndroidX.Compose.Foundation) - implement(Dependencies.AndroidX.Compose.UI) - implement(Dependencies.AndroidX.Compose.UITooling) - implement(Dependencies.AndroidX.Lifecycle.ViewModelCompose) - implement(Dependencies.AndroidX.Activity.Compose) + implementationDependency(Dependencies.AndroidX.Compose.Foundation) + implementationDependency(Dependencies.AndroidX.Compose.UI) + implementationDependency(Dependencies.AndroidX.Compose.UITooling) + implementationDependency(Dependencies.AndroidX.Lifecycle.ViewModelCompose) + implementationDependency(Dependencies.AndroidX.Activity.Compose) } kotlinAndroid { diff --git a/kaluga-library-components/src/main/kotlin/Component.kt b/kaluga-library-components/src/main/kotlin/Component.kt index f0899920e..3e49adf2e 100644 --- a/kaluga-library-components/src/main/kotlin/Component.kt +++ b/kaluga-library-components/src/main/kotlin/Component.kt @@ -125,7 +125,7 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent(currentProject: Pr val commonMain = sourceSets.getByName("commonMain").apply { dependencies { - implement(Dependencies.KotlinX.Coroutines.Core) + implementationDependency(Dependencies.KotlinX.Coroutines.Core) } } @@ -137,14 +137,14 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent(currentProject: Pr } } - val jvmMain = sourceSets.getByName("jvmMain").apply { + sourceSets.getByName("jvmMain").apply { dependencies { implementation(kotlin("stdlib")) - implement(Dependencies.KotlinX.Coroutines.Swing) + implementationDependency(Dependencies.KotlinX.Coroutines.Swing) } } - val jvmTest = sourceSets.getByName("jvmTest").apply { + sourceSets.getByName("jvmTest").apply { dependsOn(commonTest) dependencies { implementation(kotlin("test")) @@ -152,14 +152,14 @@ fun KotlinMultiplatformExtension.commonMultiplatformComponent(currentProject: Pr } } - val jsMain = sourceSets.getByName("jsMain").apply { + sourceSets.getByName("jsMain").apply { dependencies { implementation(kotlin("stdlib-js")) - implement(Dependencies.KotlinX.Coroutines.Js) + implementationDependency(Dependencies.KotlinX.Coroutines.Js) } } - val jsTest = sourceSets.getByName("jsTest").apply { + sourceSets.getByName("jsTest").apply { dependencies { implementation(kotlin("test-js")) } diff --git a/kaluga-library-components/src/main/kotlin/Dependencies.kt b/kaluga-library-components/src/main/kotlin/Dependencies.kt index 9548006d6..1723996fc 100644 --- a/kaluga-library-components/src/main/kotlin/Dependencies.kt +++ b/kaluga-library-components/src/main/kotlin/Dependencies.kt @@ -22,22 +22,22 @@ data class Dependency(private val group: String, private val name: String, priva val notation = "$group:$name${version?.let { ":$it" } ?: ""}" } -fun KotlinDependencyHandler.expose(dependency: Dependency) = api(dependency.notation) -fun KotlinDependencyHandler.implement(dependency: Dependency) = implementation(dependency.notation) +fun KotlinDependencyHandler.apiDependency(dependency: Dependency) = api(dependency.notation) +fun KotlinDependencyHandler.implementationDependency(dependency: Dependency) = implementation(dependency.notation) -fun DependencyHandler.expose(dependency: Dependency) { +fun DependencyHandler.apiDependency(dependency: Dependency) { add("api", dependency.notation) } -fun DependencyHandler.implement(dependency: Dependency) { +fun DependencyHandler.implementationDependency(dependency: Dependency) { add("implementation", dependency.notation) } -fun DependencyHandler.implementForDebug(dependency: Dependency) { +fun DependencyHandler.debugImplementationDependency(dependency: Dependency) { add("debugImplementation", dependency.notation) } -fun DependencyHandler.implementForTest(dependency: Dependency) { +fun DependencyHandler.testImplementationDependency(dependency: Dependency) { add("testImplementation", dependency.notation) } -fun DependencyHandler.implementForAndroidTest(dependency: Dependency) { +fun DependencyHandler.androidTestImplementationDependency(dependency: Dependency) { add("androidTestImplementation", dependency.notation) } diff --git a/kaluga-library-components/src/main/kotlin/Library.kt b/kaluga-library-components/src/main/kotlin/Library.kt index 72556cc8b..9aaac8215 100644 --- a/kaluga-library-components/src/main/kotlin/Library.kt +++ b/kaluga-library-components/src/main/kotlin/Library.kt @@ -25,6 +25,9 @@ import java.io.IOException private val libraries: MutableMap = mutableMapOf() +/** + * Gets a [Library] for the [Project]. Only creates a new instance of the Library if none exist yet, to speed up the build process + */ val Project.Library get() = libraries.getOrPut(this) { Library(this) } class Library(project: Project) { diff --git a/keyboard-compose/build.gradle.kts b/keyboard-compose/build.gradle.kts index 31e426241..d9d6d203f 100644 --- a/keyboard-compose/build.gradle.kts +++ b/keyboard-compose/build.gradle.kts @@ -11,7 +11,7 @@ composeAndroidComponent() dependencies { implementation(project(":base")) api(project(":keyboard")) - implement(Dependencies.AndroidX.Compose.UI) - implement(Dependencies.AndroidX.Compose.UITooling) - implement(Dependencies.KotlinX.Coroutines.Core) + implementationDependency(Dependencies.AndroidX.Compose.UI) + implementationDependency(Dependencies.AndroidX.Compose.UITooling) + implementationDependency(Dependencies.KotlinX.Coroutines.Core) } diff --git a/links/build.gradle.kts b/links/build.gradle.kts index 047717774..e89046cea 100644 --- a/links/build.gradle.kts +++ b/links/build.gradle.kts @@ -33,7 +33,7 @@ kotlin { implementation(project(":base", "")) implementation(project(":logging", "")) implementation(project(":architecture", "")) - expose(Dependencies.KotlinX.Serialization.Core) + apiDependency(Dependencies.KotlinX.Serialization.Core) } } commonTest { diff --git a/location-permissions/build.gradle.kts b/location-permissions/build.gradle.kts index 83a274afd..961396e82 100644 --- a/location-permissions/build.gradle.kts +++ b/location-permissions/build.gradle.kts @@ -9,7 +9,7 @@ plugins { publishableComponent() dependencies { - implement(Dependencies.Android.PlayServices.Location) + implementationDependency(Dependencies.Android.PlayServices.Location) } kotlin { diff --git a/location/build.gradle.kts b/location/build.gradle.kts index cd8654057..3ea167fac 100644 --- a/location/build.gradle.kts +++ b/location/build.gradle.kts @@ -9,8 +9,8 @@ plugins { publishableComponent() dependencies { - implement(Dependencies.Android.PlayServices.Location) - implement(Dependencies.KotlinX.Coroutines.PlayServices) + implementationDependency(Dependencies.Android.PlayServices.Location) + implementationDependency(Dependencies.KotlinX.Coroutines.PlayServices) } kotlin { diff --git a/logging/build.gradle.kts b/logging/build.gradle.kts index b06cc6050..48f7cc9f0 100644 --- a/logging/build.gradle.kts +++ b/logging/build.gradle.kts @@ -12,14 +12,14 @@ kotlin { sourceSets { commonMain { dependencies { - implement(Dependencies.Napier) - implement(Dependencies.Stately.Concurrency) + implementationDependency(Dependencies.Napier) + implementationDependency(Dependencies.Stately.Concurrency) } } commonTest { dependencies { - implement(Dependencies.Stately.Isolate) - implement(Dependencies.Stately.IsoCollections) + implementationDependency(Dependencies.Stately.Isolate) + implementationDependency(Dependencies.Stately.IsoCollections) api(project(":test-utils-base", "")) } } diff --git a/resources-compose/build.gradle.kts b/resources-compose/build.gradle.kts index ecc878677..aa6839737 100644 --- a/resources-compose/build.gradle.kts +++ b/resources-compose/build.gradle.kts @@ -28,9 +28,9 @@ composeAndroidComponent() dependencies { implementation(project(":base")) api(project(":resources")) - implement(Dependencies.AndroidX.Compose.Foundation) - implement(Dependencies.AndroidX.Compose.Material) - implement(Dependencies.AndroidX.Compose.UI) - implement(Dependencies.AndroidX.Compose.UITooling) - implement(Dependencies.KotlinX.Coroutines.Core) + implementationDependency(Dependencies.AndroidX.Compose.Foundation) + implementationDependency(Dependencies.AndroidX.Compose.Material) + implementationDependency(Dependencies.AndroidX.Compose.UI) + implementationDependency(Dependencies.AndroidX.Compose.UITooling) + implementationDependency(Dependencies.KotlinX.Coroutines.Core) } diff --git a/resources/build.gradle.kts b/resources/build.gradle.kts index 309e4eb81..a0094f6f5 100644 --- a/resources/build.gradle.kts +++ b/resources/build.gradle.kts @@ -15,8 +15,8 @@ kotlin { dependencies { implementation(project(":base", "")) implementation(project(":logging", "")) - expose(Dependencies.KotlinX.Serialization.Core) - expose(Dependencies.KotlinX.Serialization.Json) + apiDependency(Dependencies.KotlinX.Serialization.Core) + apiDependency(Dependencies.KotlinX.Serialization.Json) } } diff --git a/review/build.gradle.kts b/review/build.gradle.kts index 5c3b98ab1..587f4033f 100644 --- a/review/build.gradle.kts +++ b/review/build.gradle.kts @@ -9,8 +9,8 @@ plugins { publishableComponent() dependencies { - implement(Dependencies.Android.Play.Core) - implement(Dependencies.Android.Play.CoreKtx) + implementationDependency(Dependencies.Android.Play.Core) + implementationDependency(Dependencies.Android.Play.CoreKtx) } kotlin { diff --git a/scientific/build.gradle.kts b/scientific/build.gradle.kts index 2cd1a9c5c..9aeceeeb9 100644 --- a/scientific/build.gradle.kts +++ b/scientific/build.gradle.kts @@ -14,8 +14,8 @@ kotlin { commonMain { dependencies { implementation(project(":base")) - expose(Dependencies.KotlinX.Serialization.Core) - expose(Dependencies.KotlinX.Serialization.Json) + apiDependency(Dependencies.KotlinX.Serialization.Core) + apiDependency(Dependencies.KotlinX.Serialization.Json) } } commonTest { diff --git a/test-utils-base/build.gradle.kts b/test-utils-base/build.gradle.kts index 4e93e3911..b030465c3 100644 --- a/test-utils-base/build.gradle.kts +++ b/test-utils-base/build.gradle.kts @@ -9,7 +9,7 @@ plugins { publishableComponent() dependencies { - expose(Dependencies.AndroidX.ArchCore) + apiDependency(Dependencies.AndroidX.ArchCore) } kotlin { @@ -38,8 +38,8 @@ kotlin { getByName("jvmMain") { dependencies { - expose(Dependencies.KotlinX.Coroutines.Test) - expose(Dependencies.KotlinX.Coroutines.Debug) + apiDependency(Dependencies.KotlinX.Coroutines.Test) + apiDependency(Dependencies.KotlinX.Coroutines.Debug) } } } @@ -47,7 +47,7 @@ kotlin { android { dependencies { - expose(Dependencies.KotlinX.Coroutines.Test) - expose(Dependencies.KotlinX.Coroutines.Debug) + apiDependency(Dependencies.KotlinX.Coroutines.Test) + apiDependency(Dependencies.KotlinX.Coroutines.Debug) } } diff --git a/test-utils-koin/build.gradle.kts b/test-utils-koin/build.gradle.kts index 90c147628..a68d2b538 100644 --- a/test-utils-koin/build.gradle.kts +++ b/test-utils-koin/build.gradle.kts @@ -11,11 +11,10 @@ publishableComponent() kotlin { sourceSets { commonMain { - val ext = (gradle as ExtensionAware).extra dependencies { api(project(":test-utils-base")) api(project(":test-utils-architecture")) - expose(Dependencies.Koin.Core) + apiDependency(Dependencies.Koin.Core) } } commonTest { From 580422e49bab638612507112f5c78c370715976b Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 18 Nov 2022 12:22:36 +0100 Subject: [PATCH 032/227] Lint Fixes --- example/android/build.gradle.kts | 3 +-- .../architecture/ArchitectureDetailsActivity.kt | 2 +- .../architecture/ArchitectureInputActivity.kt | 1 - .../permissions/PermissionsDemoActivity.kt | 8 -------- .../platformspecific/PlatformSpecificActivity.kt | 6 ++---- .../kaluga/example/system/NetworkActivity.kt | 3 +-- .../example/shared/di/DependencyInjection.kt | 16 ++++++++-------- .../bluetooth/BluetoothDeviceDetailViewModel.kt | 2 -- .../bluetooth/BluetoothListDeviceViewModel.kt | 7 +++---- .../shared/viewmodel/link/LinksViewModel.kt | 5 ----- .../example/shared/di/DependencyInjection.kt | 4 ++-- .../viewmodel/permissions/NotificationOptions.kt | 2 +- 12 files changed, 19 insertions(+), 40 deletions(-) diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts index f412f6a3c..3f8678faf 100644 --- a/example/android/build.gradle.kts +++ b/example/android/build.gradle.kts @@ -6,7 +6,6 @@ plugins { id("org.jlleitschuh.gradle.ktlint") } - group = Library.group version = Library.version @@ -96,4 +95,4 @@ dependencies { implementationDependency(Dependencies.KotlinX.Serialization.Core) implementationDependency(Dependencies.KotlinX.Serialization.Json) -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt index 58cffa8c6..241d9bb64 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt @@ -21,8 +21,8 @@ package com.splendo.kaluga.example.architecture import android.os.Bundle import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.databinding.ActivityArchitectureDetailsBinding import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt index 3c131451e..6b9e74d07 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt @@ -25,7 +25,6 @@ import androidx.activity.result.contract.ActivityResultContract import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.databinding.ActivityArchitectureInputBinding diff --git a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt index aee8b70c2..0738ec653 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt @@ -28,14 +28,6 @@ import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel -import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission -import com.splendo.kaluga.permissions.calendar.CalendarPermission -import com.splendo.kaluga.permissions.camera.CameraPermission -import com.splendo.kaluga.permissions.contacts.ContactsPermission -import com.splendo.kaluga.permissions.location.LocationPermission -import com.splendo.kaluga.permissions.microphone.MicrophonePermission -import com.splendo.kaluga.permissions.notifications.NotificationsPermission -import com.splendo.kaluga.permissions.storage.StoragePermission import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt index e47f80400..210183171 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt @@ -51,10 +51,8 @@ class PlatformSpecificActivity : KalugaViewModelComposeActivity NavigationSpec.Activity( - ContactsActivity::class.java) - is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity( - BottomSheetActivity::class.java) + is PlatformFeatureListNavigationAction.ComposeNavigation -> NavigationSpec.Activity() + is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity() } @Composable diff --git a/example/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt index 51d96e51f..127e4494b 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt @@ -35,5 +35,4 @@ class NetworkActivity : KalugaViewModelActivity(R.layout.activ binding.viewModel = viewModel setContentView(binding.root) } - -} \ No newline at end of file +} diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt index 13c160b79..eb42b05d7 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -47,7 +47,8 @@ private fun sharedModule( ) = module { single { RestrictedLogger(RestrictedLogLevel.None) } single { PermissionsBuilder() } - single { locationStateRepoBuilderBuilder { + single { + locationStateRepoBuilderBuilder { val builder = get() builder.registerLocationPermissionIfNotRegistered( settings = BasePermissionManager.Settings(logger = get()) @@ -57,13 +58,12 @@ private fun sharedModule( } single { bluetoothBuilderBuilder { - val builder = get() - val settings = BasePermissionManager.Settings(logger = get()) - builder.registerBluetoothPermissionIfNotRegistered(settings = settings) - builder.registerLocationPermissionIfNotRegistered(settings = settings) - Permissions(builder, it) - } - .create( + val builder = get() + val settings = BasePermissionManager.Settings(logger = get()) + builder.registerBluetoothPermissionIfNotRegistered(settings = settings) + builder.registerLocationPermissionIfNotRegistered(settings = settings) + Permissions(builder, it) + }.create( scannerSettingsBuilder = { BaseScanner.Settings(it, logger = get()) }, connectionSettings = ConnectionSettings(logger = get()) ) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt index 2fd53abd0..fb46dfd0c 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt @@ -43,8 +43,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor import org.koin.core.component.KoinComponent import org.koin.core.component.inject diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt index 2eccab381..6dc37ecdb 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt @@ -18,7 +18,6 @@ package com.splendo.kaluga.example.shared.viewmodel.bluetooth import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.toBundle import com.splendo.kaluga.architecture.observable.UninitializedObservable import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.observable.toUninitializedObservable @@ -27,13 +26,13 @@ import com.splendo.kaluga.base.text.format import com.splendo.kaluga.base.utils.toHexString import com.splendo.kaluga.bluetooth.Bluetooth import com.splendo.kaluga.bluetooth.UUID -import com.splendo.kaluga.bluetooth.connect import com.splendo.kaluga.bluetooth.advertisement +import com.splendo.kaluga.bluetooth.connect import com.splendo.kaluga.bluetooth.device.BaseAdvertisementData -import com.splendo.kaluga.bluetooth.device.DeviceState import com.splendo.kaluga.bluetooth.device.ConnectableDeviceState -import com.splendo.kaluga.bluetooth.device.NotConnectableDeviceState +import com.splendo.kaluga.bluetooth.device.DeviceState import com.splendo.kaluga.bluetooth.device.Identifier +import com.splendo.kaluga.bluetooth.device.NotConnectableDeviceState import com.splendo.kaluga.bluetooth.device.stringValue import com.splendo.kaluga.bluetooth.disconnect import com.splendo.kaluga.bluetooth.get diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt index a2eb9544d..9f503c379 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt @@ -20,14 +20,9 @@ package com.splendo.kaluga.example.shared.viewmodel.link import com.splendo.kaluga.alerts.Alert import com.splendo.kaluga.alerts.AlertPresenter import com.splendo.kaluga.alerts.buildAlert -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundle -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.navigation.toBundle import com.splendo.kaluga.architecture.observable.observableOf import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel import com.splendo.kaluga.links.LinksBuilder diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt index ab8e1f8b1..886a84ce3 100644 --- a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -23,7 +23,7 @@ import org.koin.core.module.Module import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.module -internal val iosModule = module { } +internal val iosModule = module { } fun initKoin(customModules: List = emptyList()) = initKoin( iosModule, @@ -33,4 +33,4 @@ fun initKoin(customModules: List = emptyList()) = initKoin( ) internal actual val appDeclaration: KoinAppDeclaration = { -} \ No newline at end of file +} diff --git a/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt b/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt index 1ddf8c61b..5c0a64d55 100644 --- a/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt +++ b/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationOptions.kt @@ -18,4 +18,4 @@ package com.splendo.kaluga.example.shared.viewmodel.permissions import com.splendo.kaluga.permissions.notifications.NotificationOptions -actual val notificationOptions = NotificationOptions() \ No newline at end of file +actual val notificationOptions = NotificationOptions() From 7e073c98465b81187c443c605977ba937e7e19f1 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 21 Nov 2022 17:09:29 +0100 Subject: [PATCH 033/227] Adding compose for architecture --- .../navigation/ModalBottomSheetNavigator.kt | 7 +- .../architecture/compose/navigation/Route.kt | 31 ++++- .../compose/navigation/RouteNavigator.kt | 24 +++- example/android/build.gradle.kts | 1 + example/android/src/main/AndroidManifest.xml | 4 +- .../splendo/kaluga/example/ExampleActivity.kt | 2 + .../ArchitectureActivity.kt} | 21 +-- .../ArchitectureDetailsActivity.kt | 15 +- .../compose/ArchitectureDetailsLayout.kt} | 24 ++-- .../compose/ArchitectureLayout.kt | 128 ++++++++++++++++++ .../compose}/BottomSheetLayout.kt | 18 ++- .../compose}/BottomSheetSubPageLayout.kt | 9 +- .../compose}/NavigationMappers.kt | 21 +-- .../bottomSheet/ui/BottomSheetParentLayout.kt | 124 ----------------- .../Constants.kt} | 21 +-- .../contacts/ui/ContactDetailsLayout.kt | 97 ------------- .../example/contacts/ui/ContactsLayout.kt | 53 -------- .../example/contacts/ui/ContactsListLayout.kt | 100 -------------- .../kaluga/example/contacts/ui/Styles.kt | 17 --- .../contacts/viewModel/NavigationMappers.kt | 52 ------- .../{ => featurelist}/FeaturesListFragment.kt | 33 +++-- .../kaluga/example/{ => info}/InfoFragment.kt | 26 ++-- .../location/LocationBackgroundService.kt | 1 + .../PlatformSpecificActivity.kt | 88 ------------ .../layout/activity_architecture_details.xml | 10 ++ .../layout/activity_architecture_input.xml | 20 ++- .../android/src/main/res/values/strings.xml | 3 + .../ios/Demo/Base.lproj/Localizable.strings | 2 + .../example/shared/di/DependencyInjection.kt | 12 +- .../ComposeOrAndroidUISelectionViewModel.kt | 56 ++++++++ .../viewmodel/featureList/PlatformSpecific.kt | 39 +----- .../example/shared/stylable/ButtonStyles.kt | 10 ++ .../ArchitectureDetailsViewModel.kt | 18 ++- ...tViewModel.kt => ArchitectureViewModel.kt} | 29 ++-- .../BottomSheetSubPageViewModel.kt | 2 +- .../BottomSheetViewModel.kt | 2 +- .../BottomSheetParentSubPageViewModel.kt | 36 ----- .../bottomsheet/BottomSheetParentViewModel.kt | 42 ------ .../contacts/ContactDetailsViewModel.kt | 48 ------- .../contacts/ContactsListViewModel.kt | 60 -------- .../ui/SwiftUIOrUIKitSelectionViewModel.kt | 56 ++++++++ .../src/main/kotlin/AndroidCommon.kt | 16 ++- .../src/main/kotlin/AndroidDatabinding.kt | 26 ++++ .../src/main/kotlin/Component.kt | 1 + .../src/main/kotlin/Publish.kt | 3 +- .../build.gradle.kts | 25 ++-- .../androidLibAndroidTest/AndroidManifest.xml | 6 + .../kotlin/TestActivity.kt | 27 ++++ .../src/androidLibMain/AndroidManifest.xml | 2 + .../src/androidLibMain/kotlin/Binding.kt | 107 +++++++++++++++ settings.gradle.kts | 1 + 51 files changed, 675 insertions(+), 901 deletions(-) rename example/android/src/main/java/com/splendo/kaluga/example/architecture/{ArchitectureInputActivity.kt => androidui/ArchitectureActivity.kt} (76%) rename example/android/src/main/java/com/splendo/kaluga/example/architecture/{ => androidui}/ArchitectureDetailsActivity.kt (80%) rename example/android/src/main/java/com/splendo/kaluga/example/{bottomSheet/ui/BottomSheetParentSubPageLayout.kt => architecture/compose/ArchitectureDetailsLayout.kt} (67%) create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt rename example/android/src/main/java/com/splendo/kaluga/example/{bottomSheet/ui => architecture/compose}/BottomSheetLayout.kt (84%) rename example/android/src/main/java/com/splendo/kaluga/example/{bottomSheet/ui => architecture/compose}/BottomSheetSubPageLayout.kt (89%) rename example/android/src/main/java/com/splendo/kaluga/example/{bottomSheet/viewModel => architecture/compose}/NavigationMappers.kt (64%) delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt rename example/android/src/main/java/com/splendo/kaluga/example/{contacts/ContactsActivity.kt => compose/Constants.kt} (58%) delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt rename example/android/src/main/java/com/splendo/kaluga/example/{ => featurelist}/FeaturesListFragment.kt (83%) rename example/android/src/main/java/com/splendo/kaluga/example/{ => info}/InfoFragment.kt (84%) delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt create mode 100644 example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrAndroidUISelectionViewModel.kt rename example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/{ArchitectureInputViewModel.kt => ArchitectureViewModel.kt} (65%) rename example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/{bottomsheet => architecture}/BottomSheetSubPageViewModel.kt (95%) rename example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/{bottomsheet => architecture}/BottomSheetViewModel.kt (95%) delete mode 100644 example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt delete mode 100644 example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt create mode 100644 example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ui/SwiftUIOrUIKitSelectionViewModel.kt create mode 100644 kaluga-library-components/src/main/kotlin/AndroidDatabinding.kt rename example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt => resources-databinding/build.gradle.kts (51%) create mode 100644 resources-databinding/src/androidLibAndroidTest/AndroidManifest.xml create mode 100644 resources-databinding/src/androidLibAndroidTest/kotlin/TestActivity.kt create mode 100644 resources-databinding/src/androidLibMain/AndroidManifest.xml create mode 100644 resources-databinding/src/androidLibMain/kotlin/Binding.kt diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt index 5824e9590..b0296905c 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt @@ -78,7 +78,12 @@ class BottomSheetSheetContentRouteController( sheetContentRouteController.navigate(newRoute) } - override fun back(): Boolean = navHostController.popBackStack() || kotlin.run { + override fun back(result: Map): Boolean = if (navHostController.backQueue.isNotEmpty()) { + navHostController.previousBackStackEntry?.savedStateHandle?.let { savedStateHandle -> + result.entries.forEach { (key, value) -> savedStateHandle[key] = value } + } + navHostController.popBackStack() + } else { close() true } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt index 2de890d34..934d5f3c2 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt @@ -17,6 +17,7 @@ package com.splendo.kaluga.architecture.compose.navigation +import androidx.activity.result.ActivityResultLauncher import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow @@ -168,6 +169,11 @@ private val NavigationBundleValue<*>.routeArgument: String? */ sealed class Route { + companion object { + val Back = Back(emptyMap()) + val PopToRoot = PopToRoot(emptyMap()) + } + /** * Route that navigates to a new screen using a [NavigationAction] */ @@ -201,10 +207,12 @@ sealed class Route { /** * Route that navigates back to the screen associated with a [NavigationAction] - * @param routeAction The [Action] associated with the screen to navigate back to + * @param routeAction The [Action] associated with the screen to navigate back to. + * @param result A map of values to store in the [androidx.lifecycle.SavedStateHandle] of the screen to navigate back to. */ data class PopTo, Action : NavigationAction>( - override val routeAction: Action + override val routeAction: Action, + val result: Map = emptyMap() ) : Navigate() /** @@ -225,18 +233,33 @@ sealed class Route { /** * Navigates back one step in the hierarchy + * @param result A map of values to store in the [androidx.lifecycle.SavedStateHandle] of the previous screen. */ - object Back : Route() + data class Back(val result: Map = emptyMap()) : Route() /** * Navigates to the Root of a navigation stack + * @param result A map of values to store in the [androidx.lifecycle.SavedStateHandle] of the root screen. */ - object PopToRoot : Route() + data class PopToRoot(val result: Map = emptyMap()) : Route() /** * Closes all screens in the navigation stack */ object Close : Route() + + /** + * Navigates using an [ActivityResultLauncher] and a valid [input]. + * @param activityResultLauncher The launcher to launch with. This should have been created using [androidx.activity.compose.rememberLauncherForActivityResult] + * @param input The input to be provided to [activityResultLauncher] + */ + data class Launcher(val activityResultLauncher: ActivityResultLauncher, val input: I) : Route() { + + /** + * Launches the [activityResultLauncher] with [input] + */ + fun launch() = activityResultLauncher.launch(input) + } } /** diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt index 9ad98e38a..97be678cc 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt @@ -24,7 +24,7 @@ sealed interface RouteController { /** * Navigates back */ - fun back(): Boolean + fun back(result: Map = emptyMap()): Boolean /** * Closes this RouteController @@ -59,18 +59,34 @@ class NavHostRouteController( navHostController.navigate(newRoute.route) } is Route.PopTo<*, *> -> { + navHostController.getBackStackEntry(newRoute.route).savedStateHandle.let { savedStateHandle -> + newRoute.result.entries.forEach { (key, value) -> savedStateHandle[key] = value } + } navHostController.popBackStack(newRoute.route, false) } is Route.PopToIncluding<*, *> -> { navHostController.popBackStack(newRoute.route, true) } - is Route.Back -> back() - is Route.PopToRoot -> navHostController.popBackStack(ROOT_VIEW, false) + is Route.Back -> back(newRoute.result) + is Route.PopToRoot -> { + navHostController.getBackStackEntry(ROOT_VIEW).savedStateHandle.let { savedStateHandle -> + newRoute.result.entries.forEach { (key, value) -> savedStateHandle[key] = value } + } + navHostController.popBackStack(ROOT_VIEW, false) + } is Route.Close -> close() + is Route.Launcher<*> -> newRoute.launch() + } + } + + override fun back(result: Map): Boolean = if (navHostController.backQueue.isNotEmpty()){ + navHostController.previousBackStackEntry?.savedStateHandle?.let { savedStateHandle -> + result.entries.forEach { (key, value) -> savedStateHandle[key] = value } } + navHostController.popBackStack() } + else { parentRouteController?.back(result) ?: false } - override fun back(): Boolean = navHostController.popBackStack() || parentRouteController?.back() ?: false override fun close() { navHostController.popBackStack(ROOT_VIEW, true) parentRouteController?.close() diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts index 3f8678faf..8ade69aeb 100644 --- a/example/android/build.gradle.kts +++ b/example/android/build.gradle.kts @@ -75,6 +75,7 @@ dependencies { val libraryVersion = Library.version implementation("com.splendo.kaluga:architecture-compose:$libraryVersion") implementation("com.splendo.kaluga:resources-compose:$libraryVersion") + implementation("com.splendo.kaluga:resources-databinding:$libraryVersion") implementation(project(":shared")) implementationDependency(Dependencies.AndroidX.Compose.UI) diff --git a/example/android/src/main/AndroidManifest.xml b/example/android/src/main/AndroidManifest.xml index 2cd6b95de..b9c064c37 100644 --- a/example/android/src/main/AndroidManifest.xml +++ b/example/android/src/main/AndroidManifest.xml @@ -79,7 +79,7 @@ android:pathPrefix="/kalugaexample"/> - - diff --git a/example/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt index 2145a4a03..935054c51 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/ExampleActivity.kt @@ -23,6 +23,8 @@ import com.google.android.material.tabs.TabLayout import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.featurelist.FeaturesListFragment +import com.splendo.kaluga.example.info.InfoFragment import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel import org.koin.androidx.viewmodel.ext.android.viewModel diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureActivity.kt similarity index 76% rename from example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureActivity.kt index 6b9e74d07..c5d3e8dd1 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureInputActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureActivity.kt @@ -16,7 +16,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.architecture +package com.splendo.kaluga.example.architecture.androidui import android.content.Context import android.content.Intent @@ -28,13 +28,13 @@ import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.databinding.ActivityArchitectureInputBinding -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails -import com.splendo.kaluga.example.shared.viewmodel.architecture.InputNavigation import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class ArchitectureInputActivity : KalugaViewModelActivity() { +class ArchitectureActivity : KalugaViewModelActivity() { inner class Contract : ActivityResultContract() { @@ -46,12 +46,15 @@ class ArchitectureInputActivity : KalugaViewModelActivity { - NavigationSpec.Activity( - launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } - ) + ActivityNavigator> { action -> + when (action) { + is ArchitectureNavigationAction.Details -> NavigationSpec.Activity( + launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } + ) + is ArchitectureNavigationAction.BottomSheet -> throw java.lang.RuntimeException() + } } ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureDetailsActivity.kt similarity index 80% rename from example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureDetailsActivity.kt index 241d9bb64..b43381841 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureDetailsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureDetailsActivity.kt @@ -16,7 +16,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.architecture +package com.splendo.kaluga.example.architecture.androidui import android.os.Bundle import com.splendo.kaluga.architecture.navigation.ActivityNavigator @@ -25,8 +25,8 @@ import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.databinding.ActivityArchitectureDetailsBinding +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsNavigationAction import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.CloseDetailsNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -42,8 +42,13 @@ class ArchitectureDetailsActivity : KalugaViewModelActivity parametersOf( details, - ActivityNavigator { - NavigationSpec.Close(resultCode) + ActivityNavigator> { action -> + when (action) { + is ArchitectureDetailsNavigationAction.Close -> NavigationSpec.Close() + is ArchitectureDetailsNavigationAction.FinishWithDetails -> NavigationSpec.Close( + resultCode + ) + } } ) } ?: parametersOf("", 0) @@ -59,6 +64,6 @@ class ArchitectureDetailsActivity : KalugaViewModelActivity + composable(ArchitectureNavigationAction.BottomSheet.route()) { + BottomSheetLayout(contentNavHostController, sheetContentNavHostController, sheetState) + } + composable(BottomSheetNavigation.SubPage.route()) { + BottomSheetSubPageLayout( + contentNavHostController, sheetContentNavHostController, sheetState + ) + } + }, + contentRoot = { contentNavHostController, sheetContentNavHostController, sheetState -> + ArchitectureLayoutLayoutContent(contentNavHostController, sheetContentNavHostController, sheetState) + }, + content = { contentNavHostController, _, _ -> + composable( + type = NavigationBundleSpecType.SerializedType(InputDetails.serializer()) + ) { inputDetails -> + ArchitectureDetailsLayout(inputDetails, contentNavHostController) + } + } + ) + } +} + +@Composable +fun ArchitectureLayoutLayoutContent(contentNavHostController: NavHostController, sheetNavHostController: NavHostController, sheetState: ModalBottomSheetState) { + + val navigator = ModalBottomSheetNavigator( + NavHostRouteController(contentNavHostController), + BottomSheetSheetContentRouteController( + sheetNavHostController, + sheetState, + rememberCoroutineScope() + ), + ::architectureNavigationRouteMapper + ) + + val viewModel = storeAndRemember { + ArchitectureViewModel(navigator) + } + + ViewModelComposable(viewModel) { + val nameInput = viewModel.nameInput.mutableState() + val isNameValid by viewModel.isNameValid.state() + val numberInput = viewModel.numberInput.mutableState() + val isNumberValid by viewModel.isNumberValid.state() + + Column(Modifier.fillMaxWidth()) { + viewModel.showDetailsButton.Composable(modifier = Modifier.fillMaxWidth()) + } + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt similarity index 84% rename from example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index 7b77eb5f9..9db22a4e9 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt @@ -24,17 +24,17 @@ import androidx.compose.material.Button import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetNavigationRouteMapper -import com.splendo.kaluga.example.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetViewModel +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel @Composable fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { @@ -46,10 +46,8 @@ fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentN ::bottomSheetNavigationRouteMapper, ) - val viewModel = store { - remember { - BottomSheetViewModel(navigator) - } + val viewModel = storeAndRemember { + BottomSheetViewModel(navigator) } ViewModelComposable(viewModel) { @@ -57,13 +55,13 @@ fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentN Column( Modifier .fillMaxWidth() - .padding(Padding.default) + .padding(Constants.Padding.default) ) { Text(text) Button( modifier = Modifier .fillMaxWidth() - .padding(Padding.default), + .padding(Constants.Padding.default), onClick = { onSubPagePressed() } ) { Text(buttonText) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt similarity index 89% rename from example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt index 76fce4ae4..ce2829ab6 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetSubPageLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt @@ -29,14 +29,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.compose.viewModel.store import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetSubPageNavigationRouteMapper -import com.splendo.kaluga.example.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetSubPageViewModel +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel @Composable fun BottomSheetSubPageLayout( @@ -63,11 +64,11 @@ fun BottomSheetSubPageLayout( Column( Modifier .fillMaxWidth() - .padding(Padding.default) + .padding(Constants.Padding.default) ) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { Button( - modifier = Modifier.padding(Padding.default), + modifier = Modifier.padding(Constants.Padding.default), onClick = { onClosePressed() } ) { Text("X") diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt similarity index 64% rename from example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt index 616631770..3f62ed35d 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/viewModel/NavigationMappers.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt @@ -22,23 +22,24 @@ import com.splendo.kaluga.architecture.compose.navigation.Route import com.splendo.kaluga.architecture.compose.navigation.bottomSheetContent import com.splendo.kaluga.architecture.compose.navigation.bottomSheetSheetContent import com.splendo.kaluga.architecture.compose.navigation.next -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentSubPageNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetSubPageNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageNavigation /** Maps a navigation action to a route string. */ -internal fun bottomSheetParentNavigationRouteMapper(action: BottomSheetParentNavigation): BottomSheetRoute { +internal fun architectureNavigationRouteMapper(action: ArchitectureNavigationAction<*>): BottomSheetRoute { return when (action) { - is BottomSheetParentNavigation.SubPage -> action.next.bottomSheetContent - is BottomSheetParentNavigation.ShowSheet -> action.next.bottomSheetSheetContent + is ArchitectureNavigationAction.Details -> action.next.bottomSheetContent + is ArchitectureNavigationAction.BottomSheet -> action.next.bottomSheetSheetContent } } /** Maps a navigation action to a route string. */ -internal fun bottomSheetParentSubPageNavigationRouteMapper(action: BottomSheetParentSubPageNavigation): Route { +internal fun architectureDetailsNavigationRouteMapper(action: ArchitectureDetailsNavigationAction<*>): Route { return when (action) { - is BottomSheetParentSubPageNavigation.Back -> Route.Back + is ArchitectureDetailsNavigationAction.FinishWithDetails -> Route.Back(mapOf("" to action.value)) + is ArchitectureDetailsNavigationAction.Close -> Route.Back } } @@ -54,6 +55,6 @@ internal fun bottomSheetNavigationRouteMapper(action: BottomSheetNavigation): Bo internal fun bottomSheetSubPageNavigationRouteMapper(action: BottomSheetSubPageNavigation): BottomSheetRoute { return when (action) { is BottomSheetSubPageNavigation.Close -> Route.Close.bottomSheetSheetContent - is BottomSheetSubPageNavigation.Back -> Route.Back.bottomSheetSheetContent + is BottomSheetSubPageNavigation.Back -> Route.Back().bottomSheetSheetContent } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt deleted file mode 100644 index 63e4549ab..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/ui/BottomSheetParentLayout.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.bottomSheet.ui - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.Text -import androidx.compose.material.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRouteController -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetSheetContentRouteController -import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator -import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteController -import com.splendo.kaluga.architecture.compose.navigation.NavigatingModalBottomSheetLayout -import com.splendo.kaluga.architecture.compose.navigation.route -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetParentNavigationRouteMapper -import com.splendo.kaluga.example.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentNavigation -import com.splendo.kaluga.example.shared.viewmodel.bottomsheet.BottomSheetParentViewModel - -@Composable -fun BottomSheetParentLayout() { - MdcTheme { - val bottomSheetRouteController = BottomSheetRouteController( - NavHostRouteController(rememberNavController()), - BottomSheetSheetContentRouteController( - rememberNavController(), - rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), - rememberCoroutineScope() - ) - ) - - bottomSheetRouteController.NavigatingModalBottomSheetLayout( - sheetContent = { contentNavHostController, sheetContentNavHostController, sheetState -> - composable(BottomSheetParentNavigation.ShowSheet.route()) { - BottomSheetLayout(contentNavHostController, sheetContentNavHostController, sheetState) - } - composable(BottomSheetNavigation.SubPage.route()) { - BottomSheetSubPageLayout( - contentNavHostController, sheetContentNavHostController, sheetState - ) - } - }, - contentRoot = { contentNavHostController, sheetContentNavHostController, sheetState -> - BottomSheetParentLayoutContent(contentNavHostController, sheetContentNavHostController, sheetState) - }, - content = { contentNavHostController, _, _ -> - composable(BottomSheetParentNavigation.SubPage.route()) { - BottomSheetParentSubPageLayout(contentNavHostController) - } - } - ) - } -} - -@Composable -fun BottomSheetParentLayoutContent(contentNavHostController: NavHostController, sheetNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - - val navigator = ModalBottomSheetNavigator( - NavHostRouteController(contentNavHostController), - BottomSheetSheetContentRouteController( - sheetNavHostController, - sheetState, - rememberCoroutineScope() - ), - ::bottomSheetParentNavigationRouteMapper - ) - - val viewModel = store { - remember { - BottomSheetParentViewModel(navigator) - } - } - - ViewModelComposable(viewModel) { - Column(Modifier.fillMaxWidth()) { - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Padding.default), - onClick = { onShowSheetPressed() } - ) { - Text(sheetText) - } - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Padding.default), - onClick = { onSubPagePressed() } - ) { - Text(subPageText) - } - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/compose/Constants.kt similarity index 58% rename from example/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/compose/Constants.kt index 01d6d45f2..3d8fafd3d 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ContactsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/compose/Constants.kt @@ -15,18 +15,23 @@ */ -package com.splendo.kaluga.example.contacts +package com.splendo.kaluga.example.compose +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable -import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.example.contacts.ui.ContactsLayout +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp -class ContactsActivity : KalugaViewModelComposeActivity() { - override val viewModel = BaseLifecycleViewModel() +object Constants { + + object Padding { + val default = 8.dp + val x2 = 16.dp + } @Composable - override fun Layout(viewModel: BaseLifecycleViewModel) { - ContactsLayout() + fun DefaultSpacer() { + Spacer(modifier = Modifier.size(Padding.default)) } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt deleted file mode 100644 index c5b96f85c..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactDetailsLayout.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.contacts.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment.Companion.CenterHorizontally -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController -import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator -import com.splendo.kaluga.architecture.compose.navigation.rememberCombinedNavigator -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.contacts.viewModel.contactDetailsNavigationActivityMapper -import com.splendo.kaluga.example.contacts.viewModel.contactDetailsNavigationRouteMapper -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsNavigation -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsViewModel - -@Composable -fun ContactDetailsLayout(contactDetails: ContactDetails, navHostController: NavHostController) { - val routeNavigator = RouteNavigator( - navHostController, - ::contactDetailsNavigationRouteMapper - ) - - val navigator = rememberCombinedNavigator { action: ContactDetailsNavigation<*> -> - when (action) { - is ContactDetailsNavigation.Close -> routeNavigator - is ContactDetailsNavigation.SendEmail -> ::contactDetailsNavigationActivityMapper.toActivityNavigator() - } - } - - val viewModel = store { - remember { - ContactDetailsViewModel(contactDetails, navigator) - } - } - ViewModelComposable(viewModel) { - HardwareBackButtonNavigation(::back) - - Column { - val image = painterResource(id = R.drawable.ic_account) - Image( - modifier = Modifier - .size(320.dp) - .align(CenterHorizontally), - painter = image, - contentDescription = "" - ) - - Column( - modifier = Modifier.padding(horizontal = Padding.x2) - .fillMaxWidth() - ) { - Text(text = "Name:", style = MaterialTheme.typography.h6) - Text(text = contactDetails.name, style = MaterialTheme.typography.body1) - DefaultSpacer() - - Text(text = "Email:", style = MaterialTheme.typography.h6) - Text(text = contactDetails.email, style = MaterialTheme.typography.body1) - DefaultSpacer() - - Button(onClick = ::sendEmail) { - Text(text = sendEmailButtonText, style = MaterialTheme.typography.button) - } - } - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt deleted file mode 100644 index a5c221458..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsLayout.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.contacts.ui - -import androidx.compose.runtime.Composable -import androidx.navigation.compose.rememberNavController -import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator -import com.splendo.kaluga.architecture.compose.navigation.SetupNavHost -import com.splendo.kaluga.architecture.compose.navigation.composable -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.example.contacts.viewModel.contactListNavigationRouteMapper -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation - -@Composable -fun ContactsLayout() { - MdcTheme { - val navigator = RouteNavigator( - rememberNavController(), - ::contactListNavigationRouteMapper - ) - - navigator.SetupNavHost( - rootView = { - ContactsListLayout(navigator = navigator) - } - ) { navHostController -> - composable( - type = NavigationBundleSpecType.SerializedType( - ContactDetails.serializer() - ) - ) { details -> - ContactDetailsLayout(details, navHostController) - } - } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt deleted file mode 100644 index 2e72cd4e3..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/ContactsListLayout.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.contacts.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment.Companion.CenterVertically -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.splendo.kaluga.architecture.compose.state -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListViewModel - -@Composable -fun ContactsListLayout(navigator: Navigator>) { - val viewModel = store { - remember { - ContactsListViewModel(navigator) - } - } - - ViewModelComposable(viewModel) { - val items by contacts.state() - LazyColumn(modifier = Modifier.padding(Padding.default)) { - items(items) { contactDetails -> - ListItem(contactDetails) { - onContactClick(contactDetails) - } - - DefaultSpacer() - } - } - } -} - -@Composable -private fun ListItem( - contactDetails: ContactDetails, - onClickHandler: () -> Unit -) { - Row( - modifier = Modifier - .clickable(onClick = onClickHandler) - .fillMaxWidth() - ) { - val image = painterResource(id = R.drawable.ic_account) - Image( - modifier = Modifier.size(48.dp), - painter = image, - contentDescription = "" - ) - - DefaultSpacer() - - Text( - modifier = Modifier.align(CenterVertically), - text = contactDetails.name, - style = MaterialTheme.typography.h5 - ) - } -} - -@Composable -@Preview -private fun ListItemPreview() { - ListItem(ContactDetails("name", "email")) { } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt deleted file mode 100644 index 4bac3e533..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/contacts/ui/Styles.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.splendo.kaluga.example.contacts.ui - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -object Padding { - val default = 8.dp - val x2 = 16.dp -} - -@Composable -fun DefaultSpacer() { - Spacer(modifier = Modifier.size(Padding.default)) -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt deleted file mode 100644 index 09e1f9b2d..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/contacts/viewModel/NavigationMappers.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.contacts.viewModel - -import com.splendo.kaluga.architecture.compose.navigation.Route -import com.splendo.kaluga.architecture.compose.navigation.next -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactDetailsNavigation -import com.splendo.kaluga.example.shared.viewmodel.contacts.ContactsListNavigation - -/** Maps a navigation action to a route string. */ -internal fun contactListNavigationRouteMapper(action: ContactsListNavigation<*>): Route { - return when (action) { - is ContactsListNavigation.ShowContactDetails -> action.next - } -} - -/** Maps a navigation action to a route string. */ -internal fun contactDetailsNavigationRouteMapper(action: ContactDetailsNavigation<*>): Route { - return when (action) { - is ContactDetailsNavigation.Close -> Route.Back - else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}") - } -} - -/** Maps a navigation action to a NavigationSpec. */ -internal fun contactDetailsNavigationActivityMapper(action: ContactDetailsNavigation<*>): NavigationSpec { - return when (action) { - is ContactDetailsNavigation.SendEmail -> NavigationSpec.Email( - emailSettings = NavigationSpec.Email.EmailSettings( - type = NavigationSpec.Email.Type.Plain, - to = listOf(action.bundle!!.get(action.type)) - ) - ) - else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}") - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt similarity index 83% rename from example/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt rename to example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt index 9132c61e1..9d0e40fd8 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/FeaturesListFragment.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt @@ -1,22 +1,21 @@ /* + Copyright 2022 Splendo Consulting B.V. The Netherlands -Copyright 2022 Splendo Consulting B.V. The Netherlands + 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 - 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 - 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. - 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 com.splendo.kaluga.example +package com.splendo.kaluga.example.featurelist import android.os.Bundle import android.view.LayoutInflater @@ -26,8 +25,9 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment +import com.splendo.kaluga.example.R import com.splendo.kaluga.example.alerts.AlertsActivity -import com.splendo.kaluga.example.architecture.ArchitectureInputActivity +import com.splendo.kaluga.example.architecture.androidui.ArchitectureActivity import com.splendo.kaluga.example.beacons.BeaconsActivity import com.splendo.kaluga.example.bluetooth.BluetoothActivity import com.splendo.kaluga.example.databinding.ViewListButtonBinding @@ -37,7 +37,6 @@ import com.splendo.kaluga.example.link.LinksActivity import com.splendo.kaluga.example.loading.LoadingActivity import com.splendo.kaluga.example.location.LocationActivity import com.splendo.kaluga.example.permissions.PermissionsDemoListActivity -import com.splendo.kaluga.example.platformspecific.PlatformSpecificActivity import com.splendo.kaluga.example.resources.ResourcesActivity import com.splendo.kaluga.example.shared.viewmodel.featureList.Feature import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction @@ -57,14 +56,14 @@ class FeaturesListFragment : KalugaViewModelFragment(R.lay FeatureListNavigationAction.Alerts -> NavigationSpec.Activity() FeatureListNavigationAction.DateTimePicker -> NavigationSpec.Activity() FeatureListNavigationAction.LoadingIndicator -> NavigationSpec.Activity() - FeatureListNavigationAction.Architecture -> NavigationSpec.Activity() + FeatureListNavigationAction.Architecture -> NavigationSpec.Activity() FeatureListNavigationAction.Keyboard -> NavigationSpec.Activity() FeatureListNavigationAction.Links -> NavigationSpec.Activity() FeatureListNavigationAction.System -> NavigationSpec.Activity() FeatureListNavigationAction.Bluetooth -> NavigationSpec.Activity() FeatureListNavigationAction.Beacons -> NavigationSpec.Activity() FeatureListNavigationAction.Resources -> NavigationSpec.Activity() - FeatureListNavigationAction.PlatformSpecific -> NavigationSpec.Activity() + FeatureListNavigationAction.PlatformSpecific -> throw java.lang.RuntimeException("Not supported") } } ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/info/InfoFragment.kt similarity index 84% rename from example/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt rename to example/android/src/main/java/com/splendo/kaluga/example/info/InfoFragment.kt index 7f2d19139..a1cb3c4f9 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/InfoFragment.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/info/InfoFragment.kt @@ -1,22 +1,21 @@ /* + Copyright 2022 Splendo Consulting B.V. The Netherlands -Copyright 2022 Splendo Consulting B.V. The Netherlands + 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 - 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 - 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. - 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 com.splendo.kaluga.example +package com.splendo.kaluga.example.info import android.os.Bundle import android.view.LayoutInflater @@ -29,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment +import com.splendo.kaluga.example.R import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpec import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel diff --git a/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt b/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt index 27a852cd7..13e52d6f8 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt @@ -66,6 +66,7 @@ class LocationBackgroundService : androidx.lifecycle.LifecycleService() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE) } else { + @Suppress("DEPRECATION") stopForeground(true) } NotificationManagerCompat.from(applicationContext).cancel(notificationId) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt deleted file mode 100644 index 210183171..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.platformspecific - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.state -import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity -import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.example.bottomSheet.BottomSheetActivity -import com.splendo.kaluga.example.contacts.ContactsActivity -import com.splendo.kaluga.example.contacts.ui.Padding -import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformFeatureListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformSpecificFeaturesViewModel - -class PlatformSpecificActivity : KalugaViewModelComposeActivity() { - override val viewModel = PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper)) - - @Composable - override fun Layout(viewModel: PlatformSpecificFeaturesViewModel) { - PlatformSpecificFeaturesLayout(viewModel) - } -} - -private fun navigationMapper(action: PlatformFeatureListNavigationAction): NavigationSpec = - when (action) { - is PlatformFeatureListNavigationAction.ComposeNavigation -> NavigationSpec.Activity() - is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity() - } - -@Composable -private fun PlatformSpecificFeaturesLayout(viewModel: PlatformSpecificFeaturesViewModel) { - MdcTheme { - ViewModelComposable(viewModel) { - val features by feature.state() - LazyColumn { - items(features) { item -> - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Padding.default), - onClick = { onFeaturePressed(item) }, - ) { - Text( - text = item.title.uppercase(), - style = MaterialTheme.typography.button, - ) - } - } - } - } - } -} - -@Composable -@Preview -private fun PlatformSpecificFeaturesLayoutPreview() { - PlatformSpecificFeaturesLayout( - PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper)) - ) -} diff --git a/example/android/src/main/res/layout/activity_architecture_details.xml b/example/android/src/main/res/layout/activity_architecture_details.xml index 926bd42b8..584254b5a 100644 --- a/example/android/src/main/res/layout/activity_architecture_details.xml +++ b/example/android/src/main/res/layout/activity_architecture_details.xml @@ -31,6 +31,7 @@ android:text="@{viewModel.number.stateFlow}" /> + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_architecture_input.xml b/example/android/src/main/res/layout/activity_architecture_input.xml index 95c28e0ac..c13f8c6c5 100644 --- a/example/android/src/main/res/layout/activity_architecture_input.xml +++ b/example/android/src/main/res/layout/activity_architecture_input.xml @@ -4,7 +4,7 @@ + type="com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureViewModel" /> @@ -21,7 +21,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - android:hint="@{viewModel.nameHeader.liveData}" + android:hint="@{viewModel.namePlaceholder}" app:errorEnabled="@{viewModel.isNameValid.liveData}"> + app:layout_constraintEnd_toEndOf="parent"/> + + \ No newline at end of file diff --git a/example/android/src/main/res/values/strings.xml b/example/android/src/main/res/values/strings.xml index 9d10a2527..b36b28e53 100644 --- a/example/android/src/main/res/values/strings.xml +++ b/example/android/src/main/res/values/strings.xml @@ -52,8 +52,11 @@ Permission requested successfully Permission request failed + Enter your Name + Enter a number Show Details Inverse Input + Finish Keyboard Show Keyboard Hide Keyboard diff --git a/example/ios/Demo/Base.lproj/Localizable.strings b/example/ios/Demo/Base.lproj/Localizable.strings index f4f15cbf7..120761c50 100644 --- a/example/ios/Demo/Base.lproj/Localizable.strings +++ b/example/ios/Demo/Base.lproj/Localizable.strings @@ -46,6 +46,8 @@ "permission_request_success" = "Permission requested successfully"; "permission_request_failed" = "Permission request failed"; +"architecture_name_input_placeholder" = "Enter your Name"; +"architecture_number_input_placeholder" = "Enter a number"; "architecture_details" = "Show Details"; "architecture_details_inverse" = "Inverse Input"; "keyboard_manager" = "Keyboard"; diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt index 4cc0062ae..7de7b218d 100644 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -26,11 +26,11 @@ import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsNavigationAction import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel -import com.splendo.kaluga.example.shared.viewmodel.architecture.CloseDetailsNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails -import com.splendo.kaluga.example.shared.viewmodel.architecture.InputNavigation import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel @@ -96,11 +96,11 @@ internal val androidModule = module { viewModel { (permission: LocationPermission) -> LocationViewModel(permission) } - viewModel { (navigator: Navigator) -> - ArchitectureInputViewModel(navigator) + viewModel { (navigator: Navigator>) -> + ArchitectureViewModel(navigator) } - viewModel { (initialDetail: InputDetails, navigator: Navigator) -> + viewModel { (initialDetail: InputDetails, navigator: Navigator>) -> ArchitectureDetailsViewModel( initialDetail, navigator diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrAndroidUISelectionViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrAndroidUISelectionViewModel.kt new file mode 100644 index 000000000..2761e90f7 --- /dev/null +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrAndroidUISelectionViewModel.kt @@ -0,0 +1,56 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.viewmodel.compose + +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction +import com.splendo.kaluga.architecture.observable.observableOf +import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.resources.localized + +sealed class ComposeOrAndroidUINavigationAction : SingleValueNavigationAction(Unit, NavigationBundleSpecType.UnitType) { + object Compose : ComposeOrAndroidUINavigationAction() + object AndroidUI : ComposeOrAndroidUINavigationAction() +} + +sealed class UIType(val title: String) { + object Compose : UIType("android_ui_compose".localized()) + object AndroidUI : UIType("android_ui_legacy".localized()) +} + +class ComposeOrAndroidUISelectionViewModel( + navigator: Navigator +) : NavigatingViewModel(navigator) { + + val uiTypes = observableOf( + listOf( + UIType.Compose, + UIType.AndroidUI + ) + ) + + fun onUITypePressed(feature: UIType) { + navigator.navigate( + when (feature) { + is UIType.Compose -> ComposeOrAndroidUINavigationAction.Compose + is UIType.AndroidUI -> ComposeOrAndroidUINavigationAction.AndroidUI + } + ) + } +} diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt index 8d35888bc..f00fd3397 100644 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt @@ -17,41 +17,4 @@ package com.splendo.kaluga.example.shared.viewmodel.featureList -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.observable.observableOf -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -actual val showPlatformSpecificFeatures: Boolean = true - -sealed class PlatformFeatureListNavigationAction : NavigationAction(null) { - object ComposeNavigation : PlatformFeatureListNavigationAction() - object ComposeBottomSheet : PlatformFeatureListNavigationAction() -} - -sealed class PlatformFeature(val title: String) { - object ComposeNavigation : PlatformFeature("feature_platform_specific_compose_navigation".localized()) - object ComposeBottomSheet : PlatformFeature("feature_platform_specific_compose_bottom_sheet".localized()) -} - -class PlatformSpecificFeaturesViewModel( - navigator: Navigator -) : NavigatingViewModel(navigator) { - - val feature = observableOf( - listOf( - PlatformFeature.ComposeNavigation, - PlatformFeature.ComposeBottomSheet - ) - ) - - fun onFeaturePressed(feature: PlatformFeature) { - navigator.navigate( - when (feature) { - is PlatformFeature.ComposeNavigation -> PlatformFeatureListNavigationAction.ComposeNavigation - is PlatformFeature.ComposeBottomSheet -> PlatformFeatureListNavigationAction.ComposeBottomSheet - } - ) - } -} +actual val showPlatformSpecificFeatures: Boolean = false diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt index 2a0489501..ead0a8223 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt @@ -26,6 +26,16 @@ import com.splendo.kaluga.resources.stylable.GradientStyle object ButtonStyles { + val default by lazy { + ButtonStyle( + TextStyles.whiteText, + backgroundColor = DefaultColors.mediumBlue, + pressedBackgroundColor = DefaultColors.darkBlue, + disabledBackgroundColor = DefaultColors.lightSlateGray, + shape = BackgroundStyle.Shape.Rectangle(10.0f) + ) + } + val textButton by lazy { ButtonStyle(TextStyles.redText) } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt index 89e7ca1ad..4e5e7066b 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt @@ -31,12 +31,12 @@ data class InputDetails( val number: Int ) -class CloseDetailsNavigation(inputDetails: InputDetails) : SingleValueNavigationAction( - inputDetails, - NavigationBundleSpecType.SerializedType(InputDetails.serializer()) -) +sealed class ArchitectureDetailsNavigationAction(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { + object Close : ArchitectureDetailsNavigationAction(Unit, NavigationBundleSpecType.UnitType) + class FinishWithDetails(details: InputDetails) : ArchitectureDetailsNavigationAction(details, NavigationBundleSpecType.SerializedType(InputDetails.serializer())) +} -class ArchitectureDetailsViewModel(initialDetail: InputDetails, navigator: Navigator) : NavigatingViewModel(navigator) { +class ArchitectureDetailsViewModel(initialDetail: InputDetails, navigator: Navigator>) : NavigatingViewModel>(navigator) { private val _name = subjectOf(initialDetail.name) val name: InitializedObservable = _name @@ -51,7 +51,11 @@ class ArchitectureDetailsViewModel(initialDetail: InputDetails, navigator: Navig numberResult = numberResult.reversed() } - fun onClosePressed() { - navigator.navigate(CloseDetailsNavigation(InputDetails(nameResult, numberResult.toIntOrNull() ?: 0))) + fun onBackPressed() { + navigator.navigate(ArchitectureDetailsNavigationAction.Close) + } + + fun onFinishPressed() { + navigator.navigate(ArchitectureDetailsNavigationAction.FinishWithDetails(InputDetails(nameResult, numberResult.toIntOrNull() ?: 0))) } } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt similarity index 65% rename from example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt index 49d4fa4fe..9993ff153 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureInputViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt @@ -21,10 +21,12 @@ import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.ObservableOptional -import com.splendo.kaluga.architecture.observable.observableOf import com.splendo.kaluga.architecture.observable.toInitializedSubject import com.splendo.kaluga.architecture.observable.toUninitializedObservable import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.example.shared.stylable.ButtonStyles +import com.splendo.kaluga.resources.localized +import com.splendo.kaluga.resources.view.KalugaButton import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -32,15 +34,15 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -class InputNavigation(inputDetails: InputDetails) : SingleValueNavigationAction( - inputDetails, - NavigationBundleSpecType.SerializedType(InputDetails.serializer()) -) +sealed class ArchitectureNavigationAction(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { + class Details(inputDetails: InputDetails) : ArchitectureNavigationAction(inputDetails, NavigationBundleSpecType.SerializedType(InputDetails.serializer())) + object BottomSheet : ArchitectureNavigationAction(Unit, NavigationBundleSpecType.UnitType) +} -class ArchitectureInputViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { +class ArchitectureViewModel(navigator: Navigator>) : NavigatingViewModel>(navigator) { - val nameHeader = observableOf("Enter your Name") - val numberHeader = observableOf("Enter a Number") + val namePlaceholder = "architecture_name_input_placeholder".localized() + val numberPlaceholder = "architecture_number_input_placeholder".localized() private val _nameInput = MutableStateFlow("") val nameInput = _nameInput.toInitializedSubject(coroutineScope) @@ -67,7 +69,10 @@ class ArchitectureInputViewModel(navigator: Navigator) : Naviga validName && validNumber } - fun onShowDetailsPressed() { + val showDetailsButton = KalugaButton.Plain("architecture_details".localized(), ButtonStyles.default, action = this::onShowDetailsPressed) + val showBottomSheetButton = KalugaButton.Plain("bottom_sheet_show_sheet".localized(), ButtonStyles.default, action = this::onShowBottomSheetPressed) + + private fun onShowDetailsPressed() { val nameResult: ObservableOptional by nameInput val name: String? by nameResult val numberResult: ObservableOptional by numberInput @@ -75,11 +80,15 @@ class ArchitectureInputViewModel(navigator: Navigator) : Naviga coroutineScope.launch { if (isValid.first()) { navigator.navigate( - InputNavigation( + ArchitectureNavigationAction.Details( InputDetails(name ?: "", number?.toIntOrNull() ?: 0) ) ) } } } + + private fun onShowBottomSheetPressed() { + navigator.navigate(ArchitectureNavigationAction.BottomSheet) + } } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetSubPageViewModel.kt similarity index 95% rename from example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetSubPageViewModel.kt index 43b564cbb..4e0cf8cfe 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetSubPageViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetSubPageViewModel.kt @@ -15,7 +15,7 @@ */ -package com.splendo.kaluga.example.shared.viewmodel.bottomsheet +package com.splendo.kaluga.example.shared.viewmodel.architecture import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.Navigator diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetViewModel.kt similarity index 95% rename from example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetViewModel.kt index c6f5a0694..2836c4699 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetViewModel.kt @@ -15,7 +15,7 @@ */ -package com.splendo.kaluga.example.shared.viewmodel.bottomsheet +package com.splendo.kaluga.example.shared.viewmodel.architecture import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.Navigator diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt deleted file mode 100644 index 744b64c5e..000000000 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentSubPageViewModel.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bottomsheet - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class BottomSheetParentSubPageNavigation : NavigationAction(null) { - object Back : BottomSheetParentSubPageNavigation() -} - -class BottomSheetParentSubPageViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val text = "bottom_sheet_sub_page_title".localized() - - fun onBackPressed() { - navigator.navigate(BottomSheetParentSubPageNavigation.Back) - } -} diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt deleted file mode 100644 index 2a7e00d61..000000000 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bottomsheet/BottomSheetParentViewModel.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.bottomsheet - -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.resources.localized - -sealed class BottomSheetParentNavigation : NavigationAction(null) { - object ShowSheet : BottomSheetParentNavigation() - object SubPage : BottomSheetParentNavigation() -} - -class BottomSheetParentViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { - - val sheetText = "bottom_sheet_show_sheet".localized() - val subPageText = "bottom_sheet_show_sub_page".localized() - - fun onShowSheetPressed() { - navigator.navigate(BottomSheetParentNavigation.ShowSheet) - } - - fun onSubPagePressed() { - navigator.navigate(BottomSheetParentNavigation.SubPage) - } -} diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt deleted file mode 100644 index 6a712ec93..000000000 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactDetailsViewModel.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.contacts - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails - -sealed class ContactDetailsNavigation(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { - class SendEmail(email: String) : ContactDetailsNavigation( - email, - NavigationBundleSpecType.StringType - ) - object Close : ContactDetailsNavigation(Unit, NavigationBundleSpecType.UnitType) -} - -class ContactDetailsViewModel( - val contactDetails: ContactDetails, - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - - val sendEmailButtonText = "Send email" - - fun sendEmail() { - navigator.navigate(ContactDetailsNavigation.SendEmail(contactDetails.email)) - } - - fun back() { - navigator.navigate(ContactDetailsNavigation.Close) - } -} diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt deleted file mode 100644 index cb0ebb93c..000000000 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/contacts/ContactsListViewModel.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.shared.viewmodel.contacts - -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction -import com.splendo.kaluga.architecture.observable.FlowInitializedObservable -import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.example.shared.model.contacts.ContactDetails -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch - -sealed class ContactsListNavigation(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { - data class ShowContactDetails(val contactDetails: ContactDetails) : ContactsListNavigation( - contactDetails, - NavigationBundleSpecType.SerializedType(ContactDetails.serializer()) - ) -} - -class ContactsListViewModel( - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - val contacts: FlowInitializedObservable> = - MutableStateFlow(emptyList()) - .also { - coroutineScope.launch { - it.value = loadContacts() - } - }.toInitializedObservable(coroutineScope) - - private suspend fun loadContacts(): List { - // a real call shall happen here - return listOf("Alice", "Bob", "Charlie", "David").map { name -> - ContactDetails(name = name, email = "${name.lowercase()}@example.com") - } - } - - fun onContactClick(contactDetails: ContactDetails) { - navigator.navigate( - ContactsListNavigation.ShowContactDetails(contactDetails) - ) - } -} diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ui/SwiftUIOrUIKitSelectionViewModel.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ui/SwiftUIOrUIKitSelectionViewModel.kt new file mode 100644 index 000000000..cd247b87f --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ui/SwiftUIOrUIKitSelectionViewModel.kt @@ -0,0 +1,56 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.viewmodel.ui + +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction +import com.splendo.kaluga.architecture.observable.observableOf +import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.resources.localized + +sealed class SwiftUIOrUIKitNavigationAction : SingleValueNavigationAction(Unit, NavigationBundleSpecType.UnitType) { + object SwiftUI : SwiftUIOrUIKitNavigationAction() + object UIKit : SwiftUIOrUIKitNavigationAction() +} + +sealed class UIType(val title: String) { + object SwiftUI : UIType("ios_swift_ui".localized()) + object UIKit : UIType("ios_ui_kit".localized()) +} + +class SwiftUIOrUIKitSelectionViewModel( + navigator: Navigator +) : NavigatingViewModel(navigator) { + + val uiTypes = observableOf( + listOf( + UIType.SwiftUI, + UIType.UIKit + ) + ) + + fun onUITypePressed(feature: UIType) { + navigator.navigate( + when (feature) { + is UIType.SwiftUI -> SwiftUIOrUIKitNavigationAction.SwiftUI + is UIType.UIKit -> SwiftUIOrUIKitNavigationAction.UIKit + } + ) + } +} diff --git a/kaluga-library-components/src/main/kotlin/AndroidCommon.kt b/kaluga-library-components/src/main/kotlin/AndroidCommon.kt index 90a5717da..1834113ae 100644 --- a/kaluga-library-components/src/main/kotlin/AndroidCommon.kt +++ b/kaluga-library-components/src/main/kotlin/AndroidCommon.kt @@ -71,8 +71,12 @@ fun LibraryExtension.androidCommon(project: org.gradle.api.Project, componentTyp getByName("main") { manifest.srcFile("src/androidLibMain/AndroidManifest.xml") res.srcDir("src/androidLibMain/res") - if (componentType is ComponentType.Compose) { - java.srcDir("src/androidLibMain/kotlin") + when (componentType) { + is ComponentType.Compose, + is ComponentType.DataBinding -> { + java.srcDir("src/androidLibMain/kotlin") + } + is ComponentType.Default -> {} } } getByName("androidTest") { @@ -106,6 +110,14 @@ fun LibraryExtension.androidCommon(project: org.gradle.api.Project, componentTyp kotlinCompilerExtensionVersion = LibraryImpl.Android.composeCompiler } } + is ComponentType.DataBinding -> { + project.logger.lifecycle("This project module is a Databinding only module") + buildFeatures { + dataBinding { + enable = true + } + } + } is ComponentType.Default-> {} } } diff --git a/kaluga-library-components/src/main/kotlin/AndroidDatabinding.kt b/kaluga-library-components/src/main/kotlin/AndroidDatabinding.kt new file mode 100644 index 000000000..5a15541b2 --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/AndroidDatabinding.kt @@ -0,0 +1,26 @@ +import org.gradle.kotlin.dsl.dependencies + +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +fun org.gradle.api.Project.databindingAndroidComponent() { + group = Library.group + version = Library.version + commonAndroidComponent(ComponentType.DataBinding) + + publish(ComponentType.DataBinding) +} \ No newline at end of file diff --git a/kaluga-library-components/src/main/kotlin/Component.kt b/kaluga-library-components/src/main/kotlin/Component.kt index 03f6c34a1..50e118936 100644 --- a/kaluga-library-components/src/main/kotlin/Component.kt +++ b/kaluga-library-components/src/main/kotlin/Component.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTes sealed class ComponentType { object Default : ComponentType() object Compose : ComponentType() + object DataBinding : ComponentType() } fun Project.commonComponent(iosExport: (Framework.() -> Unit)? = null) { diff --git a/kaluga-library-components/src/main/kotlin/Publish.kt b/kaluga-library-components/src/main/kotlin/Publish.kt index daa9b1345..6aae56f54 100644 --- a/kaluga-library-components/src/main/kotlin/Publish.kt +++ b/kaluga-library-components/src/main/kotlin/Publish.kt @@ -24,7 +24,8 @@ fun Project.publish(componentType: ComponentType = ComponentType.Default) { publications { logger.lifecycle("This project module will be published as: $componentType") when (componentType) { - is ComponentType.Compose -> { + is ComponentType.Compose, + is ComponentType.DataBinding -> { create("release", MavenPublication::class.java) { from(components.getByName("release")) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt b/resources-databinding/build.gradle.kts similarity index 51% rename from example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt rename to resources-databinding/build.gradle.kts index 298917892..e7646c771 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bottomSheet/BottomSheetActivity.kt +++ b/resources-databinding/build.gradle.kts @@ -15,18 +15,19 @@ */ -package com.splendo.kaluga.example.bottomSheet - -import androidx.compose.runtime.Composable -import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.example.bottomSheet.ui.BottomSheetParentLayout +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("jacoco") + id("convention.publication") + id("org.jlleitschuh.gradle.ktlint") +} -class BottomSheetActivity : KalugaViewModelComposeActivity() { - override val viewModel = BaseLifecycleViewModel() +databindingAndroidComponent() - @Composable - override fun Layout(viewModel: BaseLifecycleViewModel) { - BottomSheetParentLayout() - } +dependencies { + implementation(project(":base")) + api(project(":resources")) + implementationDependency(Dependencies.Android.Material) } diff --git a/resources-databinding/src/androidLibAndroidTest/AndroidManifest.xml b/resources-databinding/src/androidLibAndroidTest/AndroidManifest.xml new file mode 100644 index 000000000..875652172 --- /dev/null +++ b/resources-databinding/src/androidLibAndroidTest/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/resources-databinding/src/androidLibAndroidTest/kotlin/TestActivity.kt b/resources-databinding/src/androidLibAndroidTest/kotlin/TestActivity.kt new file mode 100644 index 000000000..f6633c7f9 --- /dev/null +++ b/resources-databinding/src/androidLibAndroidTest/kotlin/TestActivity.kt @@ -0,0 +1,27 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.resources.databinding + +import androidx.appcompat.app.AppCompatActivity + +class TestActivity : AppCompatActivity() + +class CorrectlySetupTest { + @kotlin.test.Test + fun testSetup() = assert(true) +} diff --git a/resources-databinding/src/androidLibMain/AndroidManifest.xml b/resources-databinding/src/androidLibMain/AndroidManifest.xml new file mode 100644 index 000000000..76def87be --- /dev/null +++ b/resources-databinding/src/androidLibMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/resources-databinding/src/androidLibMain/kotlin/Binding.kt b/resources-databinding/src/androidLibMain/kotlin/Binding.kt new file mode 100644 index 000000000..25e9163ad --- /dev/null +++ b/resources-databinding/src/androidLibMain/kotlin/Binding.kt @@ -0,0 +1,107 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.resources.databinding + +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.core.widget.TextViewCompat +import androidx.databinding.BindingAdapter +import com.google.android.material.textfield.TextInputLayout +import com.splendo.kaluga.resources.Image +import com.splendo.kaluga.resources.stylable.BackgroundStyle +import com.splendo.kaluga.resources.stylable.TextStyle +import com.splendo.kaluga.resources.view.KalugaButton +import com.splendo.kaluga.resources.view.KalugaLabel +import com.splendo.kaluga.resources.view.alignment +import com.splendo.kaluga.resources.view.applyBackgroundStyle +import com.splendo.kaluga.resources.view.applyTextStyle +import com.splendo.kaluga.resources.view.bindButton +import com.splendo.kaluga.resources.view.bindLabel +import com.splendo.kaluga.resources.view.gravity +import kotlin.math.max + +object Binding { + + @BindingAdapter("backgroundStyle") + @JvmStatic + fun bindBackground(view: View, backgroundStyle: BackgroundStyle) { + view.applyBackgroundStyle(backgroundStyle) + } + + @BindingAdapter("image") + @JvmStatic + fun bindImage(view: ImageView, image: Image?) { + view.setImageDrawable(image?.drawable) + } + + @BindingAdapter("kalugaButton") + @JvmStatic + fun bindKalugaButton(button: Button, kalugaButton: KalugaButton?) { + kalugaButton?.let { + button.bindButton(it) + } + } + + @BindingAdapter("kalugaLabel") + @JvmStatic + fun bindKalugaLabel(view: TextView, kalugaLabel: KalugaLabel?) { + kalugaLabel?.let { + view.bindLabel(it) + } + } + + @BindingAdapter("textStyle") + @JvmStatic + fun bindTextStyle(textView: TextView, textStyle: TextStyle?) { + textStyle?.let { + textView.applyTextStyle(textStyle) + } + } + + @BindingAdapter("autoTextStyle", "autoTextMinScalingFactor") + @JvmStatic + fun bindAutoTextStyle(textView: TextView, autoTextStyle: TextStyle?, autoTextMinScalingFactor: Float) { + autoTextStyle?.let { + textView.applyTextStyle(it) + TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(textView, max(1, (it.size * autoTextMinScalingFactor).toInt()), it.size.toInt(), 1, TypedValue.COMPLEX_UNIT_SP) + textView.gravity = Gravity.CENTER_VERTICAL or it.alignment.alignment(textView.context).gravity + } + } + + @BindingAdapter("textStyle") + @JvmStatic + fun bindTextInputLayoutTextStyle(textView: TextInputLayout, textStyle: TextStyle) { + textView.editText?.applyTextStyle(textStyle) + } + + @BindingAdapter("prefixTextStyle") + @JvmStatic + fun bindPrefixTextStyle(textView: TextInputLayout, textStyle: TextStyle) { + textView.prefixTextView.applyTextStyle(textStyle) + } + + @BindingAdapter("suffixTextStyle") + @JvmStatic + fun bindSuffixTextStyle(textView: TextInputLayout, textStyle: TextStyle) { + textView.suffixTextView.applyTextStyle(textStyle) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 22490cf3a..7f1f0775f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -79,6 +79,7 @@ include(":keyboard-compose") include(":links") include(":resources") include(":resources-compose") +include(":resources-databinding") include(":review") include(":scientific") include(":system") From ba3a00167bbe72e456e7442bc49fd919c442c2e5 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Tue, 22 Nov 2022 17:34:03 +0100 Subject: [PATCH 034/227] Fixed navigation and updating Android example databinding --- .../navigation/ModalBottomSheetNavigator.kt | 6 +- .../architecture/compose/navigation/Route.kt | 32 +++- .../compose/navigation/RouteNavigator.kt | 71 ++++++-- architecture/build.gradle.kts | 1 + .../kotlin/lifecycle/LifecycleSubscribable.kt | 4 +- .../kotlin/navigation/NavigationSpec.kt | 45 ++++- .../kotlin/navigation/Navigator.kt | 66 ++++--- .../KalugaViewModelDialogFragment.kt | 14 ++ .../KalugaViewModelLifecycleObserver.kt | 5 +- .../kotlin/viewmodel/LifecycleViewModel.kt | 3 +- example/android/build.gradle.kts | 4 + example/android/src/main/AndroidManifest.xml | 123 +++++++++---- .../kaluga/example/alerts/AlertsActivity.kt | 43 +---- .../example/alerts/compose/AlertsLayout.kt | 80 +++++++++ .../example/alerts/xml/XMLAlertsActivity.kt | 24 +++ .../ArchitectureActivity.kt} | 23 ++- .../compose/ArchitectureDetailsLayout.kt | 13 +- .../compose/ArchitectureLayout.kt | 95 +++++++++-- .../architecture/compose/BottomSheetLayout.kt | 17 +- .../compose/BottomSheetSubPageLayout.kt | 4 +- .../architecture/compose/NavigationMappers.kt | 4 +- .../ArchitectureDetailsActivity.kt | 2 +- .../xml/BottomSheetRootDialogFragment.kt | 131 ++++++++++++++ .../xml/BottomSheetSubPageFragment.kt | 67 ++++++++ .../XMLArchitectureActivity.kt} | 8 +- .../kaluga/example/beacons/BeaconsAdapter.kt | 10 ++ .../example/bluetooth/BluetoothAdapter.kt | 21 ++- .../BluetoothCharacteristicAdapter.kt | 21 ++- .../bluetooth/BluetoothDescriptorAdapter.kt | 21 ++- .../bluetooth/BluetoothServiceAdapter.kt | 21 ++- .../example/compose/ComposeOrXMLActivity.kt | 101 +++++++++++ .../datetimepicker/DateTimePickerActivity.kt | 35 ++-- .../compose/DateTimePickerLayout.kt | 86 ++++++++++ .../xml/XMLDateTimePickerActivity.kt | 38 +++++ .../featurelist/FeaturesListFragment.kt | 44 +++-- .../kaluga/example/info/InfoFragment.kt | 32 +++- .../example/keyboard/KeyboardActivity.kt | 24 +++ .../keyboard/KeyboardManagerActivity.kt | 46 ----- .../keyboard/compose/KeyboardLayout.kt | 111 ++++++++++++ .../keyboard/xml/XMLKeyboardActivity.kt | 48 ++++++ .../kaluga/example/link/LinksActivity.kt | 2 +- .../kaluga/example/loading/LoadingActivity.kt | 51 ++---- .../example/loading/compose/LoadingLayout.kt | 78 +++++++++ .../example/loading/xml/XMLLoadingActivity.kt | 41 +++++ .../example/location/LocationActivity.kt | 18 +- ...sDemoActivity.kt => PermissionActivity.kt} | 22 +-- ...Activity.kt => PermissionsListActivity.kt} | 24 ++- .../example/resources/ResourcesActivity.kt | 85 ++------- .../resources/compose/ButtonsLayout.kt | 60 +++++++ .../example/resources/compose/ColorsLayout.kt | 161 ++++++++++++++++++ .../example/resources/compose/LabelsLayout.kt | 57 +++++++ .../resources/compose/ResourcesLayout.kt | 113 ++++++++++++ .../ButtonsActivity.kt} | 54 +++--- .../ColorsActivity.kt} | 79 +++------ .../LabelsActivity.kt} | 54 +++--- .../resources/xml/XMLResourcesActivity.kt | 99 +++++++++++ .../kaluga/example/system/SystemActivity.kt | 32 ++-- .../src/main/res/layout/activity_alerts.xml | 69 ++++---- .../layout/activity_architecture_details.xml | 6 +- .../layout/activity_architecture_input.xml | 3 +- .../src/main/res/layout/activity_button.xml | 24 +++ .../layout/activity_compose_or_android_ui.xml | 24 +++ .../res/layout/activity_date_time_picker.xml | 14 +- .../res/layout/activity_keyboard_manager.xml | 55 +++--- .../src/main/res/layout/activity_label.xml | 24 +++ .../src/main/res/layout/activity_loading.xml | 45 ++--- .../src/main/res/layout/activity_location.xml | 5 + .../main/res/layout/activity_permission.xml | 42 +++++ .../res/layout/activity_permissions_demo.xml | 30 ---- .../res/layout/activity_permissions_list.xml | 22 ++- .../main/res/layout/activity_resources.xml | 8 +- .../res/layout/activity_resources_color.xml | 16 +- .../src/main/res/layout/activity_system.xml | 52 +++--- .../main/res/layout/fragment_bottom_sheet.xml | 29 ++++ .../res/layout/fragment_bottom_sheet_root.xml | 9 + .../layout/fragment_bottom_sheet_sub_page.xml | 30 ++++ .../res/layout/fragment_features_list.xml | 23 ++- .../src/main/res/layout/fragment_info.xml | 22 ++- .../android/src/main/res/values/strings.xml | 2 + .../example/shared/di/DependencyInjection.kt | 18 ++ ...l.kt => ComposeOrXMLSelectionViewModel.kt} | 20 +-- .../example/shared/stylable/ButtonStyles.kt | 10 +- .../shared/viewmodel/alert/AlertViewModel.kt | 19 ++- .../ArchitectureDetailsViewModel.kt | 13 +- .../architecture/ArchitectureViewModel.kt | 5 +- .../architecture/BottomSheetViewModel.kt | 6 +- .../datetimepicker/DateTimePickerViewModel.kt | 10 +- .../shared/viewmodel/hud/HudViewModel.kt | 7 +- .../viewmodel/keyboard/KeyboardViewModel.kt | 7 +- .../permissions/PermissionViewModel.kt | 1 + .../viewmodel/system/SystemViewModel.kt | 2 +- .../src/main/kotlin/Dependencies.kt | 1 + .../kotlin/ComposeClearFocusHandler.kt | 2 +- .../kotlin/ClearFocusHandler.kt | 6 +- .../androidLibMain/kotlin/KeyboardManager.kt | 9 +- 95 files changed, 2491 insertions(+), 773 deletions(-) create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt rename example/android/src/main/java/com/splendo/kaluga/example/{beacons/BeaconsBinding.kt => architecture/ArchitectureActivity.kt} (55%) rename example/android/src/main/java/com/splendo/kaluga/example/architecture/{androidui => xml}/ArchitectureDetailsActivity.kt (97%) create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetRootDialogFragment.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetSubPageFragment.kt rename example/android/src/main/java/com/splendo/kaluga/example/architecture/{androidui/ArchitectureActivity.kt => xml/XMLArchitectureActivity.kt} (91%) create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardActivity.kt delete mode 100644 example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/loading/xml/XMLLoadingActivity.kt rename example/android/src/main/java/com/splendo/kaluga/example/permissions/{PermissionsDemoActivity.kt => PermissionActivity.kt} (67%) rename example/android/src/main/java/com/splendo/kaluga/example/permissions/{PermissionsDemoListActivity.kt => PermissionsListActivity.kt} (77%) create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt rename example/android/src/main/java/com/splendo/kaluga/example/resources/{ButtonActivity.kt => xml/ButtonsActivity.kt} (58%) rename example/android/src/main/java/com/splendo/kaluga/example/resources/{ColorActivity.kt => xml/ColorsActivity.kt} (63%) rename example/android/src/main/java/com/splendo/kaluga/example/resources/{LabelActivity.kt => xml/LabelsActivity.kt} (57%) create mode 100644 example/android/src/main/java/com/splendo/kaluga/example/resources/xml/XMLResourcesActivity.kt create mode 100644 example/android/src/main/res/layout/activity_button.xml create mode 100644 example/android/src/main/res/layout/activity_compose_or_android_ui.xml create mode 100644 example/android/src/main/res/layout/activity_label.xml create mode 100644 example/android/src/main/res/layout/activity_permission.xml delete mode 100644 example/android/src/main/res/layout/activity_permissions_demo.xml create mode 100644 example/android/src/main/res/layout/fragment_bottom_sheet.xml create mode 100644 example/android/src/main/res/layout/fragment_bottom_sheet_root.xml create mode 100644 example/android/src/main/res/layout/fragment_bottom_sheet_sub_page.xml rename example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/{ComposeOrAndroidUISelectionViewModel.kt => ComposeOrXMLSelectionViewModel.kt} (67%) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt index b0296905c..f6bbefd1f 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt @@ -78,10 +78,8 @@ class BottomSheetSheetContentRouteController( sheetContentRouteController.navigate(newRoute) } - override fun back(result: Map): Boolean = if (navHostController.backQueue.isNotEmpty()) { - navHostController.previousBackStackEntry?.savedStateHandle?.let { savedStateHandle -> - result.entries.forEach { (key, value) -> savedStateHandle[key] = value } - } + override fun back(result: Route.Result): Boolean = if (navHostController.backQueue.isNotEmpty()) { + navHostController.previousBackStackEntry?.setResult(result) navHostController.popBackStack() } else { close() diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt index 934d5f3c2..2aca3c577 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt @@ -19,10 +19,13 @@ package com.splendo.kaluga.architecture.compose.navigation import androidx.activity.result.ActivityResultLauncher import com.splendo.kaluga.architecture.navigation.NavigationAction +import com.splendo.kaluga.architecture.navigation.NavigationBundle import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.NavigationBundleValue +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec +import com.splendo.kaluga.architecture.navigation.toBundle import com.splendo.kaluga.base.text.KalugaDateFormatter import com.splendo.kaluga.base.text.iso8601Pattern import kotlinx.serialization.builtins.BooleanArraySerializer @@ -170,8 +173,16 @@ private val NavigationBundleValue<*>.routeArgument: String? sealed class Route { companion object { - val Back = Back(emptyMap()) - val PopToRoot = PopToRoot(emptyMap()) + val Back = Back() + val PopToRoot = PopToRoot() + } + + sealed class Result { + companion object { + const val KEY = "com.splendo.kaluga.architecture.compose.navigation.Route.Result.KEY" + } + object Empty: Result() + data class Data, Bundle : NavigationBundle>(val bundle: Bundle) : Result() } /** @@ -208,11 +219,14 @@ sealed class Route { /** * Route that navigates back to the screen associated with a [NavigationAction] * @param routeAction The [Action] associated with the screen to navigate back to. - * @param result A map of values to store in the [androidx.lifecycle.SavedStateHandle] of the screen to navigate back to. + * @param result The [Result] to be provided to the screen to navigate back to. */ - data class PopTo, Action : NavigationAction>( + data class PopTo< + SpecRow : NavigationBundleSpecRow<*>, + Action : NavigationAction, + >( override val routeAction: Action, - val result: Map = emptyMap() + val result: Result = Result.Empty ) : Navigate() /** @@ -233,15 +247,15 @@ sealed class Route { /** * Navigates back one step in the hierarchy - * @param result A map of values to store in the [androidx.lifecycle.SavedStateHandle] of the previous screen. + * @param result The [Result] to provide to the previous view. */ - data class Back(val result: Map = emptyMap()) : Route() + data class Back(val result: Result = Result.Empty) : Route() /** * Navigates to the Root of a navigation stack - * @param result A map of values to store in the [androidx.lifecycle.SavedStateHandle] of the root screen. + * @param result The [Result] to provide to the root view. */ - data class PopToRoot(val result: Map = emptyMap()) : Route() + data class PopToRoot(val result: Result = Result.Empty) : Route() /** * Closes all screens in the navigation stack diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt index 97be678cc..4a0951bfe 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt @@ -1,15 +1,26 @@ package com.splendo.kaluga.architecture.compose.navigation +import android.os.Bundle import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier +import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.splendo.kaluga.architecture.navigation.NavigationAction +import com.splendo.kaluga.architecture.navigation.NavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec +import com.splendo.kaluga.architecture.navigation.toBundle +import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.navigation.toTypedProperty /** * Controller for navigating to a [Route] @@ -24,7 +35,7 @@ sealed interface RouteController { /** * Navigates back */ - fun back(result: Map = emptyMap()): Boolean + fun back(result: Route.Result): Boolean /** * Closes this RouteController @@ -46,6 +57,7 @@ class NavHostRouteController( internal val navHostController: NavHostController, private val parentRouteController: RouteController? = null ) : RouteController { + override fun navigate(newRoute: Route) { when (newRoute) { is Route.NextRoute<*, *> -> navHostController.navigate(newRoute.route) { @@ -59,9 +71,7 @@ class NavHostRouteController( navHostController.navigate(newRoute.route) } is Route.PopTo<*, *> -> { - navHostController.getBackStackEntry(newRoute.route).savedStateHandle.let { savedStateHandle -> - newRoute.result.entries.forEach { (key, value) -> savedStateHandle[key] = value } - } + navHostController.getBackStackEntry(newRoute.route).setResult(newRoute.result) navHostController.popBackStack(newRoute.route, false) } is Route.PopToIncluding<*, *> -> { @@ -69,9 +79,7 @@ class NavHostRouteController( } is Route.Back -> back(newRoute.result) is Route.PopToRoot -> { - navHostController.getBackStackEntry(ROOT_VIEW).savedStateHandle.let { savedStateHandle -> - newRoute.result.entries.forEach { (key, value) -> savedStateHandle[key] = value } - } + navHostController.getBackStackEntry(ROOT_VIEW).setResult(newRoute.result) navHostController.popBackStack(ROOT_VIEW, false) } is Route.Close -> close() @@ -79,10 +87,8 @@ class NavHostRouteController( } } - override fun back(result: Map): Boolean = if (navHostController.backQueue.isNotEmpty()){ - navHostController.previousBackStackEntry?.savedStateHandle?.let { savedStateHandle -> - result.entries.forEach { (key, value) -> savedStateHandle[key] = value } - } + override fun back(result: Route.Result): Boolean = if (navHostController.backQueue.isNotEmpty()){ + navHostController.previousBackStackEntry?.setResult(result) navHostController.popBackStack() } else { parentRouteController?.back(result) ?: false } @@ -191,3 +197,46 @@ fun RouteController.SetupNavHost( builder = { builder(this@SetupNavHost) } ) } + +/** + * Handles a [Route.Result] matching a given [NavigationBundleSpec] + * @param spec The [NavigationBundleSpec] used to create the [Route.Result] + * @param retain If `true` the result will be retained in the [NavBackStackEntry]. It will be deleted otherwise. + * @param onResult Method for handling the received result + */ +@Composable +fun > NavHostController.HandleResult( + spec: NavigationBundleSpec, + retain: Boolean = false, + onResult: @Composable NavigationBundle.() -> Unit +) = HandleResult(retain) { toNavigationBundle(spec).onResult() } + +/** + * Handles a [Route.Result] matching a given [NavigationBundleSpecType] + * Requires that the [Route.Result] is described by a [SingleValueNavigationSpec] matching the [NavigationBundleSpecType] + * @param type The [NavigationBundleSpecType] stored in the result + * @param retain If `true` the result will be retained in the [NavBackStackEntry]. It will be deleted otherwise. + * @param onResult Method for handling the received result + */ +@Composable +fun NavHostController.HandleResult( + type: NavigationBundleSpecType, + retain: Boolean = false, + onResult: @Composable R.() -> Unit +) = HandleResult(retain) { toTypedProperty(type).onResult() } + +@Composable +internal fun NavHostController.HandleResult(retain: Boolean = false, onResult: @Composable Bundle.() -> Unit) { + val result = currentBackStackEntry?.savedStateHandle?.getStateFlow(Route.Result.KEY, null)?.collectAsState() + result?.value?.let { + onResult(it) + if (!retain) { + currentBackStackEntry?.savedStateHandle?.remove(Route.Result.KEY) + } + } +} + +internal fun NavBackStackEntry.setResult(result: Route.Result) = when (result) { + is Route.Result.Empty -> savedStateHandle.remove(Route.Result.KEY) + is Route.Result.Data<*, *> -> savedStateHandle[Route.Result.KEY] = result.bundle.toBundle() +} diff --git a/architecture/build.gradle.kts b/architecture/build.gradle.kts index 9cfd9b4f1..13f728e7c 100644 --- a/architecture/build.gradle.kts +++ b/architecture/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { apiDependency(Dependencies.AndroidX.Lifecycle.Runtime) apiDependency(Dependencies.AndroidX.Lifecycle.ViewModel) apiDependency(Dependencies.AndroidX.Lifecycle.LiveData) + apiDependency(Dependencies.Android.Material) implementationDependency(Dependencies.AndroidX.Browser) implementationDependency(Dependencies.KotlinX.AtomicFu) } diff --git a/architecture/src/androidLibMain/kotlin/lifecycle/LifecycleSubscribable.kt b/architecture/src/androidLibMain/kotlin/lifecycle/LifecycleSubscribable.kt index 018968f6d..3027859d5 100644 --- a/architecture/src/androidLibMain/kotlin/lifecycle/LifecycleSubscribable.kt +++ b/architecture/src/androidLibMain/kotlin/lifecycle/LifecycleSubscribable.kt @@ -28,7 +28,8 @@ interface LifecycleSubscribable : LifecycleSubscribableMarker { data class LifecycleManager( val activity: Activity?, val lifecycleOwner: LifecycleOwner, - val fragmentManager: FragmentManager + val fragmentManager: FragmentManager, + val childFragmentManager: FragmentManager? = null ) val manager: LifecycleManager? @@ -65,3 +66,4 @@ open class LifecycleSubscriber : LifecycleSubscribable { */ fun LifecycleSubscribable.subscribe(activity: AppCompatActivity) = subscribe(LifecycleSubscribable.LifecycleManager(activity, activity, activity.supportFragmentManager)) fun LifecycleSubscribable.subscribe(activity: Activity?, owner: LifecycleOwner, fragmentManager: FragmentManager) = subscribe(LifecycleSubscribable.LifecycleManager(activity, owner, fragmentManager)) +fun LifecycleSubscribable.subscribe(activity: Activity?, owner: LifecycleOwner, parentFragmentManager: FragmentManager, childFragmentManager: FragmentManager?) = subscribe(LifecycleSubscribable.LifecycleManager(activity, owner, parentFragmentManager, childFragmentManager)) diff --git a/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt b/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt index 25113c022..634062f68 100644 --- a/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt +++ b/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt @@ -25,6 +25,8 @@ import androidx.annotation.AnimRes import androidx.annotation.AnimatorRes import androidx.annotation.IdRes import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable import java.net.URL import kotlin.reflect.KClass import kotlin.reflect.safeCast @@ -109,6 +111,7 @@ sealed class NavigationSpec { * @param tag Optional tag of the fragment transaction * @param backStackSettings The [BackStackSettings] of the transaction. Defaults to [BackStackSettings.DontAdd] * @param animationSettings Optional [AnimationSettings] for the transaction + * @param getFragmentManager Optional getter for the [FragmentManager] to handle showing the [Fragment] * @param createFragment Function to create the [androidx.fragment.app.Fragment] */ data class Fragment( @@ -117,6 +120,7 @@ sealed class NavigationSpec { val tag: String? = null, val backStackSettings: BackStackSettings = BackStackSettings.DontAdd, val animationSettings: AnimationSettings? = null, + val getFragmentManager: LifecycleSubscribable.LifecycleManager.() -> FragmentManager = { fragmentManager }, val createFragment: () -> androidx.fragment.app.Fragment ) : NavigationSpec() { @@ -169,21 +173,56 @@ sealed class NavigationSpec { /** * Removes a [Fragment] with a given tag * @param tag The tag of the [Fragment] to remove + * @param getFragmentManager Optional getter for the [FragmentManager] to handle removing the [Fragment] */ - data class RemoveFragment(val tag: String) : NavigationSpec() + data class RemoveFragment( + val tag: String, + val getFragmentManager: LifecycleSubscribable.LifecycleManager.() -> FragmentManager = { fragmentManager } + ) : NavigationSpec() + + /** + * Pops a [Fragment] from the backstack + * @param immediate If `true` the transaction should execute without waiting for pending transactions + * @param getFragmentManager Optional getter for the [FragmentManager] to handle popping the [Fragment] + */ + data class PopFragment( + val immediate: Boolean = false, + val getFragmentManager: LifecycleSubscribable.LifecycleManager.() -> FragmentManager = { fragmentManager } + ) : NavigationSpec() + + /** + * Pops a [Fragment] from the backstack + * @param immediate If `true` the transaction should execute without waiting for pending transactions + * @param getFragmentManager Optional getter for the [FragmentManager] to handle popping the [Fragment] + */ + data class PopFragmentTo( + val name: String, + val inclusive: Boolean, + val immediate: Boolean = false, + val getFragmentManager: LifecycleSubscribable.LifecycleManager.() -> FragmentManager = { fragmentManager } + ) : NavigationSpec() /** * Shows a [DialogFragment] * @param tag Optional tag to add to the Dialog + * @param getFragmentManager Optional getter for the [FragmentManager] to handle showing the [DialogFragment] * @param createDialog Function to create the [DialogFragment] to display */ - data class Dialog(val tag: String? = null, val createDialog: () -> DialogFragment) : NavigationSpec() + data class Dialog( + val tag: String? = null, + val getFragmentManager: LifecycleSubscribable.LifecycleManager.() -> FragmentManager = { fragmentManager }, + val createDialog: () -> DialogFragment + ) : NavigationSpec() /** * Dismisses a [DialogFragment] with a given Tag * @param tag The tag of the [DialogFragment] to remove + * @param getFragmentManager Optional getter for the [FragmentManager] to handle removing the [DialogFragment] */ - data class DismissDialog(val tag: String) : NavigationSpec() + data class DismissDialog( + val tag: String, + val getFragmentManager: LifecycleSubscribable.LifecycleManager.() -> FragmentManager = { fragmentManager } + ) : NavigationSpec() /** * Shows the Camera diff --git a/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt b/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt index 83da65992..e91a5f52a 100644 --- a/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt +++ b/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt @@ -24,6 +24,7 @@ import android.provider.MediaStore import android.provider.Settings import androidx.browser.customtabs.CustomTabsIntent import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscriber @@ -50,6 +51,8 @@ class ActivityNavigator>(private val navigationMapper: ( is NavigationSpec.Close -> closeActivity(spec, bundle) is NavigationSpec.Fragment -> navigateToFragment(spec) is NavigationSpec.RemoveFragment -> removeFragment(spec) + is NavigationSpec.PopFragment -> popFragment(spec) + is NavigationSpec.PopFragmentTo -> popFragmentTo(spec) is NavigationSpec.Dialog -> navigateToDialog(spec) is NavigationSpec.DismissDialog -> dismissDialog(spec) is NavigationSpec.Camera -> navigateToCamera(spec) @@ -100,8 +103,8 @@ class ActivityNavigator>(private val navigationMapper: ( } private fun navigateToFragment(fragmentSpec: NavigationSpec.Fragment) { - assert(manager?.fragmentManager != null) - val fragmentManager = manager?.fragmentManager ?: return + assert(manager != null) + val fragmentManager = fragmentSpec.getFragmentManager(manager!!) val transaction = fragmentManager.beginTransaction().let { when (val backtrackSettings = fragmentSpec.backStackSettings) { is NavigationSpec.Fragment.BackStackSettings.Add -> it.addToBackStack( @@ -138,8 +141,8 @@ class ActivityNavigator>(private val navigationMapper: ( } private fun removeFragment(removeFragmentSpec: NavigationSpec.RemoveFragment) { - assert(manager?.fragmentManager != null) - val fragmentManager = manager?.fragmentManager ?: return + assert(manager != null) + val fragmentManager = removeFragmentSpec.getFragmentManager(manager!!) val fragment = fragmentManager.findFragmentByTag(removeFragmentSpec.tag) ?: return val transaction = fragmentManager.beginTransaction() @@ -147,15 +150,36 @@ class ActivityNavigator>(private val navigationMapper: ( transaction.commit() } + private fun popFragment(popFragmentSpec: NavigationSpec.PopFragment) { + assert(manager != null) + val fragmentManager = popFragmentSpec.getFragmentManager(manager!!) + if (popFragmentSpec.immediate) { + fragmentManager.popBackStackImmediate() + } else { + fragmentManager.popBackStack() + } + } + + private fun popFragmentTo(popToFragmentSpec: NavigationSpec.PopFragmentTo) { + assert(manager != null) + val fragmentManager = popToFragmentSpec.getFragmentManager(manager!!) + val flags = if (popToFragmentSpec.inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0 + if (popToFragmentSpec.immediate) { + fragmentManager.popBackStackImmediate(popToFragmentSpec.name, flags) + } else { + fragmentManager.popBackStack(popToFragmentSpec.name, flags) + } + } + private fun navigateToDialog(dialogSpec: NavigationSpec.Dialog) { - assert(manager?.fragmentManager != null) - val fragmentManager = manager?.fragmentManager ?: return + assert(manager != null) + val fragmentManager = dialogSpec.getFragmentManager(manager!!) dialogSpec.createDialog().show(fragmentManager, dialogSpec.tag) } private fun dismissDialog(spec: NavigationSpec.DismissDialog) { - assert(manager?.fragmentManager != null) - val fragmentManager = manager?.fragmentManager ?: return + assert(manager != null) + val fragmentManager = spec.getFragmentManager(manager!!) val dialog = fragmentManager.findFragmentByTag(spec.tag) as? DialogFragment ?: return dialog.dismiss() } @@ -198,11 +222,13 @@ class ActivityNavigator>(private val navigationMapper: ( ) } }.apply { - data = Uri.parse("mailto:") - type = when (settings.type) { - is NavigationSpec.Email.Type.Plain -> "text/plain" - is NavigationSpec.Email.Type.Stylized -> "*/*" - } + setDataAndType( + Uri.parse("mailto:"), + when (settings.type) { + is NavigationSpec.Email.Type.Plain -> "text/plain" + is NavigationSpec.Email.Type.Stylized -> "*/*" + } + ) if (settings.to.isNotEmpty()) { putExtra(Intent.EXTRA_EMAIL, settings.to.toTypedArray()) } @@ -303,12 +329,14 @@ class ActivityNavigator>(private val navigationMapper: ( } }.apply { val recipients = settings.recipients.fold("") { acc, recipient -> if (acc.isNotEmpty()) "$acc;$recipient" else recipient } - data = Uri.parse("smsto:$recipients") - type = when (settings.type) { - is NavigationSpec.TextMessenger.Type.Plain -> "text/plain" - is NavigationSpec.TextMessenger.Type.Image -> "image/*" - is NavigationSpec.TextMessenger.Type.Video -> "video/*" - } + setDataAndType( + Uri.parse("smsto:$recipients"), + when (settings.type) { + is NavigationSpec.TextMessenger.Type.Plain -> "text/plain" + is NavigationSpec.TextMessenger.Type.Image -> "image/*" + is NavigationSpec.TextMessenger.Type.Video -> "video/*" + } + ) settings.subject?.let { putExtra("subject", it) } settings.body?.let { putExtra("sms_body", it) } } diff --git a/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelDialogFragment.kt b/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelDialogFragment.kt index cde1ef316..aa55c4c82 100644 --- a/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelDialogFragment.kt +++ b/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelDialogFragment.kt @@ -20,6 +20,7 @@ package com.splendo.kaluga.architecture.viewmodel import android.os.Bundle import android.view.View import androidx.fragment.app.DialogFragment +import com.google.android.material.bottomsheet.BottomSheetDialogFragment /** * Convenience [DialogFragment] that is bound to a [LifecycleViewModel] @@ -33,3 +34,16 @@ abstract class KalugaViewModelDialogFragment : Dial viewModel.bind(this) } } + +/** + * Convenience [BottomSheetDialogFragment] that is bound to a [LifecycleViewModel] + */ +abstract class KalugaViewModelBottomSheetDialogFragment : BottomSheetDialogFragment() { + + abstract val viewModel: VM + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.bind(this) + } +} diff --git a/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelLifecycleObserver.kt b/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelLifecycleObserver.kt index 920a9a660..b250df5a5 100644 --- a/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelLifecycleObserver.kt +++ b/architecture/src/androidLibMain/kotlin/viewmodel/KalugaViewModelLifecycleObserver.kt @@ -42,7 +42,8 @@ class KalugaViewModelLifecycleObserver internal con private val viewModel: VM, private val activity: Activity?, private val lifecycleOwner: LifecycleOwner, - private val fragmentManager: FragmentManager + private val fragmentManager: FragmentManager, + private val childFragmentManager: FragmentManager? = null ) : DefaultLifecycleObserver { private val lifecycleSubscribables: List by lazy { @@ -58,7 +59,7 @@ class KalugaViewModelLifecycleObserver internal con } override fun onCreate(owner: LifecycleOwner) { - lifecycleSubscribables.forEach { it.subscribe(activity, lifecycleOwner, fragmentManager) } + lifecycleSubscribables.forEach { it.subscribe(activity, lifecycleOwner, fragmentManager, childFragmentManager) } } override fun onDestroy(owner: LifecycleOwner) { diff --git a/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt b/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt index 84fa66e1d..cc3504412 100644 --- a/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt +++ b/architecture/src/androidLibMain/kotlin/viewmodel/LifecycleViewModel.kt @@ -45,6 +45,7 @@ fun VM.bind(activity: AppCompatActivity) { */ fun VM.bind(fragment: Fragment): Boolean { val fragmentManager = fragment.parentFragmentManager - fragment.lifecycle.addObserver(KalugaViewModelLifecycleObserver(this, fragment.activity, fragment.viewLifecycleOwner, fragmentManager)) + val childFragmentManager = fragment.childFragmentManager + fragment.lifecycle.addObserver(KalugaViewModelLifecycleObserver(this, fragment.activity, fragment.viewLifecycleOwner, fragmentManager, childFragmentManager)) return true } diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts index 8ade69aeb..e3a82bbea 100644 --- a/example/android/build.gradle.kts +++ b/example/android/build.gradle.kts @@ -67,6 +67,7 @@ kotlin { languageSettings { optIn("kotlin.ExperimentalStdlibApi") optIn("androidx.compose.material.ExperimentalMaterialApi") + optIn("androidx.compose.ui.ExperimentalComposeUiApi") } } } @@ -74,6 +75,7 @@ kotlin { dependencies { val libraryVersion = Library.version implementation("com.splendo.kaluga:architecture-compose:$libraryVersion") + implementation("com.splendo.kaluga:keyboard-compose:$libraryVersion") implementation("com.splendo.kaluga:resources-compose:$libraryVersion") implementation("com.splendo.kaluga:resources-databinding:$libraryVersion") implementation(project(":shared")) @@ -96,4 +98,6 @@ dependencies { implementationDependency(Dependencies.KotlinX.Serialization.Core) implementationDependency(Dependencies.KotlinX.Serialization.Json) + + implementationDependency(Dependencies.Koin.AndroidXCompose) } diff --git a/example/android/src/main/AndroidManifest.xml b/example/android/src/main/AndroidManifest.xml index b9c064c37..ad9739cb0 100644 --- a/example/android/src/main/AndroidManifest.xml +++ b/example/android/src/main/AndroidManifest.xml @@ -45,7 +45,8 @@ tools:ignore="GoogleAppIndexingWarning"> + android:exported="true" + android:screenOrientation="portrait"> @@ -53,22 +54,55 @@ - - + android:label="@string/feature_location" + android:screenOrientation="portrait"/> + + + android:label="@string/feature_alerts" + android:screenOrientation="portrait"/> + + + android:label="@string/feature_date_time_picker" + android:screenOrientation="portrait"/> + + - + android:label="@string/feature_hud" + android:screenOrientation="portrait"/> + + + + + + android:launchMode="singleTask" + android:screenOrientation="portrait"> @@ -79,35 +113,52 @@ android:pathPrefix="/kalugaexample"/> - - - - - - - + + + + + + android:label="@string/feature_bluetooth" + android:screenOrientation="portrait"/> + android:label="@string/feature_bluetooth" + android:screenOrientation="portrait"/> + android:label="@string/feature_beacons" + android:screenOrientation="portrait"/> - - - - - - + android:label="@string/feature_resources" + android:screenOrientation="portrait"/> + + + + + + + diff --git a/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt index 5fa52e39d..c588684e4 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt @@ -1,42 +1,7 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.alerts -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel - -@SuppressLint("SetTextI18n") -class AlertsActivity : KalugaViewModelActivity(R.layout.activity_alerts) { - - override val viewModel: AlertViewModel by viewModel() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) +import com.splendo.kaluga.example.alerts.compose.ComposeAlertsActivity +import com.splendo.kaluga.example.alerts.xml.XMLAlertsActivity +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity - findViewById(R.id.btn_simple_alert).setOnClickListener { viewModel.showAlert() } - findViewById(R.id.btn_dismissible_alert).setOnClickListener { viewModel.showAndDismissAfter(3) } - findViewById(R.id.btn_alert_list).setOnClickListener { viewModel.showAlertWithList() } - findViewById(R.id.btn_alert_input_field).setOnClickListener { viewModel.showAlertWithInput() } - } -} +class AlertsActivity : ComposeOrXMLActivity(ComposeAlertsActivity::class.java, XMLAlertsActivity::class.java) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt new file mode 100644 index 000000000..c6468b7c3 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt @@ -0,0 +1,80 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.alerts.compose + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.alerts.alertPresenterBuilder +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel +import com.splendo.kaluga.resources.compose.Composable + +class ComposeAlertsActivity : AppCompatActivity() { + @SuppressLint("MissingSuperCall") // Lint bug + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider( + LocalAppCompatActivity provides this + ) { + AlertsLayout() + } + } + } +} + +@Composable +fun AlertsLayout() { + MdcTheme { + val activity = LocalAppCompatActivity.current!! + val viewModel = storeAndRemember { + AlertViewModel(activity.alertPresenterBuilder()) + } + + ViewModelComposable(viewModel) { + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + showAlertButton.Composable(modifier = Modifier.fillMaxWidth()) + showAndDismissAfter3SecondsButton.Composable(modifier = Modifier.fillMaxWidth()) + showAlertWithInputButton.Composable(modifier = Modifier.fillMaxWidth()) + showAlertWithListButton.Composable(modifier = Modifier.fillMaxWidth()) + } + } + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt new file mode 100644 index 000000000..b5d877ed5 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt @@ -0,0 +1,24 @@ +package com.splendo.kaluga.example.alerts.xml + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityAlertsBinding +import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +@SuppressLint("SetTextI18n") +class XMLAlertsActivity : KalugaViewModelActivity() { + + override val viewModel: AlertViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityAlertsBinding.inflate(LayoutInflater.from(this), null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt similarity index 55% rename from example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt index 74e5ad859..dcaef43b6 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt @@ -15,17 +15,16 @@ */ -package com.splendo.kaluga.example.beacons +package com.splendo.kaluga.example.architecture -import androidx.databinding.BindingAdapter -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconViewModel +import com.splendo.kaluga.example.architecture.compose.ComposeArchitectureActivity +import com.splendo.kaluga.example.architecture.xml.XMLArchitectureActivity +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity -object BeaconsBinding { - @BindingAdapter("beacons") - @JvmStatic - fun bindBeacons(view: RecyclerView, beacons: List?) { - val beaconsAdapter = view.adapter as? BeaconsAdapter ?: return - beaconsAdapter.beacons = beacons ?: emptyList() - } -} +class ArchitectureActivity : ComposeOrXMLActivity< + ComposeArchitectureActivity, + XMLArchitectureActivity + >( + ComposeArchitectureActivity::class.java, + XMLArchitectureActivity::class.java +) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt index b6b2ebc97..7ce473bdf 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt @@ -21,15 +21,17 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator +import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember -import com.splendo.kaluga.example.bottomSheet.viewModel.architectureDetailsNavigationRouteMapper import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails +import com.splendo.kaluga.resources.compose.Composable @Composable fun ArchitectureDetailsLayout(inputDetails: InputDetails, navHostController: NavHostController) { @@ -43,9 +45,14 @@ fun ArchitectureDetailsLayout(inputDetails: InputDetails, navHostController: Nav } ViewModelComposable(viewModel) { - HardwareBackButtonNavigation(onBackButtonClickHandler = { viewModel.onBackPressed() }) + val nameText by name.state() + val numberText by number.state() + HardwareBackButtonNavigation(onBackButtonClickHandler = { onBackPressed() }) Column(Modifier.fillMaxWidth()) { - Text("") + Text(nameText) + Text(numberText) + inverseButton.Composable(modifier = Modifier.fillMaxWidth()) + finishButton.Composable(modifier = Modifier.fillMaxWidth()) } } } \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt index b4d6b3c52..08025e907 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt @@ -1,15 +1,38 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.architecture.compose import android.annotation.SuppressLint import android.os.Bundle +import android.view.KeyEvent import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -17,7 +40,13 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -25,6 +54,7 @@ import com.google.android.material.composethemeadapter.MdcTheme import com.splendo.kaluga.architecture.compose.mutableState import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRouteController import com.splendo.kaluga.architecture.compose.navigation.BottomSheetSheetContentRouteController +import com.splendo.kaluga.architecture.compose.navigation.HandleResult import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteController import com.splendo.kaluga.architecture.compose.navigation.NavigatingModalBottomSheetLayout @@ -35,9 +65,6 @@ import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.example.bottomSheet.ui.BottomSheetLayout -import com.splendo.kaluga.example.bottomSheet.ui.BottomSheetSubPageLayout -import com.splendo.kaluga.example.bottomSheet.viewModel.architectureNavigationRouteMapper import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureNavigationAction import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureViewModel @@ -45,7 +72,7 @@ import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavig import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import com.splendo.kaluga.resources.compose.Composable -class ArchitectureActivity : AppCompatActivity() { +class ComposeArchitectureActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug override fun onCreate(savedInstanceState: Bundle?) { @@ -116,13 +143,55 @@ fun ArchitectureLayoutLayoutContent(contentNavHostController: NavHostController, } ViewModelComposable(viewModel) { - val nameInput = viewModel.nameInput.mutableState() - val isNameValid by viewModel.isNameValid.state() - val numberInput = viewModel.numberInput.mutableState() - val isNumberValid by viewModel.isNumberValid.state() + val nameInput = nameInput.mutableState() + val isNameValid by isNameValid.state() + val numberInput = numberInput.mutableState() + val isNumberValid by isNumberValid.state() - Column(Modifier.fillMaxWidth()) { - viewModel.showDetailsButton.Composable(modifier = Modifier.fillMaxWidth()) + val focusManager = LocalFocusManager.current + + contentNavHostController.HandleResult(NavigationBundleSpecType.SerializedType(InputDetails.serializer())) { + nameInput.value = name + numberInput.value = number.toString() + } + + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + ) { + OutlinedTextField( + value = nameInput.value, + onValueChange = { nameInput.value = it }, + modifier = Modifier + .fillMaxWidth() + .onPreviewKeyEvent { + if (it.key == Key.Tab && it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) { + focusManager.moveFocus(FocusDirection.Down) + true + } else { + false + } + }, + isError = !isNameValid, + placeholder = { Text(namePlaceholder) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { focusManager.moveFocus(FocusDirection.Down) } + ) + ) + OutlinedTextField( + value = numberInput.value, + onValueChange = { numberInput.value = it }, + modifier = Modifier.fillMaxWidth(), + isError = !isNumberValid, + placeholder = { Text(numberPlaceholder) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) + ) + showDetailsButton.Composable(modifier = Modifier.fillMaxWidth()) + showBottomSheetButton.Composable(modifier = Modifier.fillMaxWidth()) } } } \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index 9db22a4e9..cadbb695b 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt @@ -15,26 +15,24 @@ */ -package com.splendo.kaluga.example.bottomSheet.ui +package com.splendo.kaluga.example.architecture.compose import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember -import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetNavigationRouteMapper import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel +import com.splendo.kaluga.resources.compose.Composable @Composable fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { @@ -58,14 +56,9 @@ fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentN .padding(Constants.Padding.default) ) { Text(text) - Button( - modifier = Modifier - .fillMaxWidth() - .padding(Constants.Padding.default), - onClick = { onSubPagePressed() } - ) { - Text(buttonText) - } + button.Composable(modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default)) } } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt index ce2829ab6..565771345 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt @@ -15,7 +15,7 @@ */ -package com.splendo.kaluga.example.bottomSheet.ui +package com.splendo.kaluga.example.architecture.compose import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -29,13 +29,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.compose.viewModel.store -import com.splendo.kaluga.example.bottomSheet.viewModel.bottomSheetSubPageNavigationRouteMapper import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt index 3f62ed35d..4f8b2cc52 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt @@ -15,7 +15,7 @@ */ -package com.splendo.kaluga.example.bottomSheet.viewModel +package com.splendo.kaluga.example.architecture.compose import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRoute import com.splendo.kaluga.architecture.compose.navigation.Route @@ -38,7 +38,7 @@ internal fun architectureNavigationRouteMapper(action: ArchitectureNavigationAct /** Maps a navigation action to a route string. */ internal fun architectureDetailsNavigationRouteMapper(action: ArchitectureDetailsNavigationAction<*>): Route { return when (action) { - is ArchitectureDetailsNavigationAction.FinishWithDetails -> Route.Back(mapOf("" to action.value)) + is ArchitectureDetailsNavigationAction.FinishWithDetails -> Route.Back(Route.Result.Data(action.bundle!!)) is ArchitectureDetailsNavigationAction.Close -> Route.Back } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureDetailsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/ArchitectureDetailsActivity.kt similarity index 97% rename from example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureDetailsActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/ArchitectureDetailsActivity.kt index b43381841..652cdfe3c 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureDetailsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/ArchitectureDetailsActivity.kt @@ -16,7 +16,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.architecture.androidui +package com.splendo.kaluga.example.architecture.xml import android.os.Bundle import com.splendo.kaluga.architecture.navigation.ActivityNavigator diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetRootDialogFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetRootDialogFragment.kt new file mode 100644 index 000000000..7a8aef287 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetRootDialogFragment.kt @@ -0,0 +1,131 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.architecture.xml + +import android.os.Bundle +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelBottomSheetDialogFragment +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.FragmentBottomSheetBinding +import com.splendo.kaluga.example.databinding.FragmentBottomSheetRootBinding +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class BottomSheetRootDialogFragment : KalugaViewModelBottomSheetDialogFragment() { + + companion object { + val TAG = BottomSheetRootDialogFragment::class.simpleName.orEmpty() + } + + private val subPageNavigator = ActivityNavigator { action -> + when (action) { + is BottomSheetSubPageNavigation.Back -> NavigationSpec.PopFragment(true) { childFragmentManager!! } + is BottomSheetSubPageNavigation.Close -> NavigationSpec.DismissDialog(TAG) + } + } + override val viewModel: BottomSheetViewModel by viewModel { + parametersOf( + ActivityNavigator { action -> + when (action) { + is BottomSheetNavigation.Close -> NavigationSpec.DismissDialog(TAG) + is BottomSheetNavigation.SubPage -> NavigationSpec.Fragment(R.id.fragment_container_view, backStackSettings = NavigationSpec.Fragment.BackStackSettings.Add(), getFragmentManager = { childFragmentManager!! }) { + BottomSheetSubPageFragment(subPageNavigator) + } + } + } + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + + val binding = FragmentBottomSheetRootBinding.inflate(inflater, container, false) + if (savedInstanceState == null) { + childFragmentManager.commit { + addToBackStack(null) + add(binding.fragmentContainerView.id, BottomSheetFragment(viewModel)) + } + } + + viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + super.onCreate(owner) + + subPageNavigator.subscribe(LifecycleSubscribable.LifecycleManager(activity, viewLifecycleOwner, parentFragmentManager, childFragmentManager)) + } + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + + subPageNavigator.unsubscribe() + } + }) + + return binding.root + } +} + +class BottomSheetFragment(val viewModel: BottomSheetViewModel) : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + + val binding = FragmentBottomSheetBinding.inflate(inflater, container, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + return binding.root + } + + override fun onResume() { + super.onResume() + view?.let { + it.isFocusableInTouchMode = true + it.requestFocus() + it.setOnKeyListener { _, keyCode, _ -> + if (keyCode == KeyEvent.KEYCODE_BACK) { + viewModel.onClosePressed() + true + } else { + false + } + } + } + } + +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetSubPageFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetSubPageFragment.kt new file mode 100644 index 000000000..27ebdf191 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/BottomSheetSubPageFragment.kt @@ -0,0 +1,67 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.architecture.xml + +import android.os.Bundle +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.example.databinding.FragmentBottomSheetSubPageBinding +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class BottomSheetSubPageFragment(private val navigator: Navigator) : Fragment() { + + val viewModel: BottomSheetSubPageViewModel by viewModel { + parametersOf(navigator) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + + val binding = FragmentBottomSheetSubPageBinding.inflate(inflater, container, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + return binding.root + } + + override fun onResume() { + super.onResume() + view?.let { + it.isFocusableInTouchMode = true + it.requestFocus() + it.setOnKeyListener { _, keyCode, _ -> + if (keyCode == KeyEvent.KEYCODE_BACK) { + viewModel.onBackPressed() + true + } else { + false + } + } + } + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/XMLArchitectureActivity.kt similarity index 91% rename from example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/XMLArchitectureActivity.kt index c5d3e8dd1..5243e1dbd 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/androidui/ArchitectureActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/XMLArchitectureActivity.kt @@ -16,7 +16,7 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.architecture.androidui +package com.splendo.kaluga.example.architecture.xml import android.content.Context import android.content.Intent @@ -34,7 +34,7 @@ import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class ArchitectureActivity : KalugaViewModelActivity() { +class XMLArchitectureActivity : KalugaViewModelActivity() { inner class Contract : ActivityResultContract() { @@ -51,9 +51,9 @@ class ArchitectureActivity : KalugaViewModelActivity() { ActivityNavigator> { action -> when (action) { is ArchitectureNavigationAction.Details -> NavigationSpec.Activity( - launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } + launchType = NavigationSpec.Activity.LaunchType.ActivityContract { contract } ) - is ArchitectureNavigationAction.BottomSheet -> throw java.lang.RuntimeException() + is ArchitectureNavigationAction.BottomSheet -> NavigationSpec.Dialog(BottomSheetRootDialogFragment.TAG) { BottomSheetRootDialogFragment() } } } ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt b/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt index 1c76de087..378d5bb15 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsAdapter.kt @@ -19,6 +19,7 @@ package com.splendo.kaluga.example.beacons import android.view.LayoutInflater import android.view.ViewGroup +import androidx.databinding.BindingAdapter import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BeaconItemBinding @@ -26,6 +27,15 @@ import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconView class BeaconsAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("beacons") + @JvmStatic + fun bindBeacons(view: RecyclerView, beacons: List?) { + val beaconsAdapter = view.adapter as? BeaconsAdapter ?: return + beaconsAdapter.beacons = beacons ?: emptyList() + } + } + class BeaconItemViewHolder(val binding: BeaconItemBinding) : RecyclerView.ViewHolder(binding.root) internal var beacons: List = emptyList() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt index 5010367be..f63f2a0db 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt @@ -25,19 +25,18 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListDeviceViewModel -object DevicesBinding { - - @BindingAdapter("devices") - @JvmStatic - fun bindDevices(view: RecyclerView, devices: List?) { - val bluetoothAdapter = view.adapter as? BluetoothAdapter - ?: return - bluetoothAdapter.bluetoothDevices = devices ?: emptyList() - } -} - class BluetoothAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("devices") + @JvmStatic + fun bindDevices(view: RecyclerView, devices: List?) { + val bluetoothAdapter = view.adapter as? BluetoothAdapter + ?: return + bluetoothAdapter.bluetoothDevices = devices ?: emptyList() + } + } + class BluetoothItemViewHolder(val binding: BluetoothItemBinding) : RecyclerView.ViewHolder(binding.root) internal var bluetoothDevices: List = emptyList() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt index a0b9e6322..3b25b9260 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothCharacteristicAdapter.kt @@ -25,19 +25,18 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothCharacteristicItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothCharacteristicViewModel -object CharacteristicsBinding { - - @BindingAdapter("characteristics") - @JvmStatic - fun bindCharacteristics(view: RecyclerView, characteristics: List?) { - val characteristicAdapter = view.adapter as? BluetoothCharacteristicAdapter - ?: return - characteristicAdapter.characteristics = characteristics ?: emptyList() - } -} - class BluetoothCharacteristicAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("characteristics") + @JvmStatic + fun bindCharacteristics(view: RecyclerView, characteristics: List?) { + val characteristicAdapter = view.adapter as? BluetoothCharacteristicAdapter + ?: return + characteristicAdapter.characteristics = characteristics ?: emptyList() + } + } + class BluetoothCharacteristicItemViewHolder(val characteristicItem: BluetoothCharacteristicItemBinding) : RecyclerView.ViewHolder(characteristicItem.root) internal var characteristics = emptyList() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt index e59ee3b49..d44545dd6 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothDescriptorAdapter.kt @@ -25,19 +25,18 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothDescriptorItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDescriptorViewModel -object DescriptorsBinding { - - @BindingAdapter("descriptors") - @JvmStatic - fun bindDescriptors(view: RecyclerView, descriptors: List?) { - val descriptorAdapter = view.adapter as? BluetoothDescriptorAdapter - ?: return - descriptorAdapter.descriptors = descriptors ?: emptyList() - } -} - class BluetoothDescriptorAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("descriptors") + @JvmStatic + fun bindDescriptors(view: RecyclerView, descriptors: List?) { + val descriptorAdapter = view.adapter as? BluetoothDescriptorAdapter + ?: return + descriptorAdapter.descriptors = descriptors ?: emptyList() + } + } + class BluetoothDescriptorItemViewHolder(val descriptorItem: BluetoothDescriptorItemBinding) : RecyclerView.ViewHolder(descriptorItem.root) var descriptors = emptyList() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt index 46711f70e..9182132a9 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothServiceAdapter.kt @@ -25,19 +25,18 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothServiceItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothServiceViewModel -object ServicesBinding { - - @BindingAdapter("services") - @JvmStatic - fun bindServices(view: RecyclerView, services: List?) { - val serviceAdapter = view.adapter as? BluetoothServiceAdapter - ?: return - serviceAdapter.services = services ?: emptyList() - } -} - class BluetoothServiceAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("services") + @JvmStatic + fun bindServices(view: RecyclerView, services: List?) { + val serviceAdapter = view.adapter as? BluetoothServiceAdapter + ?: return + serviceAdapter.services = services ?: emptyList() + } + } + class BluetoothServiceItemViewHolder(val serviceItem: BluetoothServiceItemBinding) : RecyclerView.ViewHolder(serviceItem.root) internal var services: List = emptyList() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt new file mode 100644 index 000000000..91ab8e40a --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt @@ -0,0 +1,101 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.compose + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityComposeOrAndroidUiBinding +import com.splendo.kaluga.example.databinding.ViewListButtonBinding +import com.splendo.kaluga.example.shared.viewmodel.compose.ComposeOrXMLNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.compose.ComposeOrXMLSelectionViewModel +import com.splendo.kaluga.example.shared.viewmodel.compose.UIType +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +abstract class ComposeOrXMLActivity( + composeActivityClass: Class, + XMLActivityClass: Class +) : KalugaViewModelActivity() { + + override val viewModel: ComposeOrXMLSelectionViewModel by viewModel { + parametersOf( + ActivityNavigator { action -> + when (action) { + is ComposeOrXMLNavigationAction.Compose -> NavigationSpec.Activity(composeActivityClass) + is ComposeOrXMLNavigationAction.XML -> NavigationSpec.Activity(XMLActivityClass) + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityComposeOrAndroidUiBinding.inflate(LayoutInflater.from(this), null, false) + binding.uiTypesList.adapter = UITypesAdapter(viewModel) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + } +} + +class UITypesAdapter(private val viewModel: ComposeOrXMLSelectionViewModel) : RecyclerView.Adapter() { + + companion object { + @BindingAdapter("uiTypes") + @JvmStatic + fun bindUITypes(view: RecyclerView, uiTypes: List?) { + val adapter = (view.adapter as? UITypesAdapter) ?: return + adapter.uiTypes = uiTypes.orEmpty() + } + } + + class UITypesViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { + val button = binding.button + } + + var uiTypes: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UITypesViewHolder { + val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return UITypesViewHolder(binding) + } + + override fun getItemCount(): Int = uiTypes.size + + override fun onBindViewHolder(holder: UITypesViewHolder, position: Int) { + uiTypes.getOrNull(position)?.let { uiType -> + holder.button.text = uiType.title + holder.button.setOnClickListener { viewModel.onUITypePressed(uiType) } + } ?: run { + holder.button.text = null + holder.button.setOnClickListener(null) + } + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt index b766243e4..8566707a1 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/DateTimePickerActivity.kt @@ -1,21 +1,24 @@ -package com.splendo.kaluga.example.datetimepicker +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 -import android.os.Bundle -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityDateTimePickerBinding -import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel -import org.koin.android.ext.android.inject + http://www.apache.org/licenses/LICENSE-2.0 -class DateTimePickerActivity : KalugaViewModelActivity() { + 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. - override val viewModel: DateTimePickerViewModel by inject() + */ + +package com.splendo.kaluga.example.datetimepicker - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity +import com.splendo.kaluga.example.datetimepicker.compose.ComposeDateTimePickerActivity +import com.splendo.kaluga.example.datetimepicker.xml.XMLDateTimePickerActivity - val binding = ActivityDateTimePickerBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) - binding.viewModel = viewModel - binding.lifecycleOwner = this - } -} +class DateTimePickerActivity : ComposeOrXMLActivity(ComposeDateTimePickerActivity::class.java, XMLDateTimePickerActivity::class.java) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt new file mode 100644 index 000000000..1d08774f9 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt @@ -0,0 +1,86 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.datetimepicker.compose + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.datetimepicker.datePickerPresenterBuilder +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel +import com.splendo.kaluga.resources.compose.Composable + +class ComposeDateTimePickerActivity : AppCompatActivity() { + @SuppressLint("MissingSuperCall") // Lint bug + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider( + LocalAppCompatActivity provides this + ) { + DateTimePickerLayout() + } + } + } +} + +@Composable +fun DateTimePickerLayout() { + MdcTheme { + val activity = LocalAppCompatActivity.current!! + val viewModel = storeAndRemember { + DateTimePickerViewModel(activity.datePickerPresenterBuilder()) + } + + ViewModelComposable(viewModel) { + + val dateLabel by dateLabel.state() + + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + selectDateButton.Composable(modifier = Modifier.fillMaxWidth()) + selectTimeButton.Composable(modifier = Modifier.fillMaxWidth()) + Text(currentTimeTitle) + Text(dateLabel) + } + } + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt new file mode 100644 index 000000000..8f64071ae --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt @@ -0,0 +1,38 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.datetimepicker.xml + +import android.os.Bundle +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityDateTimePickerBinding +import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel +import org.koin.android.ext.android.inject + +class XMLDateTimePickerActivity : KalugaViewModelActivity() { + + override val viewModel: DateTimePickerViewModel by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityDateTimePickerBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + binding.viewModel = viewModel + binding.lifecycleOwner = this + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt index 9d0e40fd8..c43782406 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt @@ -21,22 +21,23 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment -import com.splendo.kaluga.example.R import com.splendo.kaluga.example.alerts.AlertsActivity -import com.splendo.kaluga.example.architecture.androidui.ArchitectureActivity +import com.splendo.kaluga.example.architecture.ArchitectureActivity import com.splendo.kaluga.example.beacons.BeaconsActivity import com.splendo.kaluga.example.bluetooth.BluetoothActivity +import com.splendo.kaluga.example.databinding.FragmentFeaturesListBinding import com.splendo.kaluga.example.databinding.ViewListButtonBinding import com.splendo.kaluga.example.datetimepicker.DateTimePickerActivity -import com.splendo.kaluga.example.keyboard.KeyboardManagerActivity +import com.splendo.kaluga.example.keyboard.KeyboardActivity import com.splendo.kaluga.example.link.LinksActivity import com.splendo.kaluga.example.loading.LoadingActivity import com.splendo.kaluga.example.location.LocationActivity -import com.splendo.kaluga.example.permissions.PermissionsDemoListActivity +import com.splendo.kaluga.example.permissions.PermissionsListActivity import com.splendo.kaluga.example.resources.ResourcesActivity import com.splendo.kaluga.example.shared.viewmodel.featureList.Feature import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction @@ -45,19 +46,19 @@ import com.splendo.kaluga.example.system.SystemActivity import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class FeaturesListFragment : KalugaViewModelFragment(R.layout.fragment_features_list) { +class FeaturesListFragment : KalugaViewModelFragment() { override val viewModel: FeatureListViewModel by viewModel { parametersOf( ActivityNavigator { action -> when (action) { FeatureListNavigationAction.Location -> NavigationSpec.Activity() - FeatureListNavigationAction.Permissions -> NavigationSpec.Activity() + FeatureListNavigationAction.Permissions -> NavigationSpec.Activity() FeatureListNavigationAction.Alerts -> NavigationSpec.Activity() FeatureListNavigationAction.DateTimePicker -> NavigationSpec.Activity() FeatureListNavigationAction.LoadingIndicator -> NavigationSpec.Activity() FeatureListNavigationAction.Architecture -> NavigationSpec.Activity() - FeatureListNavigationAction.Keyboard -> NavigationSpec.Activity() + FeatureListNavigationAction.Keyboard -> NavigationSpec.Activity() FeatureListNavigationAction.Links -> NavigationSpec.Activity() FeatureListNavigationAction.System -> NavigationSpec.Activity() FeatureListNavigationAction.Bluetooth -> NavigationSpec.Activity() @@ -69,19 +70,32 @@ class FeaturesListFragment : KalugaViewModelFragment(R.lay ) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val adapter = FeaturesAdapter(viewModel).apply { - - view.findViewById(R.id.features_list).adapter = this - } - viewModel.feature.observeInitialized { adapter.features = it } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + + val binding = FragmentFeaturesListBinding.inflate(inflater, container, false) + binding.viewModel = viewModel + binding.lifecycleOwner = viewLifecycleOwner + binding.featuresList.adapter = FeaturesAdapter(viewModel) + return binding.root } } class FeaturesAdapter(private val viewModel: FeatureListViewModel) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("features") + @JvmStatic + fun bindFeatures(view: RecyclerView, features: List?) { + val adapter = (view.adapter as? FeaturesAdapter) ?: return + adapter.features = features.orEmpty() + } + } + class FeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { val button = binding.button } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/info/InfoFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/info/InfoFragment.kt index a1cb3c4f9..8eb83a976 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/info/InfoFragment.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/info/InfoFragment.kt @@ -23,12 +23,14 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.widget.AppCompatButton +import androidx.databinding.BindingAdapter import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.FragmentInfoBinding import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpec import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel @@ -64,20 +66,32 @@ class InfoFragment : KalugaViewModelFragment(R.layout.fragment_in ) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - - super.onViewCreated(view, savedInstanceState) - - val adapter = InfoAdapter(viewModel).apply { - view.findViewById(R.id.info_buttons) - .adapter = this - } - viewModel.buttons.observeInitialized { adapter.buttons = it } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + + val binding = FragmentInfoBinding.inflate(inflater, container, false) + binding.viewModel = viewModel + binding.lifecycleOwner = viewLifecycleOwner + binding.infoButtons.adapter = InfoAdapter(viewModel) + return binding.root } } class InfoAdapter(private val viewModel: InfoViewModel) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("infoButtons") + @JvmStatic + fun bindInfoButtons(view: RecyclerView, infoButtons: List?) { + val adapter = (view.adapter as? InfoAdapter) ?: return + adapter.buttons = infoButtons.orEmpty() + } + } + class InfoViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button) var buttons: List = emptyList() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardActivity.kt new file mode 100644 index 000000000..f3a19a0ed --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardActivity.kt @@ -0,0 +1,24 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.keyboard + +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity +import com.splendo.kaluga.example.keyboard.compose.ComposeKeyboardActivity +import com.splendo.kaluga.example.keyboard.xml.XMLKeyboardActivity + +class KeyboardActivity : ComposeOrXMLActivity(ComposeKeyboardActivity::class.java, XMLKeyboardActivity::class.java) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt deleted file mode 100644 index 15ffc09e7..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - -Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.example.keyboard - -import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel -import com.splendo.kaluga.keyboard.ViewFocusHandler -import com.splendo.kaluga.keyboard.keyboardManagerBuilder -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class KeyboardManagerActivity : KalugaViewModelActivity(R.layout.activity_keyboard_manager) { - - override val viewModel: KeyboardViewModel by viewModel { - parametersOf( - keyboardManagerBuilder(), - ViewFocusHandler(R.id.edit_field) - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - findViewById(R.id.btn_show_keyboard).setOnClickListener { viewModel.onShowPressed() } - findViewById(R.id.btn_hide_keyboard).setOnClickListener { viewModel.onHidePressed() } - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt new file mode 100644 index 000000000..423c1914e --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt @@ -0,0 +1,111 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.keyboard.compose + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.OutlinedTextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel +import com.splendo.kaluga.keyboard.KeyboardManager +import com.splendo.kaluga.keyboard.compose.ComposeClearFocusHandler +import com.splendo.kaluga.keyboard.compose.ComposeFocusHandler +import com.splendo.kaluga.resources.compose.Composable +import kotlinx.coroutines.flow.MutableStateFlow + +class ComposeKeyboardActivity : AppCompatActivity() { + @SuppressLint("MissingSuperCall") // Lint bug + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider( + LocalAppCompatActivity provides this + ) { + KeyboardLayout() + } + } + } +} + +@Composable +fun KeyboardLayout() { + MdcTheme { + val focusHandler = LocalFocusManager.current + val viewModel = storeAndRemember { + KeyboardViewModel( + KeyboardManager.Builder( + clearFocusHandler = ComposeClearFocusHandler(MutableStateFlow(focusHandler)) + ), + ComposeFocusHandler(FocusRequester.Default) + ) + } + + ViewModelComposable(viewModel) { + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + var text by remember { + mutableStateOf("") + } + OutlinedTextField( + value = text, + onValueChange = { text = it }, + modifier = Modifier + .fillMaxWidth() + .focusRequester(FocusRequester.Default), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { focusHandler.clearFocus() } + ) + ) + showButton.Composable(modifier = Modifier.fillMaxWidth()) + hideButton.Composable(modifier = Modifier.fillMaxWidth()) + } + } + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt new file mode 100644 index 000000000..dc16bdf35 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt @@ -0,0 +1,48 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.keyboard.xml + +import android.os.Bundle +import android.view.LayoutInflater +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivityKeyboardManagerBinding +import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel +import com.splendo.kaluga.keyboard.ViewFocusHandler +import com.splendo.kaluga.keyboard.keyboardManagerBuilder +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class XMLKeyboardActivity : KalugaViewModelActivity() { + + override val viewModel: KeyboardViewModel by viewModel { + parametersOf( + keyboardManagerBuilder(), + ViewFocusHandler(R.id.edit_field) + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityKeyboardManagerBinding.inflate(LayoutInflater.from(this), null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt index 846c75ea4..478d6ee72 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/link/LinksActivity.kt @@ -30,7 +30,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import java.net.URL -class LinksActivity : KalugaViewModelActivity(R.layout.activity_link) { +class LinksActivity : KalugaViewModelActivity() { override val viewModel: LinksViewModel by viewModel { parametersOf( diff --git a/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt index 11468040e..89a0bf9e0 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/loading/LoadingActivity.kt @@ -1,45 +1,24 @@ /* + Copyright 2022 Splendo Consulting B.V. The Netherlands -Copyright 2022 Splendo Consulting B.V. The Netherlands + 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 - 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 - 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. - 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 com.splendo.kaluga.example.loading -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R -import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel - -@SuppressLint("SetTextI18n") -class LoadingActivity : KalugaViewModelActivity(R.layout.activity_loading) { - - override val viewModel: HudViewModel by viewModel() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - findViewById(R.id.btn_show_loading_indicator_system).setOnClickListener { - viewModel.onShowSystemPressed() - } +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity +import com.splendo.kaluga.example.loading.compose.ComposeLoadingActivity +import com.splendo.kaluga.example.loading.xml.XMLLoadingActivity - findViewById(R.id.btn_show_loading_indicator_custom).setOnClickListener { - viewModel.onShowCustomPressed() - } - } -} +class LoadingActivity : ComposeOrXMLActivity(ComposeLoadingActivity::class.java, XMLLoadingActivity::class.java) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt new file mode 100644 index 000000000..8c2af595f --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt @@ -0,0 +1,78 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.loading.compose + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel +import com.splendo.kaluga.hud.hudBuilder +import com.splendo.kaluga.resources.compose.Composable + +class ComposeLoadingActivity : AppCompatActivity() { + @SuppressLint("MissingSuperCall") // Lint bug + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider( + LocalAppCompatActivity provides this + ) { + LoadingLayout() + } + } + } +} + +@Composable +fun LoadingLayout() { + MdcTheme { + val activity = LocalAppCompatActivity.current!! + val viewModel = storeAndRemember { + HudViewModel(activity.hudBuilder()) + } + + ViewModelComposable(viewModel) { + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + showSystemButton.Composable(modifier = Modifier.fillMaxWidth()) + showCustomButton.Composable(modifier = Modifier.fillMaxWidth()) + } + } + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/loading/xml/XMLLoadingActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/loading/xml/XMLLoadingActivity.kt new file mode 100644 index 000000000..23da2a9fb --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/loading/xml/XMLLoadingActivity.kt @@ -0,0 +1,41 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.loading.xml + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityLoadingBinding +import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +@SuppressLint("SetTextI18n") +class XMLLoadingActivity : KalugaViewModelActivity() { + + override val viewModel: HudViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityLoadingBinding.inflate(LayoutInflater.from(this), null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt index 0ef45e6d2..e8d73e09d 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/location/LocationActivity.kt @@ -20,16 +20,15 @@ package com.splendo.kaluga.example.location import android.content.Intent import android.os.Bundle -import androidx.appcompat.widget.AppCompatButton -import androidx.appcompat.widget.AppCompatTextView +import android.view.LayoutInflater import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivityLocationBinding import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel import com.splendo.kaluga.permissions.location.LocationPermission import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class LocationActivity : KalugaViewModelActivity(R.layout.activity_location) { +class LocationActivity : KalugaViewModelActivity() { companion object { private val permission = LocationPermission(background = false, precise = true) @@ -42,16 +41,21 @@ class LocationActivity : KalugaViewModelActivity(R.layout.act override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - findViewById(R.id.enable_background).setOnClickListener { + val binding = ActivityLocationBinding.inflate(LayoutInflater.from(this), null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + + binding.enableBackground.setOnClickListener { startService(Intent(applicationContext, LocationBackgroundService::class.java)) } - findViewById(R.id.disable_background).setOnClickListener { + binding.disableBackground.setOnClickListener { stopService(Intent(applicationContext, LocationBackgroundService::class.java)) } viewModel.location.observeInitialized { - val info = findViewById(R.id.info) + val info = binding.info info.text = it info.animate().withEndAction { info.animate().setDuration(10000).alpha(0.12f).start() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt similarity index 67% rename from example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt index 0738ec653..f822454de 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt @@ -18,20 +18,19 @@ package com.splendo.kaluga.example.permissions import android.os.Bundle -import android.view.View -import android.widget.TextView +import android.view.LayoutInflater import android.widget.Toast -import androidx.appcompat.widget.AppCompatButton import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivityPermissionBinding import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class PermissionsDemoActivity : KalugaViewModelActivity(R.layout.activity_permissions_demo) { +class PermissionActivity : KalugaViewModelActivity() { override val viewModel: PermissionViewModel by viewModel { intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.let { permissionView -> @@ -42,17 +41,12 @@ class PermissionsDemoActivity : KalugaViewModelActivity(R.l override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - supportActionBar?.title = intent.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))?.title + val binding = ActivityPermissionBinding.inflate(LayoutInflater.from(this), null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) - viewModel.permissionStateMessage.observe { - findViewById(R.id.permissions_message).text = it - } - - viewModel.showPermissionButton.observe { - findViewById(R.id.btn_permissions_bluetooth_request_permissions).visibility = if (it == true) View.VISIBLE else View.GONE - } - - findViewById(R.id.btn_permissions_bluetooth_request_permissions).setOnClickListener { viewModel.requestPermission() } + supportActionBar?.title = viewModel.title viewModel.requestMessage.observeInitialized { message -> if (message != null) Toast.makeText(this, message, Toast.LENGTH_SHORT).show() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsListActivity.kt similarity index 77% rename from example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsListActivity.kt index 48fdd892b..ec3f4fdcb 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoListActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsListActivity.kt @@ -21,23 +21,25 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import androidx.appcompat.widget.AppCompatButton +import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivityPermissionsListBinding import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListNavigationAction import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class PermissionsDemoListActivity : KalugaViewModelActivity(R.layout.activity_permissions_list) { +class PermissionsListActivity : KalugaViewModelActivity() { override val viewModel: PermissionsListViewModel by viewModel { parametersOf( ActivityNavigator { - NavigationSpec.Activity() + NavigationSpec.Activity() } ) } @@ -45,15 +47,25 @@ class PermissionsDemoListActivity : KalugaViewModelActivity(R.id.permissions_list).adapter = this - } - viewModel.permissions.observeInitialized { adapter.permissions = it } + val binding = ActivityPermissionsListBinding.inflate(LayoutInflater.from(this), null, false) + binding.permissionsList.adapter = PermissionsAdapter(viewModel) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) } } class PermissionsAdapter(private val viewModel: PermissionsListViewModel) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("permissions") + @JvmStatic + fun bindPermissions(view: RecyclerView, permissions: List?) { + val adapter = (view.adapter as? PermissionsAdapter) ?: return + adapter.permissions = permissions.orEmpty() + } + } + class PermissionsViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button) var permissions: List = emptyList() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt index a587071fb..2861e0680 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/ResourcesActivity.kt @@ -1,75 +1,24 @@ -package com.splendo.kaluga.example.resources - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityResourcesBinding -import com.splendo.kaluga.example.databinding.ViewListButtonBinding -import com.splendo.kaluga.example.shared.viewmodel.resources.Resource -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class ResourcesActivity : KalugaViewModelActivity() { - override val viewModel: ResourcesListViewModel by viewModel { - parametersOf( - ActivityNavigator { action -> - when (action) { - is ResourcesListNavigationAction.Label -> NavigationSpec.Activity() - is ResourcesListNavigationAction.Color -> NavigationSpec.Activity() - is ResourcesListNavigationAction.Button -> NavigationSpec.Activity() - } - } - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands - val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) - setContentView(binding.root) + 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 - val adapter = ResourcesAdapter(viewModel).apply { + http://www.apache.org/licenses/LICENSE-2.0 - binding.resourcesList.adapter = this - } - viewModel.resources.observeInitialized { adapter.resources = it } - } -} + 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. -class ResourcesAdapter(private val viewModel: ResourcesListViewModel) : - RecyclerView.Adapter() { + */ - class ResourceViewHolder(val binding: ViewListButtonBinding) : - RecyclerView.ViewHolder(binding.root) { - val button = binding.button - } - - var resources: List = emptyList() - set(newValue) { - field = newValue - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResourceViewHolder { - val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return ResourceViewHolder(binding) - } +package com.splendo.kaluga.example.resources - override fun getItemCount(): Int = resources.size +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity +import com.splendo.kaluga.example.resources.compose.ComposeResourcesActivity +import com.splendo.kaluga.example.resources.xml.XMLResourcesActivity - override fun onBindViewHolder(holder: ResourceViewHolder, position: Int) { - resources.getOrNull(position)?.let { resource -> - holder.button.text = resource.title - holder.button.setOnClickListener { viewModel.onResourceSelected(resource) } - } ?: run { - holder.button.text = null - holder.button.setOnClickListener(null) - } - } -} +class ResourcesActivity : ComposeOrXMLActivity(ComposeResourcesActivity::class.java, XMLResourcesActivity::class.java) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt new file mode 100644 index 000000000..076f9e170 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt @@ -0,0 +1,60 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.splendo.kaluga.alerts.alertPresenterBuilder +import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel +import com.splendo.kaluga.resources.StyledStringBuilder +import com.splendo.kaluga.resources.compose.Composable + +@Composable +fun ButtonsLayout() { + val activity = LocalAppCompatActivity.current!! + val viewModel = storeAndRemember { + ButtonViewModel(StyledStringBuilder.Provider(), activity.alertPresenterBuilder()) + } + + ViewModelComposable(viewModel) { + val buttons by buttons.state() + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + buttons.forEach { + it.Composable(modifier = Modifier.fillMaxWidth()) + } + } + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt new file mode 100644 index 000000000..cad02e808 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt @@ -0,0 +1,161 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources.compose + +import android.view.KeyEvent +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import com.splendo.kaluga.alerts.alertPresenterBuilder +import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel +import com.splendo.kaluga.resources.compose.Composable +import com.splendo.kaluga.resources.compose.backgroundStyle +import com.splendo.kaluga.resources.stylable.BackgroundStyle + +@Composable +fun ColorsLayout() { + val activity = LocalAppCompatActivity.current!! + val viewModel = storeAndRemember { + ColorViewModel(activity.alertPresenterBuilder()) + } + + ViewModelComposable(viewModel) { + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + val mainColorSize = 80.dp + val backdropText by backdropText.state() + val sourceText by sourceText.state() + val backdropColorBackground by backdropColorBackground.state() + val blendedColorBackground by blendedColorBackground.state() + val sourceColorBackground by sourceColorBackground.state() + val blendModeButton by blendModeButton.state() + val lightenBackdrops by lightenBackdrops.state() + val darkenBackdrops by darkenBackdrops.state() + val lightenSource by lightenSource.state() + val darkenSource by darkenSource.state() + val lightenBlended by lightenBlended.state() + val darkenBlended by darkenBlended.state() + + val focusManager = LocalFocusManager.current + + Row(Modifier.fillMaxWidth()) { + Column(Modifier.width(mainColorSize)) { + var backdropTempText by remember { mutableStateOf(backdropText) } + Box(modifier = Modifier + .size(mainColorSize) + .backgroundStyle(backdropColorBackground)) + OutlinedTextField( + value = backdropTempText, + onValueChange = { backdropTempText = it }, + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { if (!it.hasFocus) submitBackdropText(backdropTempText) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { focusManager.clearFocus() } + ) + ) + } + Spacer(modifier = Modifier.weight(1.0f)) + Box(modifier = Modifier + .size(mainColorSize) + .backgroundStyle(blendedColorBackground)) + Spacer(modifier = Modifier.weight(1.0f)) + Column(Modifier.width(mainColorSize)) { + var sourceTempText by remember { mutableStateOf(sourceText) } + Box(modifier = Modifier + .size(mainColorSize) + .backgroundStyle(sourceColorBackground)) + OutlinedTextField( + value = sourceTempText, + onValueChange = { sourceTempText = it }, + modifier = Modifier + .fillMaxWidth() + .onFocusChanged { if (!it.hasFocus) submitSourceText(sourceTempText) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions( + onDone = { focusManager.clearFocus() } + ) + ) + } + } + + blendModeButton.Composable(modifier = Modifier.fillMaxWidth()) + flipButton.Composable(modifier = Modifier.fillMaxWidth()) + + ListOfColors(lightenBackdrops) + ListOfColors(darkenBackdrops) + ListOfColors(lightenSource) + ListOfColors(darkenSource) + ListOfColors(lightenBlended) + ListOfColors(darkenBlended) + } + } +} + +@Composable +fun ListOfColors(list: List) { + Row( + Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState())) { + list.forEach { + Box(modifier = Modifier + .size(40.dp) + .backgroundStyle(it)) + } + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt new file mode 100644 index 000000000..ab6db0c93 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt @@ -0,0 +1,57 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel +import com.splendo.kaluga.resources.StyledStringBuilder +import com.splendo.kaluga.resources.compose.Composable + +@Composable +fun LabelsLayout() { + val viewModel = storeAndRemember { + LabelViewModel(StyledStringBuilder.Provider()) + } + + ViewModelComposable(viewModel) { + val labels by labels.state() + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + labels.forEach { + it.Composable(modifier = Modifier.fillMaxWidth()) + } + } + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt new file mode 100644 index 000000000..c17eb39be --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt @@ -0,0 +1,113 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources.compose + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel +import com.splendo.kaluga.architecture.compose.navigation.SetupNavHost +import com.splendo.kaluga.architecture.compose.navigation.next +import com.splendo.kaluga.architecture.compose.navigation.route +import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.example.compose.Constants + +class ComposeResourcesActivity : AppCompatActivity() { + @SuppressLint("MissingSuperCall") // Lint bug + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider( + LocalAppCompatActivity provides this + ) { + ResourcesLayout() + } + } + } +} + +@Composable +fun ResourcesLayout() { + MdcTheme { + val navigator = RouteNavigator(rememberNavController()) { action -> + when (action) { + is ResourcesListNavigationAction.Button -> action.next + is ResourcesListNavigationAction.Color -> action.next + is ResourcesListNavigationAction.Label -> action.next + } + } + + navigator.SetupNavHost( + rootView = { + ResourcesListLayout(navigator = navigator) + } + ) { + composable(ResourcesListNavigationAction.Button.route()) { ButtonsLayout() } + composable(ResourcesListNavigationAction.Color.route()) { ColorsLayout() } + composable(ResourcesListNavigationAction.Label.route()) { LabelsLayout() } + } + } +} + +@Composable +fun ResourcesListLayout(navigator: Navigator) { + val viewModel = storeAndRemember { + ResourcesListViewModel(navigator) + } + ViewModelComposable(viewModel) { + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + val resources by resources.state() + resources.forEach { + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { onResourceSelected(it) }) { + Text(it.title) + } + } + } + } +} \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ButtonsActivity.kt similarity index 58% rename from example/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ButtonsActivity.kt index 9a38060f6..e914534b1 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ButtonsActivity.kt @@ -1,27 +1,29 @@ /* - * Copyright 2022 Splendo Consulting B.V. The Netherlands - * - * 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. + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources +package com.splendo.kaluga.example.resources.xml import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityResourcesBinding +import com.splendo.kaluga.example.databinding.ActivityButtonBinding import com.splendo.kaluga.example.databinding.ViewListButtonBinding import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration @@ -36,20 +38,26 @@ class ButtonActivity : KalugaViewModelActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) + val binding = ActivityButtonBinding.inflate(layoutInflater, null, false) + binding.buttonsList.adapter = ButtonAdapter() + binding.viewModel = viewModel + binding.lifecycleOwner = this setContentView(binding.root) - - val adapter = ButtonAdapter().apply { - - binding.resourcesList.adapter = this - } - viewModel.buttons.observeInitialized { adapter.buttons = it } - binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) + binding.buttonsList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) } } class ButtonAdapter : RecyclerView.Adapter() { + companion object { + @BindingAdapter("buttons") + @JvmStatic + fun bindButtons(view: RecyclerView, buttons: List?) { + val adapter = (view.adapter as? ButtonAdapter) ?: return + adapter.buttons = buttons.orEmpty() + } + } + class ButtonViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { val button = binding.button } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ColorsActivity.kt similarity index 63% rename from example/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ColorsActivity.kt index 105ffca09..b3b296c9c 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/ColorActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ColorsActivity.kt @@ -1,28 +1,26 @@ /* - * Copyright 2022 Splendo Consulting B.V. The Netherlands - * - * 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. + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources +package com.splendo.kaluga.example.resources.xml import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo -import android.widget.Button -import android.widget.TextView import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity @@ -31,11 +29,7 @@ import com.splendo.kaluga.example.databinding.ViewResourceListBackgroundBinding import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel import com.splendo.kaluga.resources.DefaultColors import com.splendo.kaluga.resources.stylable.BackgroundStyle -import com.splendo.kaluga.resources.view.KalugaButton -import com.splendo.kaluga.resources.view.KalugaLabel import com.splendo.kaluga.resources.view.applyBackgroundStyle -import com.splendo.kaluga.resources.view.bindButton -import com.splendo.kaluga.resources.view.bindLabel import org.koin.androidx.viewmodel.ext.android.viewModel class ColorActivity : KalugaViewModelActivity() { @@ -78,6 +72,15 @@ class ColorActivity : KalugaViewModelActivity() { class BackgroundAdapter : RecyclerView.Adapter() { + companion object { + @BindingAdapter("backgroundStyles") + @JvmStatic + fun bindBackgroundStyles(view: RecyclerView, backgroundStyles: List?) { + val adapter = (view.adapter as? BackgroundAdapter) ?: return + adapter.backgrounds = backgroundStyles.orEmpty() + } + } + class BackgroundViewHolder(val binding: ViewResourceListBackgroundBinding) : RecyclerView.ViewHolder(binding.root) { val view = binding.root } @@ -103,37 +106,3 @@ class BackgroundAdapter : RecyclerView.Adapter?) { - val adapter = recyclerView.adapter as? BackgroundAdapter ?: return - adapter.backgrounds = backgrounds ?: emptyList() - } -} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/LabelsActivity.kt similarity index 57% rename from example/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt rename to example/android/src/main/java/com/splendo/kaluga/example/resources/xml/LabelsActivity.kt index 9addfb939..b761ad0e5 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/LabelActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/LabelsActivity.kt @@ -1,27 +1,29 @@ /* - * Copyright 2022 Splendo Consulting B.V. The Netherlands - * - * 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. + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources +package com.splendo.kaluga.example.resources.xml import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.databinding.ActivityResourcesBinding +import com.splendo.kaluga.example.databinding.ActivityLabelBinding import com.splendo.kaluga.example.databinding.ViewListTextViewBinding import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration @@ -36,20 +38,26 @@ class LabelActivity : KalugaViewModelActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) + val binding = ActivityLabelBinding.inflate(layoutInflater, null, false) + binding.labelsList.adapter = LabelAdapter() + binding.viewModel = viewModel + binding.lifecycleOwner = this setContentView(binding.root) - - val adapter = LabelAdapter().apply { - - binding.resourcesList.adapter = this - } - binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) - viewModel.labels.observeInitialized { adapter.labels = it } + binding.labelsList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) } } class LabelAdapter : RecyclerView.Adapter() { + companion object { + @BindingAdapter("labels") + @JvmStatic + fun bindLabels(view: RecyclerView, labels: List?) { + val adapter = (view.adapter as? LabelAdapter) ?: return + adapter.labels = labels.orEmpty() + } + } + class LabelViewHolder(val binding: ViewListTextViewBinding) : RecyclerView.ViewHolder(binding.root) { val label = binding.label } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/XMLResourcesActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/XMLResourcesActivity.kt new file mode 100644 index 000000000..71b32caf5 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/XMLResourcesActivity.kt @@ -0,0 +1,99 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.resources.xml + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityResourcesBinding +import com.splendo.kaluga.example.databinding.ViewListButtonBinding +import com.splendo.kaluga.example.shared.viewmodel.resources.Resource +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class XMLResourcesActivity : KalugaViewModelActivity() { + override val viewModel: ResourcesListViewModel by viewModel { + parametersOf( + ActivityNavigator { action -> + when (action) { + is ResourcesListNavigationAction.Label -> NavigationSpec.Activity() + is ResourcesListNavigationAction.Color -> NavigationSpec.Activity() + is ResourcesListNavigationAction.Button -> NavigationSpec.Activity() + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false) + binding.resourcesList.adapter = ResourcesAdapter(viewModel) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + } +} + +class ResourcesAdapter(private val viewModel: ResourcesListViewModel) : + RecyclerView.Adapter() { + + companion object { + @BindingAdapter("resources") + @JvmStatic + fun bindResources(view: RecyclerView, resources: List?) { + val adapter = (view.adapter as? ResourcesAdapter) ?: return + adapter.resources = resources.orEmpty() + } + } + + class ResourceViewHolder(val binding: ViewListButtonBinding) : + RecyclerView.ViewHolder(binding.root) { + val button = binding.button + } + + var resources: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResourceViewHolder { + val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ResourceViewHolder(binding) + } + + override fun getItemCount(): Int = resources.size + + override fun onBindViewHolder(holder: ResourceViewHolder, position: Int) { + resources.getOrNull(position)?.let { resource -> + holder.button.text = resource.title + holder.button.setOnClickListener { viewModel.onResourceSelected(resource) } + } ?: run { + holder.button.text = null + holder.button.setOnClickListener(null) + } + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt index 4a1a63290..631ef9267 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/system/SystemActivity.kt @@ -20,11 +20,12 @@ package com.splendo.kaluga.example.system import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.architecture.navigation.ActivityNavigator import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R +import com.splendo.kaluga.example.databinding.ActivitySystemBinding import com.splendo.kaluga.example.databinding.ViewListButtonBinding import com.splendo.kaluga.example.shared.viewmodel.system.SystemFeatures import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions @@ -32,7 +33,7 @@ import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class SystemActivity : KalugaViewModelActivity(R.layout.activity_system) { +class SystemActivity : KalugaViewModelActivity() { override val viewModel: SystemViewModel by viewModel { parametersOf( ActivityNavigator { action -> @@ -46,13 +47,11 @@ class SystemActivity : KalugaViewModelActivity(R.layout.activit override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val adapter = SystemFeatureAdapter(viewModel).apply { - findViewById(R.id.system_features_list).adapter = this - } - - viewModel.modules.observeInitialized { - adapter.modules = it - } + val binding = ActivitySystemBinding.inflate(LayoutInflater.from(this), null, false) + binding.systemFeaturesList.adapter = SystemFeatureAdapter(viewModel) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) } } @@ -60,11 +59,20 @@ class SystemFeatureAdapter( private val viewModel: SystemViewModel ) : RecyclerView.Adapter() { + companion object { + @BindingAdapter("systemFeatures") + @JvmStatic + fun bindSystemFeatures(view: RecyclerView, resources: List?) { + val adapter = (view.adapter as? SystemFeatureAdapter) ?: return + adapter.systemFeatures = resources.orEmpty() + } + } + inner class SystemFeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) { val button = binding.button } - var modules: List = emptyList() + var systemFeatures: List = emptyList() set(newValue) { field = newValue notifyDataSetChanged() @@ -76,7 +84,7 @@ class SystemFeatureAdapter( } override fun onBindViewHolder(holder: SystemFeatureViewHolder, position: Int) { - modules.getOrNull(position)?.let { feature -> + systemFeatures.getOrNull(position)?.let { feature -> holder.button.text = feature.name holder.button.setOnClickListener { viewModel.onButtonTapped(feature) } } ?: run { @@ -85,5 +93,5 @@ class SystemFeatureAdapter( } } - override fun getItemCount(): Int = modules.size + override fun getItemCount(): Int = systemFeatures.size } diff --git a/example/android/src/main/res/layout/activity_alerts.xml b/example/android/src/main/res/layout/activity_alerts.xml index 74ea5dea3..8176a84d6 100644 --- a/example/android/src/main/res/layout/activity_alerts.xml +++ b/example/android/src/main/res/layout/activity_alerts.xml @@ -1,39 +1,50 @@ - - - + + + + + android:layout_height="match_parent"> - + android:orientation="vertical" + android:padding="10dp"> - + - + - + + + - + - + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_architecture_details.xml b/example/android/src/main/res/layout/activity_architecture_details.xml index 584254b5a..eda34f47b 100644 --- a/example/android/src/main/res/layout/activity_architecture_details.xml +++ b/example/android/src/main/res/layout/activity_architecture_details.xml @@ -34,20 +34,18 @@ android:id="@+id/inverse_button" android:layout_width="match_parent" android:layout_height="wrap_content" - android:onClick="@{view -> viewModel.onInversePressed()}" app:layout_constraintTop_toBottomOf="@id/number" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - android:text="@string/architecture_details_inverse"/> + app:kalugaButton="@{viewModel.inverseButton}"/> + app:kalugaButton="@{viewModel.finishButton}"/> \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_architecture_input.xml b/example/android/src/main/res/layout/activity_architecture_input.xml index c13f8c6c5..cb65056fe 100644 --- a/example/android/src/main/res/layout/activity_architecture_input.xml +++ b/example/android/src/main/res/layout/activity_architecture_input.xml @@ -65,7 +65,8 @@ app:kalugaButton="@{viewModel.showBottomSheetButton}" app:layout_constraintTop_toBottomOf="@id/show_details_button" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"/> + app:layout_constraintEnd_toEndOf="parent" + android:layout_marginTop="10dp"/> \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_button.xml b/example/android/src/main/res/layout/activity_button.xml new file mode 100644 index 000000000..71b53f2d6 --- /dev/null +++ b/example/android/src/main/res/layout/activity_button.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_compose_or_android_ui.xml b/example/android/src/main/res/layout/activity_compose_or_android_ui.xml new file mode 100644 index 000000000..ff40589c6 --- /dev/null +++ b/example/android/src/main/res/layout/activity_compose_or_android_ui.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_date_time_picker.xml b/example/android/src/main/res/layout/activity_date_time_picker.xml index f92a40694..891c345a7 100644 --- a/example/android/src/main/res/layout/activity_date_time_picker.xml +++ b/example/android/src/main/res/layout/activity_date_time_picker.xml @@ -1,5 +1,5 @@ - + + android:orientation="vertical" + android:padding="10dp"> + app:kalugaButton="@{viewModel.selectDateButton}"/> + app:kalugaButton="@{viewModel.selectTimeButton}" + android:layout_marginTop="10dp"/> + android:text="@{viewModel.currentTimeTitle}"/> - - - + + + + - - + android:layout_height="match_parent"> - + android:orientation="vertical"> - + + + + + - + - + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_label.xml b/example/android/src/main/res/layout/activity_label.xml new file mode 100644 index 000000000..c6f98eebb --- /dev/null +++ b/example/android/src/main/res/layout/activity_label.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_loading.xml b/example/android/src/main/res/layout/activity_loading.xml index 7a18b8ce4..2722291fa 100644 --- a/example/android/src/main/res/layout/activity_loading.xml +++ b/example/android/src/main/res/layout/activity_loading.xml @@ -1,27 +1,34 @@ - - - + + + + + android:layout_height="match_parent"> - + android:orientation="vertical"> - + + + - + - + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_location.xml b/example/android/src/main/res/layout/activity_location.xml index 12715da9c..1d3606d13 100644 --- a/example/android/src/main/res/layout/activity_location.xml +++ b/example/android/src/main/res/layout/activity_location.xml @@ -1,5 +1,10 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_permissions_demo.xml b/example/android/src/main/res/layout/activity_permissions_demo.xml deleted file mode 100644 index e76948d19..000000000 --- a/example/android/src/main/res/layout/activity_permissions_demo.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_permissions_list.xml b/example/android/src/main/res/layout/activity_permissions_list.xml index 9b358e3bb..a18fb68bc 100644 --- a/example/android/src/main/res/layout/activity_permissions_list.xml +++ b/example/android/src/main/res/layout/activity_permissions_list.xml @@ -1,9 +1,17 @@ - + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_resources.xml b/example/android/src/main/res/layout/activity_resources.xml index df84bfee3..d5e34f08e 100644 --- a/example/android/src/main/res/layout/activity_resources.xml +++ b/example/android/src/main/res/layout/activity_resources.xml @@ -1,5 +1,10 @@ + + + + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:resources="@{viewModel.resources.stateFlow}"> diff --git a/example/android/src/main/res/layout/activity_resources_color.xml b/example/android/src/main/res/layout/activity_resources_color.xml index 0c73058f8..94bb0c0cd 100644 --- a/example/android/src/main/res/layout/activity_resources_color.xml +++ b/example/android/src/main/res/layout/activity_resources_color.xml @@ -61,7 +61,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" - app:button="@{viewModel.blendModeButton.stateFlow}" + app:kalugaButton="@{viewModel.blendModeButton.stateFlow}" app:layout_constraintStart_toStartOf="@id/blended" app:layout_constraintEnd_toEndOf="@id/blended" app:layout_constraintTop_toBottomOf="@id/blended"/> @@ -71,7 +71,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" - app:button="@{viewModel.flipButton}" + app:kalugaButton="@{viewModel.flipButton}" app:layout_constraintStart_toStartOf="@id/blended" app:layout_constraintEnd_toEndOf="@id/blended" app:layout_constraintTop_toBottomOf="@id/blend_mode"/> @@ -97,7 +97,7 @@ app:layout_constraintTop_toBottomOf="@id/flip" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:backgrounds="@{viewModel.lightenBackdrops.stateFlow}" + app:backgroundStyles="@{viewModel.lightenBackdrops.stateFlow}" tools:listitem="@layout/view_resource_list_background" /> @@ -109,7 +109,7 @@ app:layout_constraintTop_toBottomOf="@id/backdrop_lighten" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:backgrounds="@{viewModel.darkenBackdrops.stateFlow}" + app:backgroundStyles="@{viewModel.darkenBackdrops.stateFlow}" tools:listitem="@layout/view_resource_list_background" /> @@ -121,7 +121,7 @@ app:layout_constraintTop_toBottomOf="@id/backdrop_darken" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:backgrounds="@{viewModel.lightenSource.stateFlow}" + app:backgroundStyles="@{viewModel.lightenSource.stateFlow}" tools:listitem="@layout/view_resource_list_background" /> @@ -133,7 +133,7 @@ app:layout_constraintTop_toBottomOf="@id/source_lighten" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:backgrounds="@{viewModel.darkenSource.stateFlow}" + app:backgroundStyles="@{viewModel.darkenSource.stateFlow}" tools:listitem="@layout/view_resource_list_background" /> @@ -145,7 +145,7 @@ app:layout_constraintTop_toBottomOf="@id/source_darken" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:backgrounds="@{viewModel.lightenBlended.stateFlow}" + app:backgroundStyles="@{viewModel.lightenBlended.stateFlow}" tools:listitem="@layout/view_resource_list_background" /> @@ -157,7 +157,7 @@ app:layout_constraintTop_toBottomOf="@id/blended_lighten" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:backgrounds="@{viewModel.darkenBlended.stateFlow}" + app:backgroundStyles="@{viewModel.darkenBlended.stateFlow}" tools:listitem="@layout/view_resource_list_background" /> diff --git a/example/android/src/main/res/layout/activity_system.xml b/example/android/src/main/res/layout/activity_system.xml index 77a494611..6fcd59fdf 100644 --- a/example/android/src/main/res/layout/activity_system.xml +++ b/example/android/src/main/res/layout/activity_system.xml @@ -1,33 +1,25 @@ - - - - + + + + + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_gravity="center_horizontal" + android:layout_margin="@dimen/networkLayoutsMargin"> + + - \ No newline at end of file + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/fragment_bottom_sheet.xml b/example/android/src/main/res/layout/fragment_bottom_sheet.xml new file mode 100644 index 000000000..0c8adee8f --- /dev/null +++ b/example/android/src/main/res/layout/fragment_bottom_sheet.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/fragment_bottom_sheet_root.xml b/example/android/src/main/res/layout/fragment_bottom_sheet_root.xml new file mode 100644 index 000000000..137e1ce4c --- /dev/null +++ b/example/android/src/main/res/layout/fragment_bottom_sheet_root.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/fragment_bottom_sheet_sub_page.xml b/example/android/src/main/res/layout/fragment_bottom_sheet_sub_page.xml new file mode 100644 index 000000000..1625f839f --- /dev/null +++ b/example/android/src/main/res/layout/fragment_bottom_sheet_sub_page.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/fragment_features_list.xml b/example/android/src/main/res/layout/fragment_features_list.xml index 25d5138a2..e101f7efd 100644 --- a/example/android/src/main/res/layout/fragment_features_list.xml +++ b/example/android/src/main/res/layout/fragment_features_list.xml @@ -1,9 +1,18 @@ - + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/fragment_info.xml b/example/android/src/main/res/layout/fragment_info.xml index 1f333028f..798d885f2 100644 --- a/example/android/src/main/res/layout/fragment_info.xml +++ b/example/android/src/main/res/layout/fragment_info.xml @@ -1,9 +1,17 @@ - + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/example/android/src/main/res/values/strings.xml b/example/android/src/main/res/values/strings.xml index b36b28e53..57804e927 100644 --- a/example/android/src/main/res/values/strings.xml +++ b/example/android/src/main/res/values/strings.xml @@ -112,4 +112,6 @@ This is a bottom sheet Sheet Sub Page + Compose Implementation + XML Implementation diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt index 7de7b218d..0c82a2d00 100644 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -30,11 +30,17 @@ import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDeta import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureNavigationAction import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetails +import com.splendo.kaluga.example.shared.viewmodel.compose.ComposeOrXMLNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.compose.ComposeOrXMLSelectionViewModel import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel @@ -88,6 +94,10 @@ internal val androidModule = module { ) } + viewModel { (navigator: Navigator) -> + ComposeOrXMLSelectionViewModel(navigator) + } + viewModel { (navigator: Navigator) -> PermissionsListViewModel(navigator) } @@ -107,6 +117,14 @@ internal val androidModule = module { ) } + viewModel { (navigator: Navigator) -> + BottomSheetViewModel(navigator) + } + + viewModel { (navigator: Navigator) -> + BottomSheetSubPageViewModel(navigator) + } + viewModel { AlertViewModel(AlertPresenter.Builder()) } diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrAndroidUISelectionViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrXMLSelectionViewModel.kt similarity index 67% rename from example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrAndroidUISelectionViewModel.kt rename to example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrXMLSelectionViewModel.kt index 2761e90f7..dc9f30a63 100644 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrAndroidUISelectionViewModel.kt +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrXMLSelectionViewModel.kt @@ -24,32 +24,32 @@ import com.splendo.kaluga.architecture.observable.observableOf import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel import com.splendo.kaluga.resources.localized -sealed class ComposeOrAndroidUINavigationAction : SingleValueNavigationAction(Unit, NavigationBundleSpecType.UnitType) { - object Compose : ComposeOrAndroidUINavigationAction() - object AndroidUI : ComposeOrAndroidUINavigationAction() +sealed class ComposeOrXMLNavigationAction : SingleValueNavigationAction(Unit, NavigationBundleSpecType.UnitType) { + object Compose : ComposeOrXMLNavigationAction() + object XML : ComposeOrXMLNavigationAction() } sealed class UIType(val title: String) { object Compose : UIType("android_ui_compose".localized()) - object AndroidUI : UIType("android_ui_legacy".localized()) + object XML : UIType("android_ui_xml".localized()) } -class ComposeOrAndroidUISelectionViewModel( - navigator: Navigator -) : NavigatingViewModel(navigator) { +class ComposeOrXMLSelectionViewModel( + navigator: Navigator +) : NavigatingViewModel(navigator) { val uiTypes = observableOf( listOf( UIType.Compose, - UIType.AndroidUI + UIType.XML ) ) fun onUITypePressed(feature: UIType) { navigator.navigate( when (feature) { - is UIType.Compose -> ComposeOrAndroidUINavigationAction.Compose - is UIType.AndroidUI -> ComposeOrAndroidUINavigationAction.AndroidUI + is UIType.Compose -> ComposeOrXMLNavigationAction.Compose + is UIType.XML -> ComposeOrXMLNavigationAction.XML } ) } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt index ead0a8223..fc82372e8 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt @@ -28,11 +28,11 @@ object ButtonStyles { val default by lazy { ButtonStyle( - TextStyles.whiteText, - backgroundColor = DefaultColors.mediumBlue, - pressedBackgroundColor = DefaultColors.darkBlue, - disabledBackgroundColor = DefaultColors.lightSlateGray, - shape = BackgroundStyle.Shape.Rectangle(10.0f) + TextStyles.defaultTitle, + backgroundColor = DefaultColors.lightGray, + pressedBackgroundColor = DefaultColors.gray, + disabledBackgroundColor = DefaultColors.dimGray, + shape = BackgroundStyle.Shape.Rectangle(4.0f) ) } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt index 5fe15bade..ef647044d 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt @@ -24,13 +24,18 @@ import com.splendo.kaluga.alerts.buildActionSheet import com.splendo.kaluga.alerts.buildAlert import com.splendo.kaluga.alerts.buildAlertWithInput import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.example.shared.stylable.ButtonStyles import com.splendo.kaluga.logging.debug +import com.splendo.kaluga.resources.localized +import com.splendo.kaluga.resources.view.KalugaButton import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds -class AlertViewModel(val builder: BaseAlertPresenter.Builder) : BaseLifecycleViewModel() { +class AlertViewModel(val builder: BaseAlertPresenter.Builder, dismissTime: Duration = 3.seconds) : BaseLifecycleViewModel() { - fun showAlert() { + val showAlertButton = KalugaButton.Plain("show_alert".localized(), ButtonStyles.default) { coroutineScope.launch { val okAction = Alert.Action("OK", Alert.Action.Style.POSITIVE) val cancelAction = Alert.Action("Cancel", Alert.Action.Style.NEGATIVE) @@ -46,22 +51,22 @@ class AlertViewModel(val builder: BaseAlertPresenter.Builder) : BaseLifecycleVie } } - fun showAndDismissAfter(timeSecs: Long) { + val showAndDismissAfter3SecondsButton = KalugaButton.Plain("dismissible_alert".localized(), ButtonStyles.default) { coroutineScope.launch { val job = launch { builder.buildAlert(coroutineScope) { - setTitle("Wait for $timeSecs sec...") + setTitle("Wait for ${dismissTime.inWholeSeconds} sec...") setPositiveButton("OK") }.show() } launch { - delay(timeSecs * 1_000) + delay(dismissTime) job.cancel() } } } - fun showAlertWithInput() { + val showAlertWithInputButton = KalugaButton.Plain("alert_list".localized(), ButtonStyles.default) { coroutineScope.launch { val okAction = Alert.Action("OK", Alert.Action.Style.POSITIVE) val cancelAction = Alert.Action("Cancel", Alert.Action.Style.NEGATIVE) @@ -80,7 +85,7 @@ class AlertViewModel(val builder: BaseAlertPresenter.Builder) : BaseLifecycleVie } } - fun showAlertWithList() { + val showAlertWithListButton = KalugaButton.Plain("alert_input".localized(), ButtonStyles.default) { coroutineScope.launch { builder.buildActionSheet(this) { setTitle("Select an option") diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt index 4e5e7066b..9128b50c2 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.kt @@ -23,6 +23,9 @@ import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.InitializedObservable import com.splendo.kaluga.architecture.observable.subjectOf import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.example.shared.stylable.ButtonStyles +import com.splendo.kaluga.resources.localized +import com.splendo.kaluga.resources.view.KalugaButton import kotlinx.serialization.Serializable @Serializable @@ -46,16 +49,16 @@ class ArchitectureDetailsViewModel(initialDetail: InputDetails, navigator: Navig private var nameResult: String by _name.valueDelegate private var numberResult: String by _number.valueDelegate - fun onInversePressed() { + val inverseButton = KalugaButton.Plain("architecture_details_inverse".localized(), ButtonStyles.default) { nameResult = nameResult.reversed() numberResult = numberResult.reversed() } - fun onBackPressed() { - navigator.navigate(ArchitectureDetailsNavigationAction.Close) + val finishButton = KalugaButton.Plain("architecture_finish".localized(), ButtonStyles.default) { + navigator.navigate(ArchitectureDetailsNavigationAction.FinishWithDetails(InputDetails(nameResult, numberResult.toIntOrNull() ?: 0))) } - fun onFinishPressed() { - navigator.navigate(ArchitectureDetailsNavigationAction.FinishWithDetails(InputDetails(nameResult, numberResult.toIntOrNull() ?: 0))) + fun onBackPressed() { + navigator.navigate(ArchitectureDetailsNavigationAction.Close) } } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt index 9993ff153..53a89bc80 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt @@ -21,6 +21,7 @@ import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.ObservableOptional +import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.observable.toInitializedSubject import com.splendo.kaluga.architecture.observable.toUninitializedObservable import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel @@ -55,14 +56,14 @@ class ArchitectureViewModel(navigator: Navigator it.isNotEmpty() } } - val isNameValid = _isNameValid.toUninitializedObservable(coroutineScope) + val isNameValid = _isNameValid.toInitializedObservable(false, coroutineScope) private val _isNumberValid: Flow get() { return _numberInput.map { it.toIntOrNull() != null } } - val isNumberValid = _isNumberValid.toUninitializedObservable(coroutineScope) + val isNumberValid = _isNumberValid.toInitializedObservable(false, coroutineScope) private val isValid = combine(_isNameValid, _isNumberValid) { validName, validNumber -> diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetViewModel.kt index 2836c4699..0a865f8fb 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetViewModel.kt @@ -20,7 +20,9 @@ package com.splendo.kaluga.example.shared.viewmodel.architecture import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.example.shared.stylable.ButtonStyles import com.splendo.kaluga.resources.localized +import com.splendo.kaluga.resources.view.KalugaButton sealed class BottomSheetNavigation : NavigationAction(null) { object Close : BottomSheetNavigation() @@ -30,9 +32,7 @@ sealed class BottomSheetNavigation : NavigationAction(null) { class BottomSheetViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { val text = "bottom_sheet_sheet_content".localized() - val buttonText = "bottom_sheet_show_sub_page".localized() - - fun onSubPagePressed() { + val button = KalugaButton.Plain("bottom_sheet_show_sub_page".localized(), ButtonStyles.default) { navigator.navigate(BottomSheetNavigation.SubPage) } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt index 0a72e95f4..8bfd03c1f 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt @@ -1,5 +1,6 @@ package com.splendo.kaluga.example.shared.viewmodel.datetimepicker +import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.observable.toUninitializedObservable import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import com.splendo.kaluga.base.text.DateFormatStyle @@ -8,7 +9,9 @@ import com.splendo.kaluga.base.utils.DefaultKalugaDate import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter import com.splendo.kaluga.datetimepicker.buildDatePicker import com.splendo.kaluga.datetimepicker.buildTimePicker +import com.splendo.kaluga.example.shared.stylable.ButtonStyles import com.splendo.kaluga.resources.localized +import com.splendo.kaluga.resources.view.KalugaButton import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -25,9 +28,10 @@ class DateTimePickerViewModel(val dateTimePickerPresenterBuilder: DateTimePicker millisecond = 0 } ) - val dateLabel = selectedDate.map { formatter.format(it) }.toUninitializedObservable(coroutineScope) + val currentTimeTitle = "current_time".localized() + val dateLabel = selectedDate.map { formatter.format(it) }.toInitializedObservable("", coroutineScope) - fun onSelectDatePressed() { + val selectDateButton = KalugaButton.Plain("select_date".localized(), ButtonStyles.default) { coroutineScope.launch { dateTimePickerPresenterBuilder.buildDatePicker( this, @@ -43,7 +47,7 @@ class DateTimePickerViewModel(val dateTimePickerPresenterBuilder: DateTimePicker } } - fun onSelectTimePressed() { + val selectTimeButton = KalugaButton.Plain("select_time".localized(), ButtonStyles.default) { coroutineScope.launch { dateTimePickerPresenterBuilder.buildTimePicker(this) { setSelectedDate(this@DateTimePickerViewModel.selectedDate.value) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt index 6f7d4d03f..01293ceb3 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt @@ -19,17 +19,20 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands package com.splendo.kaluga.example.shared.viewmodel.hud import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.example.shared.stylable.ButtonStyles import com.splendo.kaluga.hud.BaseHUD import com.splendo.kaluga.hud.HUDStyle import com.splendo.kaluga.hud.build import com.splendo.kaluga.hud.dismissAfter import com.splendo.kaluga.hud.presentDuring +import com.splendo.kaluga.resources.localized +import com.splendo.kaluga.resources.view.KalugaButton import kotlinx.coroutines.delay import kotlinx.coroutines.launch class HudViewModel(val builder: BaseHUD.Builder) : BaseLifecycleViewModel() { - fun onShowSystemPressed() { + val showSystemButton = KalugaButton.Plain("show_loading_indicator_system".localized(), ButtonStyles.default) { // SYSTEM style by default // No title by default coroutineScope.launch { @@ -37,7 +40,7 @@ class HudViewModel(val builder: BaseHUD.Builder) : BaseLifecycleViewModel() { } } - fun onShowCustomPressed() { + val showCustomButton = KalugaButton.Plain("show_loading_indicator_custom".localized(), ButtonStyles.default) { coroutineScope.launch { builder.build(this) { setStyle(HUDStyle.CUSTOM) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt index c177e9aa4..1fca5c7fb 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt @@ -18,18 +18,21 @@ package com.splendo.kaluga.example.shared.viewmodel.keyboard import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.example.shared.stylable.ButtonStyles import com.splendo.kaluga.keyboard.BaseKeyboardManager import com.splendo.kaluga.keyboard.FocusHandler +import com.splendo.kaluga.resources.localized +import com.splendo.kaluga.resources.view.KalugaButton class KeyboardViewModel(keyboardManagerBuilder: BaseKeyboardManager.Builder, private val editFieldFocusHandler: FocusHandler) : BaseLifecycleViewModel() { private val keyboardManager: BaseKeyboardManager = keyboardManagerBuilder.create(coroutineScope) - fun onShowPressed() { + val showButton = KalugaButton.Plain("show_keyboard".localized(), ButtonStyles.default) { keyboardManager.show(editFieldFocusHandler) } - fun onHidePressed() { + val hideButton = KalugaButton.Plain("hide_keyboard".localized(), ButtonStyles.default) { keyboardManager.hide() } } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt index 1872b1dbe..429fcacb5 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt @@ -38,6 +38,7 @@ private val permissionsDispatcher = singleThreadDispatcher("PermissionsDispatche class PermissionViewModel(private val permission: Permission) : BaseLifecycleViewModel(), KoinComponent { + val title = permission.name private val permissionsBuilder: PermissionsBuilder by inject() private val permissions = Permissions(permissionsBuilder, coroutineScope.coroutineContext + permissionsDispatcher) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt index bec3c7dfd..1a6ae21ba 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt @@ -36,7 +36,7 @@ class SystemViewModel( navigator: Navigator ) : NavigatingViewModel(navigator) { - val modules = + val systemFeatures = observableOf( listOf( SystemFeatures.Network diff --git a/kaluga-library-components/src/main/kotlin/Dependencies.kt b/kaluga-library-components/src/main/kotlin/Dependencies.kt index 15f243def..77faf8d0f 100644 --- a/kaluga-library-components/src/main/kotlin/Dependencies.kt +++ b/kaluga-library-components/src/main/kotlin/Dependencies.kt @@ -159,6 +159,7 @@ object Dependencies { private const val group = "io.insert-koin" private const val version = "3.2.2" val Android = Dependency(group, "koin-android", version) + val AndroidXCompose = Dependency(group, "koin-androidx-compose", version) val Core = Dependency(group, "koin-core", version) } diff --git a/keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt b/keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt index 02f1b0339..806a5dd6b 100644 --- a/keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt +++ b/keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt @@ -23,7 +23,7 @@ import com.splendo.kaluga.keyboard.ClearFocusHandler import kotlinx.coroutines.flow.StateFlow class ComposeClearFocusHandler(private val focusManager: StateFlow) : ClearFocusHandler { - override fun clearFocus(activity: Activity) { + override fun clearFocus(activity: Activity?) { focusManager.value.clearFocus(true) } } diff --git a/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt b/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt index 0b77bb506..e9342038b 100644 --- a/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt +++ b/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt @@ -20,11 +20,11 @@ package com.splendo.kaluga.keyboard import android.app.Activity interface ClearFocusHandler { - fun clearFocus(activity: Activity) + fun clearFocus(activity: Activity?) } class ViewClearFocusHandler : ClearFocusHandler { - override fun clearFocus(activity: Activity) { - activity.currentFocus?.clearFocus() + override fun clearFocus(activity: Activity?) { + activity?.currentFocus?.clearFocus() } } diff --git a/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt b/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt index 3fb0bbbbb..f331f5bdc 100644 --- a/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt @@ -40,14 +40,13 @@ actual class KeyboardManager( } override fun show(focusHandler: FocusHandler) { - lifecycleManagerObserver.manager?.activity?.let { - focusHandler.requestFocus(it) - } + focusHandler.requestFocus(lifecycleManagerObserver.manager?.activity) } override fun hide() { - lifecycleManagerObserver.manager?.activity?.let { activity -> - clearFocusHandler.clearFocus(activity) + val managedActivity = lifecycleManagerObserver.manager?.activity + clearFocusHandler.clearFocus(managedActivity) + managedActivity?.let { activity -> val inputMethodManager = activity.getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager inputMethodManager?.let { if (it.isAcceptingText) { From e039af4ed1dbedd6742a10801b48289e1e42d8a0 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 25 Nov 2022 14:33:37 +0100 Subject: [PATCH 035/227] Refactoring Navigation/Architecture for Compose To deal with Configuration changes --- .../ComposableLifecycleSubscribable.kt | 42 ++++ .../compose/navigation/CombinedNavigator.kt | 32 +-- .../navigation/ModalBottomSheetNavigator.kt | 222 +++++++++++------- .../compose/navigation/RouteNavigator.kt | 138 ++++++----- .../compose/viewModel/ViewModelComposable.kt | 95 +++++++- .../compose/ArchitectureDetailsLayout.kt | 18 +- .../compose/ArchitectureLayout.kt | 181 +++++++------- .../architecture/compose/BottomSheetLayout.kt | 23 +- .../compose/BottomSheetSubPageLayout.kt | 26 +- .../resources/compose/ResourcesLayout.kt | 72 +++--- 10 files changed, 489 insertions(+), 360 deletions(-) create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt new file mode 100644 index 000000000..b1c18d25d --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt @@ -0,0 +1,42 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.lifecycle + +import androidx.compose.runtime.Composable +import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty1 +import kotlin.reflect.KVisibility +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.starProjectedType + +interface ComposableLifecycleSubscribable : LifecycleSubscribableMarker { + val modifier: @Composable (@Composable () -> Unit) -> Unit +} + +@Suppress("UNCHECKED_CAST") +internal val VM.ComposableLifecycleSubscribable: List get() = this::class.memberProperties + .filter { it !is KMutableProperty1 } + .mapNotNull { it as? KProperty1 } + .filter { + it.getter.visibility == KVisibility.PUBLIC && + it.getter.returnType.isSubtypeOf(LifecycleSubscribableMarker::class.starProjectedType) + } + .mapNotNull { it.getter(this) as? ComposableLifecycleSubscribable } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt index 8aab5cf6e..5f0e02193 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt @@ -26,34 +26,4 @@ import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.NavigationSpec import com.splendo.kaluga.architecture.navigation.Navigator -/** Routes navigation actions to underlying navigators defined by [navigatorForAction]. */ -class CombinedNavigator>( - private val navigatorForAction: CombinedNavigator.(A) -> Navigator -) : Navigator, LifecycleSubscribable by LifecycleSubscriber() { - - /** @return [ActivityNavigator] constructed with this navigation mapper.*/ - fun ((A) -> NavigationSpec).toActivityNavigator() = ActivityNavigator(this) - - override fun navigate(action: A) { - val navigator = navigatorForAction(action) - if (navigator is LifecycleSubscribable && navigator.manager == null) { - // navigator depends on the lifecycle but was not subscribed yet - manager?.let { manager -> - with(navigator) { - subscribe(manager) - navigate(action) - unsubscribe() - } - } - } else { - navigator.navigate(action) - } - } -} - -@Composable -fun > rememberCombinedNavigator( - navigatorForAction: CombinedNavigator.(A) -> Navigator -): Navigator = - remember { CombinedNavigator(navigatorForAction) } - .apply { bind() } + diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt index f6bbefd1f..db1a45c4a 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt @@ -5,14 +5,20 @@ import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.Navigator import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch /** @@ -55,82 +61,136 @@ class BottomSheetRouteController( } } -/** - * A [RouteController] used for navigating the sheet content in a [ModalBottomSheetLayout] - * @param navHostController The [NavHostController] to navigate the sheet content - * @param sheetState The [ModalBottomSheetState] associated with the [ModalBottomSheetLayout] - * @param coroutineScope A [CoroutineScope] used for navigation actions - */ class BottomSheetSheetContentRouteController( - internal val navHostController: NavHostController, - internal val sheetState: ModalBottomSheetState, - private val coroutineScope: CoroutineScope + val navHostController: StateFlow, + private val sheetState: SheetState ) : RouteController { private val sheetContentRouteController = NavHostRouteController(navHostController, this) override fun navigate(newRoute: Route) { if (!sheetState.isVisible) { - coroutineScope.launch { - sheetState.animateTo(ModalBottomSheetValue.Expanded) - } + sheetState.show(ModalBottomSheetValue.Expanded) } sheetContentRouteController.navigate(newRoute) } - override fun back(result: Route.Result): Boolean = if (navHostController.backQueue.isNotEmpty()) { - navHostController.previousBackStackEntry?.setResult(result) - navHostController.popBackStack() - } else { - close() - true + override fun back(result: Route.Result): Boolean { + val navHostController = navHostController.value + return if (navHostController != null && navHostController.backQueue.isNotEmpty()) { + navHostController.previousBackStackEntry?.setResult(result) + navHostController.popBackStack() + } else { + close() + true + } } override fun close() { - coroutineScope.launch { - sheetState.hide() - } + sheetState.hide() } } -/** - * A [Navigator] for a [ModalBottomSheetLayout] - * @param routeController The [BottomSheetRouteController] used for navigating. - * @param navigationMapper A mapper that converts an [NavigationAction] handled by this navigator into a [BottomSheetRoute] - */ -class ModalBottomSheetNavigator>( - internal val routeController: BottomSheetRouteController, +interface SheetState { + val isVisible: Boolean + fun show(value: ModalBottomSheetValue) + fun hide() +} + +private object EmptySheetState : SheetState { + override val isVisible: Boolean = false + override fun show(value: ModalBottomSheetValue) {} + override fun hide() {} +} + +private data class SheetStateState( + val state: ModalBottomSheetState, + val coroutineScope: CoroutineScope? +) : SheetState { + override val isVisible: Boolean get() = state.isVisible + override fun show(value: ModalBottomSheetValue) { + coroutineScope?.launch { state.animateTo(value) } + } + + override fun hide() { + coroutineScope?.launch { state.hide() } + } +} + +sealed class ModalBottomSheetNavigator>( private val navigationMapper: (A) -> BottomSheetRoute -) : Navigator { - - constructor( - contentNavHostController: NavHostController, - sheetContentNavHostController: NavHostController, - sheetState: ModalBottomSheetState, - coroutineScope: CoroutineScope, - navigationMapper: (A) -> BottomSheetRoute - ) : this( - BottomSheetRouteController( - NavHostRouteController(contentNavHostController), - BottomSheetSheetContentRouteController( - sheetContentNavHostController, - sheetState, - coroutineScope - ) - ), - navigationMapper +) : Navigator, ComposableLifecycleSubscribable { + + abstract val routeController: BottomSheetRouteController + + override fun navigate(action: A) = routeController.navigate(navigationMapper(action)) +} + +class NavHostModalBottomSheetNavigator>( + navigationMapper: (A) -> BottomSheetRoute, + contentBuilder: BottomSheetContentBuilder, + sheetContentBuilder: BottomSheetContentBuilder, + private val initialSheetValue: ModalBottomSheetValue = ModalBottomSheetValue.Hidden +) : ModalBottomSheetNavigator(navigationMapper) { + private val contentNavHostController = MutableStateFlow(null) + private val sheetContentNavHostController = MutableStateFlow(null) + private val sheetState = MutableStateFlow(EmptySheetState) + override val routeController = BottomSheetRouteController( + NavHostRouteController(contentNavHostController), + BottomSheetSheetContentRouteController( + sheetContentNavHostController, + object : SheetState { + override val isVisible: Boolean = sheetState.value.isVisible + override fun show(value: ModalBottomSheetValue) = sheetState.value.show(value) + override fun hide() = sheetState.value.hide() + } + ) ) - constructor( - contentRouteController: RouteController, - sheetContentRouteController: BottomSheetSheetContentRouteController, - navigationMapper: (A) -> BottomSheetRoute - ) : this( - BottomSheetRouteController(contentRouteController, sheetContentRouteController), - navigationMapper + override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> + val contentNavController = rememberNavController() + val sheetContentNavController = rememberNavController() + val modalBottomSheetState = rememberModalBottomSheetState(initialValue = initialSheetValue) + val coroutineScope = rememberCoroutineScope() + contentNavHostController.tryEmit(contentNavController) + sheetContentNavHostController.tryEmit(sheetContentNavController) + sheetState.tryEmit(SheetStateState(modalBottomSheetState, coroutineScope)) + SetupNavigatingModalBottomSheetLayout( + routeController.sheetContentRouteController, + contentNavController, + sheetContentNavController, + sheetContentBuilder, + content, + contentBuilder, + modalBottomSheetState + ) + } +} + +class NavHostModalBottomSheetContentNavigator>( + contentNavHostController: NavHostController, + sheetContentNavHostController: NavHostController, + sheetState: ModalBottomSheetState, + navigationMapper: (A) -> BottomSheetRoute, +) : ModalBottomSheetNavigator(navigationMapper) { + + private val sheetStateState = MutableStateFlow(SheetStateState(sheetState, null)) + override val routeController = BottomSheetRouteController( + NavHostRouteController(MutableStateFlow(contentNavHostController)), + BottomSheetSheetContentRouteController( + MutableStateFlow(sheetContentNavHostController), + object : SheetState { + override val isVisible: Boolean = sheetStateState.value.isVisible + override fun show(value: ModalBottomSheetValue) = sheetStateState.value.show(value) + override fun hide() = sheetStateState.value.hide() + } + ) ) - override fun navigate(action: A) = routeController.navigate(navigationMapper(action)) + override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> + sheetStateState.tryEmit(SheetStateState(sheetState, rememberCoroutineScope())) + content() + } } typealias BottomSheetRootContentBuilder = (contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) -> Unit @@ -143,57 +203,39 @@ typealias BottomSheetContentBuilder = NavGraphBuilder.(contentNavHostController: * @param content The [BottomSheetContentBuilder] for building the navigation content of the [ModalBottomSheetLayout] */ @Composable -fun > ModalBottomSheetNavigator.NavigatingModalBottomSheetLayout( +private fun SetupNavigatingModalBottomSheetLayout( + sheetContentRouteController: RouteController, + contentNavHostController: NavHostController, + sheetContentNavHostController: NavHostController, sheetContent: BottomSheetContentBuilder, - contentRoot: @Composable BottomSheetRootContentBuilder, - content: BottomSheetContentBuilder -) = routeController.NavigatingModalBottomSheetLayout( - sheetContent = sheetContent, - contentRoot = contentRoot, - content = content -) - -/** - * Creates a [ModalBottomSheetLayout] that supports navigation using a [ModalBottomSheetNavigator] - * @param sheetContent The [BottomSheetContentBuilder] for building the navigation sheet content of the [ModalBottomSheetLayout] - * @param contentRoot The [BottomSheetRootContentBuilder] for building the root view of the content of the [ModalBottomSheetLayout] - * @param content The [BottomSheetContentBuilder] for building the navigation content of the [ModalBottomSheetLayout] - */ -@Composable -fun BottomSheetRouteController.NavigatingModalBottomSheetLayout( - sheetContent: BottomSheetContentBuilder, - contentRoot: @Composable BottomSheetRootContentBuilder, - content: BottomSheetContentBuilder + contentRoot: @Composable () -> Unit, + content: BottomSheetContentBuilder, + sheetState: ModalBottomSheetState ) = ModalBottomSheetLayout( sheetContent = { - if (sheetContentRouteController.sheetState.isVisible) { + if (sheetState.isVisible) { HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) } Box(Modifier.defaultMinSize(minHeight = 1.dp)) { - sheetContentRouteController.SetupNavHost { sheetContentNavHostController -> + SetupNavHost(sheetContentNavHostController) { navHostController -> sheetContent( - contentRouteController.navHostController, - sheetContentNavHostController, - sheetContentRouteController.sheetState + contentNavHostController, + navHostController, + sheetState ) } } }, - sheetState = sheetContentRouteController.sheetState, + sheetState = sheetState, ) { - contentRouteController.SetupNavHost( - rootView = { navHostController -> - contentRoot( - navHostController, - sheetContentRouteController.navHostController, - sheetContentRouteController.sheetState - ) - } + SetupNavHost( + navHostController = contentNavHostController, + rootView = contentRoot ) { navHostController -> content( navHostController, - sheetContentRouteController.navHostController, - sheetContentRouteController.sheetState + sheetContentNavHostController, + sheetState ) } } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt index 4a0951bfe..b05dd2131 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt @@ -11,6 +11,8 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.NavigationBundle import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec @@ -21,6 +23,8 @@ import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec import com.splendo.kaluga.architecture.navigation.toBundle import com.splendo.kaluga.architecture.navigation.toNavigationBundle import com.splendo.kaluga.architecture.navigation.toTypedProperty +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow /** * Controller for navigating to a [Route] @@ -43,22 +47,18 @@ sealed interface RouteController { fun close() } -internal val RouteController.navHostController get() = when (this) { - is NavHostRouteController -> navHostController - is BottomSheetSheetContentRouteController -> navHostController -} - /** * A [RouteController] managed by a [NavHostController] - * @param navHostController The [NavHostController] managing the route stack + * @param navHostController A [StateFlow] indicating the current [NavHostController] managing the route stack * @param parentRouteController The [NavHostController] that is a parent to the [navHostController] if it exists */ class NavHostRouteController( - internal val navHostController: NavHostController, + internal val navHostController: StateFlow, private val parentRouteController: RouteController? = null ) : RouteController { override fun navigate(newRoute: Route) { + val navHostController = navHostController.value ?: return when (newRoute) { is Route.NextRoute<*, *> -> navHostController.navigate(newRoute.route) { launchSingleTop = true @@ -87,14 +87,18 @@ class NavHostRouteController( } } - override fun back(result: Route.Result): Boolean = if (navHostController.backQueue.isNotEmpty()){ - navHostController.previousBackStackEntry?.setResult(result) - navHostController.popBackStack() + override fun back(result: Route.Result): Boolean { + val navHostController = navHostController.value + return if (navHostController != null && navHostController.backQueue.isNotEmpty()) { + navHostController.previousBackStackEntry?.setResult(result) + navHostController.popBackStack() + } else { + parentRouteController?.back(result) ?: false + } } - else { parentRouteController?.back(result) ?: false } override fun close() { - navHostController.popBackStack(ROOT_VIEW, true) + navHostController.value?.popBackStack(ROOT_VIEW, true) parentRouteController?.close() } } @@ -104,100 +108,94 @@ typealias RouteContentBuilder = NavGraphBuilder.(NavHostController) -> Unit /** * A Navigator managed by a [RouteController] - * @param routeController The [RouteController] managing this navigation * @param navigationMapper A mapper that converts an [NavigationAction] handled by this navigator into a [Route] */ -open class RouteNavigator>( - internal val routeController: RouteController, +sealed class RouteNavigator>( private val navigationMapper: (A) -> Route -) : Navigator { +) : Navigator, ComposableLifecycleSubscribable { - constructor( - navHostController: NavHostController, - navigationMapper: (A) -> Route - ) : this(NavHostRouteController(navHostController), navigationMapper) + abstract val routeController: RouteController override fun navigate(action: A) { routeController.navigate(navigationMapper(action)) } } -/** - * Creates a a [NavHost] for a given [RouteNavigator] - * @param builder The [RouteContentBuilder] for building the content of the [NavHost] - */ -@Composable -fun > RouteNavigator.SetupNavHost(builder: RouteContentBuilder) = routeController.SetupNavHost(builder) +class NavHostRouteNavigator>( + navigationMapper: (A) -> Route, + parentRouteController: RouteController? = null, + builder: RouteContentBuilder +) : RouteNavigator(navigationMapper) { + + private val navHostController = MutableStateFlow(null) + override val routeController: RouteController = NavHostRouteController(navHostController, parentRouteController) + + override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> + val navController = rememberNavController() + navHostController.tryEmit(navController) + SetupNavHost( + navHostController = navController, + rootView = content, + builder = builder + ) + } +} -/** - * Creates a a [NavHost] for a given [RouteController] - * @param builder The [RouteContentBuilder] for building the content of the [NavHost] - */ -@Composable -fun RouteController.SetupNavHost(builder: RouteContentBuilder) { - SetupNavHost(startDestination = ROOT_VIEW) { - composable(ROOT_VIEW) { - Spacer(modifier = Modifier.fillMaxWidth()) - } - builder(navHostController) +class NavHostContentRouteNavigator>( + navHostController: NavHostController, + parentRouteController: RouteController? = null, + navigationMapper: (A) -> Route +) : RouteNavigator(navigationMapper) { + + override val routeController: RouteController = NavHostRouteController(MutableStateFlow(navHostController), parentRouteController) + + override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> + content() } } -/** - * Creates a a [NavHost] for a given [RouteNavigator] - * @param rootView The [RouteRootContentBuilder] for the initial view of the [NavHost] - * @param builder The [RouteContentBuilder] for building the content of the [NavHost] - */ @Composable -fun > RouteNavigator.SetupNavHost( - rootView: @Composable RouteRootContentBuilder, +fun SetupNavHost( + navHostController: NavHostController, builder: RouteContentBuilder -) = routeController.SetupNavHost(rootView, builder) +) = SetupNavHost( + navHostController = navHostController, + rootView = { Spacer(modifier = Modifier.fillMaxWidth()) }, + builder +) -/** - * Creates a a [NavHost] for a given [RouteController] - * @param rootView The [RouteRootContentBuilder] for the initial view of the [NavHost] - * @param builder The [RouteContentBuilder] for building the content of the [NavHost] - */ @Composable -fun RouteController.SetupNavHost( - rootView: @Composable RouteRootContentBuilder, +fun SetupNavHost( + navHostController: NavHostController, + rootView: @Composable () -> Unit, builder: RouteContentBuilder +) = SetupNavHost( + navHostController = navHostController, + startDestination = ROOT_VIEW ) { - SetupNavHost(startDestination = ROOT_VIEW) { - composable(ROOT_VIEW, content = { rootView(navHostController) }) - builder(navHostController) - } + composable(ROOT_VIEW, content = { rootView() }) + builder(navHostController) } -/** - * Creates a a [NavHost] for a given [RouteNavigator] - * @param startDestination The start destination of the [NavHost] - * @param builder The [RouteContentBuilder] for building the content of the [NavHost] - */ -@Composable -fun > RouteNavigator.SetupNavHost( - startDestination: String, - builder: NavGraphBuilder.(RouteController) -> Unit -) = routeController.SetupNavHost(startDestination, builder) - /** * Creates a a [NavHost] for a given [RouteController] * @param startDestination The start destination of the [NavHost] * @param builder The [RouteContentBuilder] for building the content of the [NavHost] */ @Composable -fun RouteController.SetupNavHost( +fun SetupNavHost( + navHostController: NavHostController, startDestination: String, - builder: NavGraphBuilder.(RouteController) -> Unit + builder: NavGraphBuilder.() -> Unit ) { NavHost( navController = navHostController, startDestination = startDestination, - builder = { builder(this@SetupNavHost) } + builder = { builder() } ) } + /** * Handles a [Route.Result] matching a given [NavigationBundleSpec] * @param spec The [NavigationBundleSpec] used to create the [Route.Result] diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt index 95904cfad..65da486c9 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt @@ -1,16 +1,31 @@ package com.splendo.kaluga.architecture.compose.viewModel +import android.content.Context +import android.content.ContextWrapper +import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.DisallowComposableCalls import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.fragment.app.FragmentManager import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner +import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable +import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable +import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker +import com.splendo.kaluga.architecture.lifecycle.subscribe import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty1 +import kotlin.reflect.KVisibility +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.starProjectedType /** * Composable which manages [viewModel] lifecycle and optionally adds it to local [ViewModelStore]. @@ -22,15 +37,51 @@ fun ViewModelComposable( viewModel: ViewModel, content: @Composable (ViewModel.() -> Unit)? = null ) { - viewModel.linkLifecycle() - content?.invoke(viewModel) + LocalContext.current.activity?.let { + ViewModelComposable(it, it.supportFragmentManager, viewModel, content) + } +} + +@Composable +fun FragmentViewModelComposable( + fragmentManager: FragmentManager, + viewModel: ViewModel, + content: @Composable (ViewModel.() -> Unit)? = null +) = ViewModelComposable(LocalContext.current.activity, fragmentManager, viewModel, content) + +@Composable +private fun ViewModelComposable( + activity: AppCompatActivity?, + fragmentManager: FragmentManager, + viewModel: ViewModel, + content: @Composable (ViewModel.() -> Unit)? = null +) { + viewModel.linkLifecycle(activity, fragmentManager) + val composeLifecycleSubscribables = viewModel.ComposableLifecycleSubscribable + if (composeLifecycleSubscribables.isEmpty()) { + content?.invoke(viewModel) + } else { + val modifier = viewModel.ComposableLifecycleSubscribable.reduceRight { new, acc -> + object : ComposableLifecycleSubscribable { + override val modifier: @Composable (@Composable () -> Unit) -> Unit = { content -> + new.modifier { acc.modifier(content) } + } + } + } + modifier.modifier { content?.invoke(viewModel) } + } } /** * Stores a view model in the local [ViewModelStore]. Use if the view model * was created manually and is not located in Activity/Fragment [ViewModelStore]. */ -@Composable fun store(provider: @Composable () -> VM): VM = +@Composable +@Deprecated( + "Does not work for configuration changes (e.g. rotation).", +replaceWith = ReplaceWith("viewModel()", "androidx.lifecycle.viewmodel.compose.viewModel") +) +fun store(provider: @Composable () -> VM): VM = provider().also { handleLocalViewModelStore(it) } /** @@ -39,6 +90,10 @@ fun ViewModelComposable( * provider will only be evaluated during the composition. Recomposition will always return the value produced by provider. */ @Composable +@Deprecated( + "Does not work for configuration changes (e.g. rotation).", + replaceWith = ReplaceWith("viewModel()", "androidx.lifecycle.viewmodel.compose.viewModel") +) fun storeAndRemember(provider: @DisallowComposableCalls () -> VM): VM = store { remember(provider) } @@ -49,6 +104,10 @@ fun storeAndRemember(provider: @DisallowComposable * provider will only be evaluated during the composition. Recomposition will always return the value produced by provider. */ @Composable +@Deprecated( + "Does not work for configuration changes (e.g. rotation).", + replaceWith = ReplaceWith("viewModel()", "androidx.lifecycle.viewmodel.compose.viewModel") +) fun storeAndRemember(key: Any?, provider: @DisallowComposableCalls () -> VM): VM = store { remember(key, provider) } @@ -91,10 +150,10 @@ private fun rememberComposableViewModelStoreOwner(viewModel: BaseLifecycleViewMo } @Composable -private fun VM.linkLifecycle(): VM { +private fun VM.linkLifecycle(activity: AppCompatActivity?, fragmentManager: FragmentManager): VM { val lifecycle = LocalLifecycleOwner.current.lifecycle DisposableEffect(Unit) { - val observer = VmObserver(this@linkLifecycle) + val observer = VmObserver(this@linkLifecycle, activity, fragmentManager) lifecycle.addObserver(observer) @@ -106,9 +165,25 @@ private fun VM.linkLifecycle(): VM { return this } -private class VmObserver(private val viewModel: VM) : DefaultLifecycleObserver { +private class VmObserver(private val viewModel: VM, private val activity: AppCompatActivity?, private val fragmentManager: FragmentManager) : DefaultLifecycleObserver { private var resumed = false + private val lifecycleSubscribables: List by lazy { + @Suppress("UNCHECKED_CAST") + viewModel::class.memberProperties + .filter { it !is KMutableProperty1 } + .mapNotNull { it as? KProperty1 } + .filter { + it.getter.visibility == KVisibility.PUBLIC && + it.getter.returnType.isSubtypeOf(LifecycleSubscribableMarker::class.starProjectedType) + } + .mapNotNull { it.getter(viewModel) as? LifecycleSubscribable } + } + + override fun onCreate(owner: LifecycleOwner) { + lifecycleSubscribables.forEach { it.subscribe(activity, owner, fragmentManager) } + } + override fun onResume(owner: LifecycleOwner) { viewModel.didResume().also { resumed = true } } @@ -120,5 +195,13 @@ private class VmObserver(private val viewModel: VM) if (resumed) { viewModel.didPause() } + lifecycleSubscribables.forEach { it.unsubscribe() } } } + +private val Context.activity: AppCompatActivity? get() = when (this) { + is AppCompatActivity -> this + is ContextWrapper -> baseContext.activity // recursive lookup + else -> null +} + diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt index 7ce473bdf..3e95524f9 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation +import com.splendo.kaluga.architecture.compose.navigation.NavHostContentRouteNavigator import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable @@ -32,16 +33,19 @@ import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf @Composable fun ArchitectureDetailsLayout(inputDetails: InputDetails, navHostController: NavHostController) { - val navigator = RouteNavigator( - navHostController, - ::architectureDetailsNavigationRouteMapper - ) - - val viewModel = storeAndRemember { - ArchitectureDetailsViewModel(inputDetails, navigator) + val viewModel = koinViewModel { + parametersOf( + inputDetails, + NavHostContentRouteNavigator( + navHostController, + navigationMapper = ::architectureDetailsNavigationRouteMapper + ) + ) } ViewModelComposable(viewModel) { diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt index 08025e907..972fa14c1 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt @@ -56,8 +56,8 @@ import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRouteContro import com.splendo.kaluga.architecture.compose.navigation.BottomSheetSheetContentRouteController import com.splendo.kaluga.architecture.compose.navigation.HandleResult import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator +import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteController -import com.splendo.kaluga.architecture.compose.navigation.NavigatingModalBottomSheetLayout import com.splendo.kaluga.architecture.compose.navigation.composable import com.splendo.kaluga.architecture.compose.navigation.route import com.splendo.kaluga.architecture.compose.state @@ -71,6 +71,8 @@ import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureView import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf class ComposeArchitectureActivity : AppCompatActivity() { @@ -91,107 +93,94 @@ class ComposeArchitectureActivity : AppCompatActivity() { @Composable fun ArchitectureLayout() { MdcTheme { - val architectureRouteController = BottomSheetRouteController( - NavHostRouteController(rememberNavController()), - BottomSheetSheetContentRouteController( - rememberNavController(), - rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), - rememberCoroutineScope() + val viewModel = koinViewModel { + parametersOf( + NavHostModalBottomSheetNavigator>( + navigationMapper = ::architectureNavigationRouteMapper, + contentBuilder = { contentNavHostController, _, _ -> + composable( + type = NavigationBundleSpecType.SerializedType(InputDetails.serializer()) + ) { inputDetails -> + ArchitectureDetailsLayout(inputDetails, contentNavHostController) + } + }, + sheetContentBuilder = { contentNavHostController, sheetContentNavHostController, sheetState -> + composable(ArchitectureNavigationAction.BottomSheet.route()) { + BottomSheetLayout( + contentNavHostController, + sheetContentNavHostController, + sheetState + ) + } + composable(BottomSheetNavigation.SubPage.route()) { + BottomSheetSubPageLayout( + contentNavHostController, sheetContentNavHostController, sheetState + ) + } + }, + initialSheetValue = ModalBottomSheetValue.Hidden + ) ) - ) - - architectureRouteController.NavigatingModalBottomSheetLayout( - sheetContent = { contentNavHostController, sheetContentNavHostController, sheetState -> - composable(ArchitectureNavigationAction.BottomSheet.route()) { - BottomSheetLayout(contentNavHostController, sheetContentNavHostController, sheetState) - } - composable(BottomSheetNavigation.SubPage.route()) { - BottomSheetSubPageLayout( - contentNavHostController, sheetContentNavHostController, sheetState - ) - } - }, - contentRoot = { contentNavHostController, sheetContentNavHostController, sheetState -> - ArchitectureLayoutLayoutContent(contentNavHostController, sheetContentNavHostController, sheetState) - }, - content = { contentNavHostController, _, _ -> - composable( - type = NavigationBundleSpecType.SerializedType(InputDetails.serializer()) - ) { inputDetails -> - ArchitectureDetailsLayout(inputDetails, contentNavHostController) - } - } - ) - } -} - -@Composable -fun ArchitectureLayoutLayoutContent(contentNavHostController: NavHostController, sheetNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - - val navigator = ModalBottomSheetNavigator( - NavHostRouteController(contentNavHostController), - BottomSheetSheetContentRouteController( - sheetNavHostController, - sheetState, - rememberCoroutineScope() - ), - ::architectureNavigationRouteMapper - ) - - val viewModel = storeAndRemember { - ArchitectureViewModel(navigator) - } - - ViewModelComposable(viewModel) { - val nameInput = nameInput.mutableState() - val isNameValid by isNameValid.state() - val numberInput = numberInput.mutableState() - val isNumberValid by isNumberValid.state() - - val focusManager = LocalFocusManager.current - - contentNavHostController.HandleResult(NavigationBundleSpecType.SerializedType(InputDetails.serializer())) { - nameInput.value = name - numberInput.value = number.toString() } - Column( - verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - ) { - OutlinedTextField( - value = nameInput.value, - onValueChange = { nameInput.value = it }, + ViewModelComposable(viewModel) { + val nameInput = nameInput.mutableState() + val isNameValid by isNameValid.state() + val numberInput = numberInput.mutableState() + val isNumberValid by isNumberValid.state() + + val focusManager = LocalFocusManager.current + +// contentNavHostController.HandleResult( +// NavigationBundleSpecType.SerializedType( +// InputDetails.serializer() +// ) +// ) { +// nameInput.value = name +// numberInput.value = number.toString() +// } + + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), modifier = Modifier .fillMaxWidth() - .onPreviewKeyEvent { - if (it.key == Key.Tab && it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) { - focusManager.moveFocus(FocusDirection.Down) - true - } else { - false - } - }, - isError = !isNameValid, - placeholder = { Text(namePlaceholder) }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), - keyboardActions = KeyboardActions( - onNext = { focusManager.moveFocus(FocusDirection.Down) } + .verticalScroll(rememberScrollState()) + ) { + OutlinedTextField( + value = nameInput.value, + onValueChange = { nameInput.value = it }, + modifier = Modifier + .fillMaxWidth() + .onPreviewKeyEvent { + if (it.key == Key.Tab && it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) { + focusManager.moveFocus(FocusDirection.Down) + true + } else { + false + } + }, + isError = !isNameValid, + placeholder = { Text(namePlaceholder) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions( + onNext = { focusManager.moveFocus(FocusDirection.Down) } + ) ) - ) - OutlinedTextField( - value = numberInput.value, - onValueChange = { numberInput.value = it }, - modifier = Modifier.fillMaxWidth(), - isError = !isNumberValid, - placeholder = { Text(numberPlaceholder) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) - ) - showDetailsButton.Composable(modifier = Modifier.fillMaxWidth()) - showBottomSheetButton.Composable(modifier = Modifier.fillMaxWidth()) + OutlinedTextField( + value = numberInput.value, + onValueChange = { numberInput.value = it }, + modifier = Modifier.fillMaxWidth(), + isError = !isNumberValid, + placeholder = { Text(numberPlaceholder) }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) + ) + showDetailsButton.Composable(modifier = Modifier.fillMaxWidth()) + showBottomSheetButton.Composable(modifier = Modifier.fillMaxWidth()) + } } } } \ No newline at end of file diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index cadbb695b..5eec979e0 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt @@ -28,24 +28,27 @@ import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator +import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetContentNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf @Composable fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetNavigationRouteMapper, - ) - - val viewModel = storeAndRemember { - BottomSheetViewModel(navigator) + val viewModel = koinViewModel { + parametersOf( + NavHostModalBottomSheetContentNavigator( + contentNavHostController, + sheetContentNavHostController, + sheetState, + ::bottomSheetNavigationRouteMapper + ) + ) } ViewModelComposable(viewModel) { diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt index 565771345..16b5d70d0 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt @@ -32,10 +32,15 @@ import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator +import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetContentNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.compose.viewModel.store import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf @Composable fun BottomSheetSubPageLayout( @@ -43,18 +48,15 @@ fun BottomSheetSubPageLayout( sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState ) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetSubPageNavigationRouteMapper - ) - - val viewModel = store { - remember { - BottomSheetSubPageViewModel(navigator) - } + val viewModel = koinViewModel { + parametersOf( + NavHostModalBottomSheetContentNavigator( + contentNavHostController, + sheetContentNavHostController, + sheetState, + ::bottomSheetSubPageNavigationRouteMapper + ) + ) } ViewModelComposable(viewModel) { diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt index c17eb39be..86a20688c 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.google.android.material.composethemeadapter.MdcTheme +import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteNavigator import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember @@ -48,6 +49,8 @@ import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.example.compose.Constants +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf class ComposeResourcesActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug @@ -67,45 +70,38 @@ class ComposeResourcesActivity : AppCompatActivity() { @Composable fun ResourcesLayout() { MdcTheme { - val navigator = RouteNavigator(rememberNavController()) { action -> - when (action) { - is ResourcesListNavigationAction.Button -> action.next - is ResourcesListNavigationAction.Color -> action.next - is ResourcesListNavigationAction.Label -> action.next - } - } - - navigator.SetupNavHost( - rootView = { - ResourcesListLayout(navigator = navigator) - } - ) { - composable(ResourcesListNavigationAction.Button.route()) { ButtonsLayout() } - composable(ResourcesListNavigationAction.Color.route()) { ColorsLayout() } - composable(ResourcesListNavigationAction.Label.route()) { LabelsLayout() } + val viewModel = koinViewModel { + parametersOf( + NavHostRouteNavigator( + navigationMapper = { action -> + when (action) { + is ResourcesListNavigationAction.Button -> action.next + is ResourcesListNavigationAction.Color -> action.next + is ResourcesListNavigationAction.Label -> action.next + } + } + ) { + composable(ResourcesListNavigationAction.Button.route()) { ButtonsLayout() } + composable(ResourcesListNavigationAction.Color.route()) { ColorsLayout() } + composable(ResourcesListNavigationAction.Label.route()) { LabelsLayout() } + } + ) } - } -} - -@Composable -fun ResourcesListLayout(navigator: Navigator) { - val viewModel = storeAndRemember { - ResourcesListViewModel(navigator) - } - ViewModelComposable(viewModel) { - Column( - verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), - modifier = Modifier - .fillMaxWidth() - .padding(Constants.Padding.default) - .verticalScroll(rememberScrollState()) - ) { - val resources by resources.state() - resources.forEach { - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { onResourceSelected(it) }) { - Text(it.title) + ViewModelComposable(viewModel) { + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()) + ) { + val resources by resources.state() + resources.forEach { + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { onResourceSelected(it) }) { + Text(it.title) + } } } } From f6e14031718c9f3103daa8415348e75d9cf36f3f Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 25 Nov 2022 23:54:34 +0100 Subject: [PATCH 036/227] Make Architecture Compose work for Configuration changes --- .../ComposableLifecycleSubscribable.kt | 2 +- .../navigation/ModalBottomSheetNavigator.kt | 249 +++++++++++------- .../compose/navigation/RouteNavigator.kt | 135 +++++++--- .../compose/viewModel/ViewModelComposable.kt | 6 +- example/android/src/main/AndroidManifest.xml | 64 ++--- .../example/alerts/compose/AlertsLayout.kt | 13 +- .../compose/ArchitectureDetailsLayout.kt | 13 +- .../compose/ArchitectureLayout.kt | 41 +-- .../architecture/compose/BottomSheetLayout.kt | 16 +- .../compose/BottomSheetSubPageLayout.kt | 18 +- .../compose/DateTimePickerLayout.kt | 8 +- .../keyboard/compose/KeyboardLayout.kt | 12 +- .../example/loading/compose/LoadingLayout.kt | 13 +- .../resources/compose/ButtonsLayout.kt | 10 +- .../example/resources/compose/ColorsLayout.kt | 18 +- .../example/resources/compose/LabelsLayout.kt | 7 +- .../resources/compose/ResourcesLayout.kt | 11 +- .../shared/viewmodel/alert/AlertViewModel.kt | 4 +- .../src/main/kotlin/Dependencies.kt | 2 +- 19 files changed, 346 insertions(+), 296 deletions(-) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt index b1c18d25d..40f0d8404 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable.kt @@ -28,7 +28,7 @@ import kotlin.reflect.full.memberProperties import kotlin.reflect.full.starProjectedType interface ComposableLifecycleSubscribable : LifecycleSubscribableMarker { - val modifier: @Composable (@Composable () -> Unit) -> Unit + val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit } @Suppress("UNCHECKED_CAST") diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt index db1a45c4a..7445b907c 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt @@ -1,24 +1,33 @@ package com.splendo.kaluga.architecture.compose.navigation import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController +import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** @@ -61,22 +70,49 @@ class BottomSheetRouteController( } } +class BottomSheetContentNavHostRouteController( + provider: StateFlow, + parent: RouteController? = null, +) : NavHostProvidingRouteController(provider, parent) { + override fun BottomSheetNavigatorState.provide(): NavHostController = contentNavHostController +} + +class BottomSheetSheetContentNavHostRouteController( + provider: StateFlow, + parent: RouteController? = null, +) : NavHostProvidingRouteController(provider, parent) { + override fun BottomSheetNavigatorState.provide(): NavHostController = sheetContentNavHostController +} + +fun > BottomSheetContentRouteNavigator( + bottomSheetNavigator: StateFlow, + parentRouteController: RouteController? = null, + navigationMapper: (A) -> Route +): NavHostProvidingContentRouteNavigator = NavHostProvidingContentRouteNavigator( + BottomSheetContentNavHostRouteController(bottomSheetNavigator, parentRouteController), + navigationMapper +) + class BottomSheetSheetContentRouteController( - val navHostController: StateFlow, - private val sheetState: SheetState + private val bottomSheetNavigatorState: StateFlow, + private val coroutineScope: StateFlow ) : RouteController { - private val sheetContentRouteController = NavHostRouteController(navHostController, this) + private val sheetContentRouteController = BottomSheetSheetContentNavHostRouteController(bottomSheetNavigatorState, this) override fun navigate(newRoute: Route) { - if (!sheetState.isVisible) { - sheetState.show(ModalBottomSheetValue.Expanded) + bottomSheetNavigatorState.value?.let { (_, _, sheetState) -> + if (!sheetState.isVisible) { + coroutineScope.value?.launch { + sheetState.animateTo(ModalBottomSheetValue.Expanded) + } + } } sheetContentRouteController.navigate(newRoute) } override fun back(result: Route.Result): Boolean { - val navHostController = navHostController.value + val navHostController = bottomSheetNavigatorState.value?.contentNavHostController return if (navHostController != null && navHostController.backQueue.isNotEmpty()) { navHostController.previousBackStackEntry?.setResult(result) navHostController.popBackStack() @@ -87,33 +123,11 @@ class BottomSheetSheetContentRouteController( } override fun close() { - sheetState.hide() - } -} - -interface SheetState { - val isVisible: Boolean - fun show(value: ModalBottomSheetValue) - fun hide() -} - -private object EmptySheetState : SheetState { - override val isVisible: Boolean = false - override fun show(value: ModalBottomSheetValue) {} - override fun hide() {} -} - -private data class SheetStateState( - val state: ModalBottomSheetState, - val coroutineScope: CoroutineScope? -) : SheetState { - override val isVisible: Boolean get() = state.isVisible - override fun show(value: ModalBottomSheetValue) { - coroutineScope?.launch { state.animateTo(value) } - } - - override fun hide() { - coroutineScope?.launch { state.hide() } + bottomSheetNavigatorState.value?.let { (_, _, sheetState) -> + coroutineScope.value?.launch { + sheetState.hide() + } + } } } @@ -129,72 +143,79 @@ sealed class ModalBottomSheetNavigator>( class NavHostModalBottomSheetNavigator>( navigationMapper: (A) -> BottomSheetRoute, contentBuilder: BottomSheetContentBuilder, + contentRootResultHandlers: List> = emptyList(), sheetContentBuilder: BottomSheetContentBuilder, private val initialSheetValue: ModalBottomSheetValue = ModalBottomSheetValue.Hidden ) : ModalBottomSheetNavigator(navigationMapper) { - private val contentNavHostController = MutableStateFlow(null) - private val sheetContentNavHostController = MutableStateFlow(null) - private val sheetState = MutableStateFlow(EmptySheetState) + + private val bottomSheetNavigationState = MutableStateFlow(null) + private val sheetStateCoroutineScope = MutableStateFlow(null) override val routeController = BottomSheetRouteController( - NavHostRouteController(contentNavHostController), + BottomSheetContentNavHostRouteController(bottomSheetNavigationState), BottomSheetSheetContentRouteController( - sheetContentNavHostController, - object : SheetState { - override val isVisible: Boolean = sheetState.value.isVisible - override fun show(value: ModalBottomSheetValue) = sheetState.value.show(value) - override fun hide() = sheetState.value.hide() - } + bottomSheetNavigationState, + sheetStateCoroutineScope ) ) - override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> val contentNavController = rememberNavController() - val sheetContentNavController = rememberNavController() - val modalBottomSheetState = rememberModalBottomSheetState(initialValue = initialSheetValue) - val coroutineScope = rememberCoroutineScope() - contentNavHostController.tryEmit(contentNavController) - sheetContentNavHostController.tryEmit(sheetContentNavController) - sheetState.tryEmit(SheetStateState(modalBottomSheetState, coroutineScope)) + bottomSheetNavigationState.tryEmit( + BottomSheetNavigatorState( + contentNavController, + rememberNavController(), + rememberModalBottomSheetState(initialValue = initialSheetValue) + ) + ) + sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) SetupNavigatingModalBottomSheetLayout( + bottomSheetNavigationState, routeController.sheetContentRouteController, - contentNavController, - sheetContentNavController, sheetContentBuilder, - content, - contentBuilder, - modalBottomSheetState + { + contentRootResultHandlers.forEach { + it.HandleResult(viewModel = this, navHostController = contentNavController) + } + content() + }, + contentBuilder ) } } class NavHostModalBottomSheetContentNavigator>( - contentNavHostController: NavHostController, - sheetContentNavHostController: NavHostController, - sheetState: ModalBottomSheetState, + bottomSheetNavigatorState: StateFlow, navigationMapper: (A) -> BottomSheetRoute, ) : ModalBottomSheetNavigator(navigationMapper) { - private val sheetStateState = MutableStateFlow(SheetStateState(sheetState, null)) + private val sheetStateCoroutineScope = MutableStateFlow(null) + + override val routeController = BottomSheetRouteController( - NavHostRouteController(MutableStateFlow(contentNavHostController)), + BottomSheetContentNavHostRouteController(bottomSheetNavigatorState), BottomSheetSheetContentRouteController( - MutableStateFlow(sheetContentNavHostController), - object : SheetState { - override val isVisible: Boolean = sheetStateState.value.isVisible - override fun show(value: ModalBottomSheetValue) = sheetStateState.value.show(value) - override fun hide() = sheetStateState.value.hide() - } + bottomSheetNavigatorState, + sheetStateCoroutineScope ) ) - override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> - sheetStateState.tryEmit(SheetStateState(sheetState, rememberCoroutineScope())) + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) content() } } -typealias BottomSheetRootContentBuilder = (contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) -> Unit -typealias BottomSheetContentBuilder = NavGraphBuilder.(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) -> Unit +data class BottomSheetNavigatorState( + val contentNavHostController: NavHostController, + val sheetContentNavHostController: NavHostController, + val sheetState: ModalBottomSheetState + ) + +fun StateFlow.contentNavHostControllerState(coroutineScope: CoroutineScope) = map { it?.contentNavHostController }.stateIn(coroutineScope, SharingStarted.Lazily, null) +fun StateFlow.sheetContentNavHostControllerState(coroutineScope: CoroutineScope) = map { it?.sheetContentNavHostController }.stateIn(coroutineScope, SharingStarted.Lazily, null) + +typealias BottomSheetRootContentBuilder = (bottomSheetNavigationState: StateFlow) -> Unit +typealias BottomSheetContentBuilder = NavGraphBuilder.(bottomSheetNavigationState: StateFlow) -> Unit /** * Creates a [ModalBottomSheetLayout] that supports navigation using a [ModalBottomSheetNavigator] @@ -204,38 +225,72 @@ typealias BottomSheetContentBuilder = NavGraphBuilder.(contentNavHostController: */ @Composable private fun SetupNavigatingModalBottomSheetLayout( + bottomSheetNavigationState: StateFlow, sheetContentRouteController: RouteController, - contentNavHostController: NavHostController, - sheetContentNavHostController: NavHostController, sheetContent: BottomSheetContentBuilder, contentRoot: @Composable () -> Unit, - content: BottomSheetContentBuilder, - sheetState: ModalBottomSheetState -) = ModalBottomSheetLayout( - sheetContent = { - if (sheetState.isVisible) { - HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) - } - Box(Modifier.defaultMinSize(minHeight = 1.dp)) { - SetupNavHost(sheetContentNavHostController) { navHostController -> - sheetContent( - contentNavHostController, - navHostController, - sheetState + content: BottomSheetContentBuilder +) { + val currentBottomSheetNavigationState by bottomSheetNavigationState.collectAsState() + currentBottomSheetNavigationState?.let { navigationState -> + ModalBottomSheetLayout( + sheetContent = { + if (navigationState.sheetState.isVisible) { + HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) + } + Box(Modifier.defaultMinSize(minHeight = 1.dp)) { + SetupNavHost( + bottomSheetNavigatorState = bottomSheetNavigationState, + navHostController = { sheetContentNavHostController }, + ) {state -> + sheetContent( + state + ) + } + } + }, + sheetState = navigationState.sheetState, + ) { + SetupNavHost( + bottomSheetNavigatorState = bottomSheetNavigationState, + navHostController = { contentNavHostController }, + rootView = contentRoot + ) { state -> + content( + state ) } } - }, - sheetState = sheetState, -) { - SetupNavHost( - navHostController = contentNavHostController, - rootView = contentRoot - ) { navHostController -> - content( - navHostController, - sheetContentNavHostController, - sheetState - ) } } + +@Composable +fun SetupNavHost( + bottomSheetNavigatorState: StateFlow, + navHostController: BottomSheetNavigatorState.() -> NavHostController, + builder: BottomSheetContentBuilder +) = SetupNavHost( + bottomSheetNavigatorState = bottomSheetNavigatorState, + navHostController = navHostController, + rootView = { Spacer(modifier = Modifier.fillMaxWidth()) }, + builder +) + +@Composable +fun SetupNavHost( + bottomSheetNavigatorState: StateFlow, + navHostController: BottomSheetNavigatorState.() -> NavHostController, + rootView: @Composable () -> Unit, + builder: BottomSheetContentBuilder +) { + val currentBottomSheetNavigatorState by bottomSheetNavigatorState.collectAsState() + currentBottomSheetNavigatorState?.let { + SetupNavHost( + navHostController = it.navHostController(), + startDestination = ROOT_VIEW + ) { + composable(ROOT_VIEW, content = { rootView() }) + builder(bottomSheetNavigatorState) + } + } +} \ No newline at end of file diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt index b05dd2131..616fdea97 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder @@ -23,8 +24,10 @@ import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec import com.splendo.kaluga.architecture.navigation.toBundle import com.splendo.kaluga.architecture.navigation.toNavigationBundle import com.splendo.kaluga.architecture.navigation.toTypedProperty +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.serialization.KSerializer /** * Controller for navigating to a [Route] @@ -47,18 +50,15 @@ sealed interface RouteController { fun close() } -/** - * A [RouteController] managed by a [NavHostController] - * @param navHostController A [StateFlow] indicating the current [NavHostController] managing the route stack - * @param parentRouteController The [NavHostController] that is a parent to the [navHostController] if it exists - */ -class NavHostRouteController( - internal val navHostController: StateFlow, +sealed class NavHostProvidingRouteController( + private val provider: StateFlow, private val parentRouteController: RouteController? = null ) : RouteController { + abstract fun Provider.provide(): NavHostController + override fun navigate(newRoute: Route) { - val navHostController = navHostController.value ?: return + val navHostController = provider.value?.provide() ?: return when (newRoute) { is Route.NextRoute<*, *> -> navHostController.navigate(newRoute.route) { launchSingleTop = true @@ -88,7 +88,7 @@ class NavHostRouteController( } override fun back(result: Route.Result): Boolean { - val navHostController = navHostController.value + val navHostController = provider.value?.provide() return if (navHostController != null && navHostController.backQueue.isNotEmpty()) { navHostController.previousBackStackEntry?.setResult(result) navHostController.popBackStack() @@ -98,13 +98,19 @@ class NavHostRouteController( } override fun close() { - navHostController.value?.popBackStack(ROOT_VIEW, true) + provider.value?.provide()?.popBackStack(ROOT_VIEW, true) parentRouteController?.close() } } -typealias RouteRootContentBuilder = (NavHostController) -> Unit -typealias RouteContentBuilder = NavGraphBuilder.(NavHostController) -> Unit +class NavHostRouteController( + provider: StateFlow, + parentRouteController: RouteController? = null +) : NavHostProvidingRouteController(provider, parentRouteController) { + override fun NavHostController.provide(): NavHostController = this +} + +typealias RouteContentBuilder = NavGraphBuilder.(StateFlow) -> Unit /** * A Navigator managed by a [RouteController] @@ -124,39 +130,51 @@ sealed class RouteNavigator>( class NavHostRouteNavigator>( navigationMapper: (A) -> Route, parentRouteController: RouteController? = null, + rootResultHandlers: List> = emptyList(), builder: RouteContentBuilder ) : RouteNavigator(navigationMapper) { private val navHostController = MutableStateFlow(null) override val routeController: RouteController = NavHostRouteController(navHostController, parentRouteController) - override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> val navController = rememberNavController() navHostController.tryEmit(navController) SetupNavHost( - navHostController = navController, - rootView = content, + navHostController = navHostController, + rootView = { + rootResultHandlers.forEach { resultHandler -> + resultHandler.HandleResult(viewModel = this, navHostController = navController) + } + content() + }, builder = builder ) } } -class NavHostContentRouteNavigator>( - navHostController: NavHostController, - parentRouteController: RouteController? = null, +class NavHostProvidingContentRouteNavigator, Provider>( + override val routeController: NavHostProvidingRouteController, navigationMapper: (A) -> Route ) : RouteNavigator(navigationMapper) { - override val routeController: RouteController = NavHostRouteController(MutableStateFlow(navHostController), parentRouteController) - - override val modifier: @Composable (@Composable () -> Unit) -> Unit = @Composable { content -> + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> content() } } +fun > NavHostContentRouteNavigator( + navHostController: StateFlow, + parentRouteController: RouteController? = null, + navigationMapper: (A) -> Route +): NavHostProvidingContentRouteNavigator = NavHostProvidingContentRouteNavigator( + NavHostRouteController(navHostController, parentRouteController), + navigationMapper +) + @Composable fun SetupNavHost( - navHostController: NavHostController, + navHostController: StateFlow, builder: RouteContentBuilder ) = SetupNavHost( navHostController = navHostController, @@ -166,15 +184,20 @@ fun SetupNavHost( @Composable fun SetupNavHost( - navHostController: NavHostController, + navHostController: StateFlow, rootView: @Composable () -> Unit, builder: RouteContentBuilder -) = SetupNavHost( - navHostController = navHostController, - startDestination = ROOT_VIEW ) { - composable(ROOT_VIEW, content = { rootView() }) - builder(navHostController) + val currentNavHostController by navHostController.collectAsState() + currentNavHostController?.let { + SetupNavHost( + navHostController = it, + startDestination = ROOT_VIEW + ) { + composable(ROOT_VIEW, content = { rootView() }) + builder(navHostController) + } + } } /** @@ -195,6 +218,56 @@ fun SetupNavHost( ) } +sealed class NavHostRootResultHandler { + abstract val viewModelClass: Class + abstract val retain: Boolean + abstract val onResult: ViewModel.(R) -> Unit + @Composable + internal abstract fun NavHostController.HandleResult(callback: (R) -> Unit) + + data class Bundle>( + override val viewModelClass: Class, + val spec: NavigationBundleSpec, + override val retain: Boolean = false, + override val onResult: ViewModel.(NavigationBundle) -> Unit + ) : NavHostRootResultHandler>() { + @Composable + override fun NavHostController.HandleResult(callback: (NavigationBundle) -> Unit) = HandleResult(spec, retain) { callback(this) } + } + + data class Type( + override val viewModelClass: Class, + val spec: NavigationBundleSpecType, + override val retain: Boolean, + override val onResult: ViewModel.(R) -> Unit + ) : NavHostRootResultHandler() { + + @Composable + override fun NavHostController.HandleResult(callback: (R) -> Unit) = HandleResult(spec, retain) { callback(this) } + } + + @Composable + internal fun HandleResult(viewModel: BaseLifecycleViewModel, navHostController: NavHostController) { + viewModelClass.cast(viewModel)?.let { vm -> + navHostController.HandleResult { vm.onResult(it) } + } + } +} + +inline fun > NavigationBundleSpec.NavHostRootResultHandler( + retain: Boolean = false, + noinline onResult: ViewModel.(NavigationBundle) -> Unit +) = NavHostRootResultHandler.Bundle(ViewModel::class.java, this, retain, onResult) + +inline fun NavigationBundleSpecType.NavHostRootResultHandler( + retain: Boolean = false, + noinline onResult: ViewModel.(R) -> Unit +) = NavHostRootResultHandler.Type(ViewModel::class.java, this, retain, onResult) + +inline fun KSerializer.NavHostRootResultHandler( + retain: Boolean = false, + noinline onResult: ViewModel.(R) -> Unit +) = NavHostRootResultHandler.Type(ViewModel::class.java, NavigationBundleSpecType.SerializedType(this), retain, onResult) /** * Handles a [Route.Result] matching a given [NavigationBundleSpec] @@ -206,7 +279,7 @@ fun SetupNavHost( fun > NavHostController.HandleResult( spec: NavigationBundleSpec, retain: Boolean = false, - onResult: @Composable NavigationBundle.() -> Unit + onResult: NavigationBundle.() -> Unit ) = HandleResult(retain) { toNavigationBundle(spec).onResult() } /** @@ -220,11 +293,11 @@ fun > NavHostController.HandleResult( fun NavHostController.HandleResult( type: NavigationBundleSpecType, retain: Boolean = false, - onResult: @Composable R.() -> Unit + onResult: R.() -> Unit ) = HandleResult(retain) { toTypedProperty(type).onResult() } @Composable -internal fun NavHostController.HandleResult(retain: Boolean = false, onResult: @Composable Bundle.() -> Unit) { +internal fun NavHostController.HandleResult(retain: Boolean = false, onResult: Bundle.() -> Unit) { val result = currentBackStackEntry?.savedStateHandle?.getStateFlow(Route.Result.KEY, null)?.collectAsState() result?.value?.let { onResult(it) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt index 65da486c9..5408b0ec0 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt @@ -63,12 +63,12 @@ private fun ViewModelComposable( } else { val modifier = viewModel.ComposableLifecycleSubscribable.reduceRight { new, acc -> object : ComposableLifecycleSubscribable { - override val modifier: @Composable (@Composable () -> Unit) -> Unit = { content -> - new.modifier { acc.modifier(content) } + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = { content -> + new.modifier(this) { acc.modifier(this, content) } } } } - modifier.modifier { content?.invoke(viewModel) } + modifier.modifier(viewModel) { content?.invoke(viewModel) } } } diff --git a/example/android/src/main/AndroidManifest.xml b/example/android/src/main/AndroidManifest.xml index ad9739cb0..99fa7584b 100644 --- a/example/android/src/main/AndroidManifest.xml +++ b/example/android/src/main/AndroidManifest.xml @@ -46,7 +46,7 @@ + android:screenOrientation="fullSensor"> @@ -55,54 +55,54 @@ + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"> @@ -115,50 +115,50 @@ + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> + android:screenOrientation="fullSensor"/> diff --git a/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt index c6468b7c3..d4c536a79 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt @@ -31,13 +31,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.alerts.alertPresenterBuilder -import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.alert.AlertViewModel import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel class ComposeAlertsActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug @@ -45,9 +43,7 @@ class ComposeAlertsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContent { - CompositionLocalProvider( - LocalAppCompatActivity provides this - ) { + CompositionLocalProvider { AlertsLayout() } } @@ -57,10 +53,7 @@ class ComposeAlertsActivity : AppCompatActivity() { @Composable fun AlertsLayout() { MdcTheme { - val activity = LocalAppCompatActivity.current!! - val viewModel = storeAndRemember { - AlertViewModel(activity.alertPresenterBuilder()) - } + val viewModel = koinViewModel() ViewModelComposable(viewModel) { Column( diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt index 3e95524f9..d90cfa198 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt @@ -23,26 +23,25 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetContentRouteNavigator +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetNavigatorState import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.NavHostContentRouteNavigator -import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import com.splendo.kaluga.resources.compose.Composable +import kotlinx.coroutines.flow.StateFlow import org.koin.androidx.compose.koinViewModel import org.koin.core.parameter.parametersOf @Composable -fun ArchitectureDetailsLayout(inputDetails: InputDetails, navHostController: NavHostController) { +fun ArchitectureDetailsLayout(inputDetails: InputDetails, bottomSheetNavigatorState: StateFlow) { val viewModel = koinViewModel { parametersOf( inputDetails, - NavHostContentRouteNavigator( - navHostController, + BottomSheetContentRouteNavigator( + bottomSheetNavigatorState, navigationMapper = ::architectureDetailsNavigationRouteMapper ) ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt index 972fa14c1..1baaa606b 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt @@ -30,15 +30,12 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text -import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.input.key.Key @@ -47,23 +44,16 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.navigation.NavHostController import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import com.google.android.material.composethemeadapter.MdcTheme import com.splendo.kaluga.architecture.compose.mutableState -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRouteController -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetSheetContentRouteController -import com.splendo.kaluga.architecture.compose.navigation.HandleResult -import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetNavigator -import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteController +import com.splendo.kaluga.architecture.compose.navigation.NavHostRootResultHandler import com.splendo.kaluga.architecture.compose.navigation.composable import com.splendo.kaluga.architecture.compose.navigation.route import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureNavigationAction @@ -95,26 +85,30 @@ fun ArchitectureLayout() { MdcTheme { val viewModel = koinViewModel { parametersOf( - NavHostModalBottomSheetNavigator>( + NavHostModalBottomSheetNavigator( navigationMapper = ::architectureNavigationRouteMapper, - contentBuilder = { contentNavHostController, _, _ -> + contentBuilder = { bottomSheetNavigationState -> composable( type = NavigationBundleSpecType.SerializedType(InputDetails.serializer()) ) { inputDetails -> - ArchitectureDetailsLayout(inputDetails, contentNavHostController) + ArchitectureDetailsLayout(inputDetails, bottomSheetNavigationState) } }, - sheetContentBuilder = { contentNavHostController, sheetContentNavHostController, sheetState -> + contentRootResultHandlers = listOf( + InputDetails.serializer().NavHostRootResultHandler { + nameInput.post(it.name) + numberInput.post(it.number.toString()) + } + ), + sheetContentBuilder = { bottomSheetNavigationState -> composable(ArchitectureNavigationAction.BottomSheet.route()) { BottomSheetLayout( - contentNavHostController, - sheetContentNavHostController, - sheetState + bottomSheetNavigationState ) } composable(BottomSheetNavigation.SubPage.route()) { BottomSheetSubPageLayout( - contentNavHostController, sheetContentNavHostController, sheetState + bottomSheetNavigationState ) } }, @@ -131,15 +125,6 @@ fun ArchitectureLayout() { val focusManager = LocalFocusManager.current -// contentNavHostController.HandleResult( -// NavigationBundleSpecType.SerializedType( -// InputDetails.serializer() -// ) -// ) { -// nameInput.value = name -// numberInput.value = number.toString() -// } - Column( verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), modifier = Modifier diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index 5eec979e0..ddc837b76 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt @@ -20,32 +20,26 @@ package com.splendo.kaluga.example.architecture.compose import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetNavigatorState import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetContentNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants -import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel import com.splendo.kaluga.resources.compose.Composable +import kotlinx.coroutines.flow.StateFlow import org.koin.androidx.compose.koinViewModel import org.koin.core.parameter.parametersOf @Composable -fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { +fun BottomSheetLayout(bottomSheetNavigationState: StateFlow) { val viewModel = koinViewModel { parametersOf( - NavHostModalBottomSheetContentNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, + NavHostModalBottomSheetContentNavigator( + bottomSheetNavigationState, ::bottomSheetNavigationRouteMapper ) ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt index 16b5d70d0..d33cd851b 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt @@ -23,37 +23,27 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.Button -import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetNavigatorState import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetContentNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.store import com.splendo.kaluga.example.compose.Constants -import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation -import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel +import kotlinx.coroutines.flow.StateFlow import org.koin.androidx.compose.koinViewModel import org.koin.core.parameter.parametersOf @Composable fun BottomSheetSubPageLayout( - contentNavHostController: NavHostController, - sheetContentNavHostController: NavHostController, - sheetState: ModalBottomSheetState + bottomSheetNavigationState: StateFlow ) { val viewModel = koinViewModel { parametersOf( NavHostModalBottomSheetContentNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, + bottomSheetNavigationState, ::bottomSheetSubPageNavigationRouteMapper ) ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt index 1d08774f9..09307a1b3 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt @@ -36,11 +36,10 @@ import com.google.android.material.composethemeadapter.MdcTheme import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember -import com.splendo.kaluga.datetimepicker.datePickerPresenterBuilder import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel class ComposeDateTimePickerActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug @@ -60,10 +59,7 @@ class ComposeDateTimePickerActivity : AppCompatActivity() { @Composable fun DateTimePickerLayout() { MdcTheme { - val activity = LocalAppCompatActivity.current!! - val viewModel = storeAndRemember { - DateTimePickerViewModel(activity.datePickerPresenterBuilder()) - } + val viewModel = koinViewModel() ViewModelComposable(viewModel) { diff --git a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt index 423c1914e..2f7036a0f 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt @@ -42,9 +42,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel import com.splendo.kaluga.keyboard.KeyboardManager @@ -52,6 +50,8 @@ import com.splendo.kaluga.keyboard.compose.ComposeClearFocusHandler import com.splendo.kaluga.keyboard.compose.ComposeFocusHandler import com.splendo.kaluga.resources.compose.Composable import kotlinx.coroutines.flow.MutableStateFlow +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf class ComposeKeyboardActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug @@ -59,9 +59,7 @@ class ComposeKeyboardActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContent { - CompositionLocalProvider( - LocalAppCompatActivity provides this - ) { + CompositionLocalProvider { KeyboardLayout() } } @@ -72,8 +70,8 @@ class ComposeKeyboardActivity : AppCompatActivity() { fun KeyboardLayout() { MdcTheme { val focusHandler = LocalFocusManager.current - val viewModel = storeAndRemember { - KeyboardViewModel( + val viewModel = koinViewModel { + parametersOf( KeyboardManager.Builder( clearFocusHandler = ComposeClearFocusHandler(MutableStateFlow(focusHandler)) ), diff --git a/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt index 8c2af595f..61a4c6d98 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt @@ -31,13 +31,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel -import com.splendo.kaluga.hud.hudBuilder import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel class ComposeLoadingActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug @@ -45,9 +43,7 @@ class ComposeLoadingActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContent { - CompositionLocalProvider( - LocalAppCompatActivity provides this - ) { + CompositionLocalProvider { LoadingLayout() } } @@ -57,10 +53,7 @@ class ComposeLoadingActivity : AppCompatActivity() { @Composable fun LoadingLayout() { MdcTheme { - val activity = LocalAppCompatActivity.current!! - val viewModel = storeAndRemember { - HudViewModel(activity.hudBuilder()) - } + val viewModel = koinViewModel() ViewModelComposable(viewModel) { Column( diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt index 076f9e170..6eed87299 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt @@ -26,22 +26,16 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import com.splendo.kaluga.alerts.alertPresenterBuilder import com.splendo.kaluga.architecture.compose.state -import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel -import com.splendo.kaluga.resources.StyledStringBuilder import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel @Composable fun ButtonsLayout() { - val activity = LocalAppCompatActivity.current!! - val viewModel = storeAndRemember { - ButtonViewModel(StyledStringBuilder.Provider(), activity.alertPresenterBuilder()) - } + val viewModel = koinViewModel() ViewModelComposable(viewModel) { val buttons by buttons.state() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt index cad02e808..00eef8809 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.example.resources.compose -import android.view.KeyEvent import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -25,7 +24,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -34,38 +32,28 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp -import com.splendo.kaluga.alerts.alertPresenterBuilder import com.splendo.kaluga.architecture.compose.state -import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel import com.splendo.kaluga.resources.compose.Composable import com.splendo.kaluga.resources.compose.backgroundStyle import com.splendo.kaluga.resources.stylable.BackgroundStyle +import org.koin.androidx.compose.koinViewModel @Composable fun ColorsLayout() { - val activity = LocalAppCompatActivity.current!! - val viewModel = storeAndRemember { - ColorViewModel(activity.alertPresenterBuilder()) - } + val viewModel = koinViewModel() ViewModelComposable(viewModel) { Column( diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt index ab6db0c93..091fcd546 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt @@ -28,17 +28,14 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel -import com.splendo.kaluga.resources.StyledStringBuilder import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel @Composable fun LabelsLayout() { - val viewModel = storeAndRemember { - LabelViewModel(StyledStringBuilder.Provider()) - } + val viewModel = koinViewModel() ViewModelComposable(viewModel) { val labels by labels.state() diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt index 86a20688c..3dcee4363 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt @@ -34,21 +34,16 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import com.google.android.material.composethemeadapter.MdcTheme import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteNavigator -import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator -import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity -import com.splendo.kaluga.architecture.compose.viewModel.storeAndRemember -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction -import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel -import com.splendo.kaluga.architecture.compose.navigation.SetupNavHost import com.splendo.kaluga.architecture.compose.navigation.next import com.splendo.kaluga.architecture.compose.navigation.route import com.splendo.kaluga.architecture.compose.state +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable -import com.splendo.kaluga.architecture.navigation.Navigator import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel import org.koin.androidx.compose.koinViewModel import org.koin.core.parameter.parametersOf diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt index ef647044d..8c0a48ef4 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt @@ -66,7 +66,7 @@ class AlertViewModel(val builder: BaseAlertPresenter.Builder, dismissTime: Durat } } - val showAlertWithInputButton = KalugaButton.Plain("alert_list".localized(), ButtonStyles.default) { + val showAlertWithInputButton = KalugaButton.Plain("alert_input".localized(), ButtonStyles.default) { coroutineScope.launch { val okAction = Alert.Action("OK", Alert.Action.Style.POSITIVE) val cancelAction = Alert.Action("Cancel", Alert.Action.Style.NEGATIVE) @@ -85,7 +85,7 @@ class AlertViewModel(val builder: BaseAlertPresenter.Builder, dismissTime: Durat } } - val showAlertWithListButton = KalugaButton.Plain("alert_input".localized(), ButtonStyles.default) { + val showAlertWithListButton = KalugaButton.Plain("alert_list".localized(), ButtonStyles.default) { coroutineScope.launch { builder.buildActionSheet(this) { setTitle("Select an option") diff --git a/kaluga-library-components/src/main/kotlin/Dependencies.kt b/kaluga-library-components/src/main/kotlin/Dependencies.kt index 77faf8d0f..e6b8900d8 100644 --- a/kaluga-library-components/src/main/kotlin/Dependencies.kt +++ b/kaluga-library-components/src/main/kotlin/Dependencies.kt @@ -131,7 +131,7 @@ object Dependencies { } object Navigation { private const val group = "$groupBase.navigation" - private const val version = "2.5.2" + private const val version = "2.5.3" val Compose = Dependency(group, "navigation-compose", version) } From 0cb26b69126d4a5804f8ecbb4ba7bdfe636ba89b Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 28 Nov 2022 12:25:48 +0100 Subject: [PATCH 037/227] Make KeyboardManager support Compose properly --- .../kaluga/example/ExampleApplication.kt | 23 +++++++- .../keyboard/compose/KeyboardLayout.kt | 13 ++--- .../keyboard/xml/XMLKeyboardActivity.kt | 13 +++-- .../example/shared/di/DependencyInjection.kt | 7 --- .../viewmodel/keyboard/KeyboardViewModel.kt | 4 +- keyboard-compose/build.gradle.kts | 4 +- .../kotlin/ComposeClearFocusHandler.kt | 29 ---------- .../kotlin/ComposeFocusHandler.kt | 7 +-- .../kotlin/ComposeKeyboardManager.kt | 57 +++++++++++++++++++ .../kotlin/ClearFocusHandler.kt | 30 ---------- .../src/androidLibMain/kotlin/FocusHandler.kt | 6 +- .../androidLibMain/kotlin/KeyboardManager.kt | 22 ++++--- .../kotlin/AndroidKeyboardManagerTests.kt | 10 ++-- .../commonMain/kotlin/BaseKeyboardManager.kt | 31 +++------- .../commonTest/kotlin/KeyboardManagerTests.kt | 8 +-- keyboard/src/iosMain/kotlin/FocusHandler.kt | 6 +- .../src/iosMain/kotlin/KeyboardManager.kt | 8 +-- .../iosTest/kotlin/KeyboardManagerTests.kt | 10 ++-- keyboard/src/jsMain/kotlin/FocusHandler.kt | 20 ------- keyboard/src/jsMain/kotlin/KeyboardManager.kt | 6 +- keyboard/src/jvmMain/kotlin/FocusHandler.kt | 20 ------- .../src/jvmMain/kotlin/KeyboardManager.kt | 8 +-- .../androidLibMain/kotlin/MockFocusHandler.kt | 35 ------------ .../src/commonMain/kotlin/MockFocusHandler.kt | 14 +---- .../commonMain/kotlin/MockKeyboardManager.kt | 18 +++--- .../commonTest/kotlin/MockFocusHandlerTest.kt | 10 ++-- .../kotlin/MockKeyboardManagerTest.kt | 5 +- .../src/iosMain/kotlin/MockFocusHandler.kt | 34 ----------- .../src/jsMain/kotlin/MockFocusHandler.kt | 30 ---------- .../src/jvmMain/kotlin/MockFocusHandler.kt | 30 ---------- 30 files changed, 162 insertions(+), 356 deletions(-) delete mode 100644 keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt create mode 100644 keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt delete mode 100644 keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt delete mode 100644 keyboard/src/jsMain/kotlin/FocusHandler.kt delete mode 100644 keyboard/src/jvmMain/kotlin/FocusHandler.kt delete mode 100644 test-utils-keyboard/src/androidLibMain/kotlin/MockFocusHandler.kt delete mode 100644 test-utils-keyboard/src/iosMain/kotlin/MockFocusHandler.kt delete mode 100644 test-utils-keyboard/src/jsMain/kotlin/MockFocusHandler.kt delete mode 100644 test-utils-keyboard/src/jvmMain/kotlin/MockFocusHandler.kt diff --git a/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt b/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt index 7622e5ab7..ae7c5c228 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt @@ -20,7 +20,17 @@ package com.splendo.kaluga.example import android.app.Application import com.splendo.kaluga.base.ApplicationHolder +import com.splendo.kaluga.example.keyboard.compose.composeKeyboardViewModel +import com.splendo.kaluga.example.keyboard.xml.XMLKeyboardActivity import com.splendo.kaluga.example.shared.di.initKoin +import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel +import com.splendo.kaluga.keyboard.ViewFocusHandler +import com.splendo.kaluga.keyboard.ViewKeyboardManager +import com.splendo.kaluga.keyboard.compose.ComposeFocusHandler +import com.splendo.kaluga.keyboard.compose.ComposeKeyboardManager +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.qualifier.named +import org.koin.dsl.module class ExampleApplication : Application() { @@ -28,6 +38,17 @@ class ExampleApplication : Application() { super.onCreate() ApplicationHolder.application = this - initKoin() + initKoin( + listOf( + module { + viewModel(named(composeKeyboardViewModel)) { (keyboardBuilder: ComposeKeyboardManager.Builder, focusHandler: ComposeFocusHandler) -> + KeyboardViewModel(keyboardBuilder, focusHandler) + } + viewModel(named(XMLKeyboardActivity.viewModelName)) { (keyboardBuilder: ViewKeyboardManager.Builder, focusHandler: ViewFocusHandler) -> + KeyboardViewModel(keyboardBuilder, focusHandler) + } + } + ) + ) } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt index 2f7036a0f..eeff64946 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt @@ -45,13 +45,14 @@ import com.google.android.material.composethemeadapter.MdcTheme import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel -import com.splendo.kaluga.keyboard.KeyboardManager -import com.splendo.kaluga.keyboard.compose.ComposeClearFocusHandler import com.splendo.kaluga.keyboard.compose.ComposeFocusHandler +import com.splendo.kaluga.keyboard.compose.ComposeKeyboardManager import com.splendo.kaluga.resources.compose.Composable -import kotlinx.coroutines.flow.MutableStateFlow import org.koin.androidx.compose.koinViewModel import org.koin.core.parameter.parametersOf +import org.koin.core.qualifier.named + +val composeKeyboardViewModel = "ComposeKeyboardViewModel" class ComposeKeyboardActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug @@ -70,11 +71,9 @@ class ComposeKeyboardActivity : AppCompatActivity() { fun KeyboardLayout() { MdcTheme { val focusHandler = LocalFocusManager.current - val viewModel = koinViewModel { + val viewModel = koinViewModel>(named(composeKeyboardViewModel)) { parametersOf( - KeyboardManager.Builder( - clearFocusHandler = ComposeClearFocusHandler(MutableStateFlow(focusHandler)) - ), + ComposeKeyboardManager.Builder(), ComposeFocusHandler(FocusRequester.Default) ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt index dc16bdf35..342a627ea 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.kt @@ -24,15 +24,20 @@ import com.splendo.kaluga.example.R import com.splendo.kaluga.example.databinding.ActivityKeyboardManagerBinding import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel import com.splendo.kaluga.keyboard.ViewFocusHandler -import com.splendo.kaluga.keyboard.keyboardManagerBuilder +import com.splendo.kaluga.keyboard.ViewKeyboardManager import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf +import org.koin.core.qualifier.named -class XMLKeyboardActivity : KalugaViewModelActivity() { +class XMLKeyboardActivity : KalugaViewModelActivity>() { - override val viewModel: KeyboardViewModel by viewModel { + companion object { + const val viewModelName = "ViewKeyboardViewModel" + } + + override val viewModel: KeyboardViewModel by viewModel(named(viewModelName)) { parametersOf( - keyboardManagerBuilder(), + ViewKeyboardManager.Builder(), ViewFocusHandler(R.id.edit_field) ) } diff --git a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt index 0c82a2d00..4fc65982a 100644 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -47,7 +47,6 @@ import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewMo import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel -import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel @@ -63,8 +62,6 @@ import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationAction import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel import com.splendo.kaluga.hud.HUD -import com.splendo.kaluga.keyboard.FocusHandler -import com.splendo.kaluga.keyboard.KeyboardManager import com.splendo.kaluga.links.LinksBuilder import com.splendo.kaluga.location.LocationStateRepoBuilder import com.splendo.kaluga.permissions.base.Permission @@ -137,10 +134,6 @@ internal val androidModule = module { HudViewModel(HUD.Builder()) } - viewModel { (keyboardBuilder: KeyboardManager.Builder, focusHandler: FocusHandler) -> - KeyboardViewModel(keyboardBuilder, focusHandler) - } - viewModel { (navigator: Navigator>) -> LinksViewModel( LinksBuilder(), diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt index 1fca5c7fb..79a6f4293 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/keyboard/KeyboardViewModel.kt @@ -24,9 +24,9 @@ import com.splendo.kaluga.keyboard.FocusHandler import com.splendo.kaluga.resources.localized import com.splendo.kaluga.resources.view.KalugaButton -class KeyboardViewModel(keyboardManagerBuilder: BaseKeyboardManager.Builder, private val editFieldFocusHandler: FocusHandler) : BaseLifecycleViewModel() { +class KeyboardViewModel(val keyboardManagerBuilder: BaseKeyboardManager.Builder, private val editFieldFocusHandler: FH) : BaseLifecycleViewModel() { - private val keyboardManager: BaseKeyboardManager = keyboardManagerBuilder.create(coroutineScope) + private val keyboardManager: BaseKeyboardManager = keyboardManagerBuilder.create(coroutineScope) val showButton = KalugaButton.Plain("show_keyboard".localized(), ButtonStyles.default) { keyboardManager.show(editFieldFocusHandler) diff --git a/keyboard-compose/build.gradle.kts b/keyboard-compose/build.gradle.kts index d9d6d203f..2488de54d 100644 --- a/keyboard-compose/build.gradle.kts +++ b/keyboard-compose/build.gradle.kts @@ -11,7 +11,5 @@ composeAndroidComponent() dependencies { implementation(project(":base")) api(project(":keyboard")) - implementationDependency(Dependencies.AndroidX.Compose.UI) - implementationDependency(Dependencies.AndroidX.Compose.UITooling) - implementationDependency(Dependencies.KotlinX.Coroutines.Core) + api(project(":architecture-compose")) } diff --git a/keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt b/keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt deleted file mode 100644 index 806a5dd6b..000000000 --- a/keyboard-compose/src/androidLibMain/kotlin/ComposeClearFocusHandler.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.keyboard.compose - -import android.app.Activity -import androidx.compose.ui.focus.FocusManager -import com.splendo.kaluga.keyboard.ClearFocusHandler -import kotlinx.coroutines.flow.StateFlow - -class ComposeClearFocusHandler(private val focusManager: StateFlow) : ClearFocusHandler { - override fun clearFocus(activity: Activity?) { - focusManager.value.clearFocus(true) - } -} diff --git a/keyboard-compose/src/androidLibMain/kotlin/ComposeFocusHandler.kt b/keyboard-compose/src/androidLibMain/kotlin/ComposeFocusHandler.kt index 650bb9dcc..052b9c513 100644 --- a/keyboard-compose/src/androidLibMain/kotlin/ComposeFocusHandler.kt +++ b/keyboard-compose/src/androidLibMain/kotlin/ComposeFocusHandler.kt @@ -17,12 +17,7 @@ package com.splendo.kaluga.keyboard.compose -import android.app.Activity import androidx.compose.ui.focus.FocusRequester import com.splendo.kaluga.keyboard.FocusHandler -class ComposeFocusHandler(private val focusRequester: FocusRequester) : FocusHandler { - override fun requestFocus(activity: Activity?) { - focusRequester.requestFocus() - } -} +class ComposeFocusHandler(val focusRequester: FocusRequester) : FocusHandler diff --git a/keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt b/keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt new file mode 100644 index 000000000..afb71838d --- /dev/null +++ b/keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt @@ -0,0 +1,57 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.keyboard.compose + +import androidx.compose.runtime.Composable +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.keyboard.BaseKeyboardManager +import kotlinx.coroutines.CoroutineScope +import java.util.WeakHashMap + +class ComposeKeyboardManager(internal var currentFocusManager: FocusManager? = null) : BaseKeyboardManager { + + class Builder : BaseKeyboardManager.Builder, + ComposableLifecycleSubscribable { + + private val builtManagers = WeakHashMap() + + override fun create(coroutineScope: CoroutineScope): BaseKeyboardManager = ComposeKeyboardManager().also { + builtManagers[it.hashCode()] = it + } + + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = { content -> + val focusManager = LocalFocusManager.current + builtManagers.values.forEach { + it.currentFocusManager = focusManager + } + content() + } + + } + + override fun show(focusHandler: ComposeFocusHandler) { + focusHandler.focusRequester.requestFocus() + } + + override fun hide() { + currentFocusManager?.clearFocus() + } +} diff --git a/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt b/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt deleted file mode 100644 index e9342038b..000000000 --- a/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.keyboard - -import android.app.Activity - -interface ClearFocusHandler { - fun clearFocus(activity: Activity?) -} - -class ViewClearFocusHandler : ClearFocusHandler { - override fun clearFocus(activity: Activity?) { - activity?.currentFocus?.clearFocus() - } -} diff --git a/keyboard/src/androidLibMain/kotlin/FocusHandler.kt b/keyboard/src/androidLibMain/kotlin/FocusHandler.kt index 2f9bb8410..c9ce25cc7 100644 --- a/keyboard/src/androidLibMain/kotlin/FocusHandler.kt +++ b/keyboard/src/androidLibMain/kotlin/FocusHandler.kt @@ -24,14 +24,10 @@ import android.view.inputmethod.InputMethod.SHOW_EXPLICIT import android.view.inputmethod.InputMethodManager import androidx.annotation.IdRes -actual interface FocusHandler { - fun requestFocus(activity: Activity?) -} - class ViewFocusHandler( @IdRes private val id: Int ) : FocusHandler { - override fun requestFocus(activity: Activity?) { + fun requestFocus(activity: Activity?) { if (activity == null) return val view = activity.findViewById(id) ?: return diff --git a/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt b/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt index f331f5bdc..ed8a45924 100644 --- a/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt @@ -26,27 +26,25 @@ import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromC import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver import kotlinx.coroutines.CoroutineScope -actual class KeyboardManager( +class ViewKeyboardManager( private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver(), - private val clearFocusHandler: ClearFocusHandler, coroutineScope: CoroutineScope -) : BaseKeyboardManager, CoroutineScope by coroutineScope { +) : BaseKeyboardManager, CoroutineScope by coroutineScope { - actual class Builder( - private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver(), - private val clearFocusHandler: ClearFocusHandler = ViewClearFocusHandler() - ) : BaseKeyboardManager.Builder, LifecycleSubscribable by lifecycleManagerObserver { - actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager(lifecycleManagerObserver, clearFocusHandler, coroutineScope) + class Builder( + private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver() + ) : BaseKeyboardManager.Builder, LifecycleSubscribable by lifecycleManagerObserver { + override fun create(coroutineScope: CoroutineScope) = ViewKeyboardManager(lifecycleManagerObserver, coroutineScope) } - override fun show(focusHandler: FocusHandler) { + override fun show(focusHandler: ViewFocusHandler) { focusHandler.requestFocus(lifecycleManagerObserver.manager?.activity) } override fun hide() { val managedActivity = lifecycleManagerObserver.manager?.activity - clearFocusHandler.clearFocus(managedActivity) managedActivity?.let { activity -> + activity.currentFocus?.clearFocus() val inputMethodManager = activity.getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager inputMethodManager?.let { if (it.isAcceptingText) { @@ -66,6 +64,6 @@ actual class KeyboardManager( * which can automatically track which Activity is active for it. * */ -fun AppCompatActivity.keyboardManagerBuilder(clearFocusHandler: ClearFocusHandler = ViewClearFocusHandler()): KeyboardManager.Builder = getOrPutAndRemoveOnDestroyFromCache { - KeyboardManager.Builder(lifecycleManagerObserver(), clearFocusHandler) +fun AppCompatActivity.keyboardManagerBuilder(): ViewKeyboardManager.Builder = getOrPutAndRemoveOnDestroyFromCache { + ViewKeyboardManager.Builder(lifecycleManagerObserver()) } diff --git a/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.kt b/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.kt index 0dfb8d86a..7ffa25c6e 100644 --- a/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.kt +++ b/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.kt @@ -25,20 +25,20 @@ import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable import com.splendo.kaluga.keyboard.AndroidKeyboardManagerTests.AndroidKeyboardTestContext import kotlinx.coroutines.CoroutineScope import org.mockito.ArgumentMatchers -import org.mockito.Mockito.`when` import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` -class AndroidKeyboardManagerTests : KeyboardManagerTests() { +class AndroidKeyboardManagerTests : KeyboardManagerTests() { companion object { private const val viewId = 1 } - inner class AndroidKeyboardTestContext(coroutineScope: CoroutineScope) : KeyboardTestContext(), CoroutineScope by coroutineScope { + inner class AndroidKeyboardTestContext(coroutineScope: CoroutineScope) : KeyboardTestContext(), CoroutineScope by coroutineScope { override val focusHandler get() = ViewFocusHandler(viewId) - override lateinit var builder: KeyboardManager.Builder + override lateinit var builder: ViewKeyboardManager.Builder val mockActivity: Activity = mock(Activity::class.java) var mockView: View = mock(View::class.java) @@ -70,7 +70,7 @@ class AndroidKeyboardManagerTests : KeyboardManagerTests { /** - * Base KeyboardManager builder class, which used to create an KeyboardManager - * - * @see KeyboardManager + * Base KeyboardManager builder class, which used to create a [BaseKeyboardManager] */ - interface Builder : LifecycleSubscribableMarker { + interface Builder : LifecycleSubscribableMarker { /** * Creates KeyboardManager object * * @return The KeyboardManager object */ - fun create(coroutineScope: CoroutineScope): BaseKeyboardManager + fun create(coroutineScope: CoroutineScope): BaseKeyboardManager } /** - * Shows the keyboard for a given [KeyboardHostingView] + * Shows the keyboard for a given [FocusHandler] * - * @param keyboardHostingView The view for which the keyboard will be shown + * @param focusHandler The [FocusHandler] for which the keyboard will be shown */ - fun show(focusHandler: FocusHandler) + fun show(focusHandler: FH) /** * Dismisses the current keyboard */ fun hide() } - -/** - * Manager for Showing and Hiding the Keyboard. - */ -expect class KeyboardManager : BaseKeyboardManager { - - /** - * Builder for creating a [KeyboardManager]. - */ - class Builder : BaseKeyboardManager.Builder { - override fun create(coroutineScope: CoroutineScope): KeyboardManager - } -} diff --git a/keyboard/src/commonTest/kotlin/KeyboardManagerTests.kt b/keyboard/src/commonTest/kotlin/KeyboardManagerTests.kt index a987a7f2d..80159c7fa 100644 --- a/keyboard/src/commonTest/kotlin/KeyboardManagerTests.kt +++ b/keyboard/src/commonTest/kotlin/KeyboardManagerTests.kt @@ -21,11 +21,11 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.yield import kotlin.test.Test -abstract class KeyboardManagerTests : UIThreadTest() { +abstract class KeyboardManagerTests> : UIThreadTest() { - abstract class KeyboardTestContext : TestContext, CoroutineScope { - abstract val builder: KeyboardManager.Builder - abstract val focusHandler: FocusHandler + abstract class KeyboardTestContext : TestContext, CoroutineScope { + abstract val builder: BaseKeyboardManager.Builder + abstract val focusHandler: FH abstract fun verifyShow() abstract fun verifyDismiss() diff --git a/keyboard/src/iosMain/kotlin/FocusHandler.kt b/keyboard/src/iosMain/kotlin/FocusHandler.kt index 145757b73..6b30daad3 100644 --- a/keyboard/src/iosMain/kotlin/FocusHandler.kt +++ b/keyboard/src/iosMain/kotlin/FocusHandler.kt @@ -19,12 +19,8 @@ package com.splendo.kaluga.keyboard import platform.UIKit.UIView -actual interface FocusHandler { - fun requestFocus() -} - class UIKitFocusHandler(val view: UIView) : FocusHandler { - override fun requestFocus() { + fun requestFocus() { if (view.canBecomeFirstResponder) { view.becomeFirstResponder() } diff --git a/keyboard/src/iosMain/kotlin/KeyboardManager.kt b/keyboard/src/iosMain/kotlin/KeyboardManager.kt index 4f0d605a6..095bd639a 100644 --- a/keyboard/src/iosMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/iosMain/kotlin/KeyboardManager.kt @@ -22,13 +22,13 @@ import kotlinx.coroutines.CoroutineScope import platform.UIKit.UIApplication import platform.darwin.sel_registerName -actual class KeyboardManager(private val application: UIApplication) : BaseKeyboardManager { +class UIKitKeyboardManager(private val application: UIApplication) : BaseKeyboardManager { - actual class Builder(private val application: UIApplication = UIApplication.sharedApplication) : BaseKeyboardManager.Builder { - actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager(application) + class Builder(private val application: UIApplication = UIApplication.sharedApplication) : BaseKeyboardManager.Builder { + override fun create(coroutineScope: CoroutineScope) = UIKitKeyboardManager(application) } - override fun show(focusHandler: FocusHandler) { + override fun show(focusHandler: UIKitFocusHandler) { focusHandler.requestFocus() } diff --git a/keyboard/src/iosTest/kotlin/KeyboardManagerTests.kt b/keyboard/src/iosTest/kotlin/KeyboardManagerTests.kt index db8c0e6b7..c8bbdc7ac 100644 --- a/keyboard/src/iosTest/kotlin/KeyboardManagerTests.kt +++ b/keyboard/src/iosTest/kotlin/KeyboardManagerTests.kt @@ -1,21 +1,21 @@ package com.splendo.kaluga.keyboard -import com.splendo.kaluga.keyboard.IOSKeyboardManagerTests.IOSKeyboardTestContext +import com.splendo.kaluga.keyboard.UIKitKeyboardManagerTests.IOSKeyboardTestContext import kotlinx.coroutines.CoroutineScope import platform.CoreGraphics.CGRectMake import platform.UIKit.UIApplication import platform.UIKit.UITextField import kotlin.test.assertTrue -class IOSKeyboardManagerTests : KeyboardManagerTests() { +class UIKitKeyboardManagerTests : KeyboardManagerTests() { - class IOSKeyboardTestContext(coroutineScope: CoroutineScope) : KeyboardTestContext(), CoroutineScope by coroutineScope { + class IOSKeyboardTestContext(coroutineScope: CoroutineScope) : KeyboardTestContext(), CoroutineScope by coroutineScope { private val application = UIApplication.sharedApplication val textField = MockTextField() - override val builder get() = KeyboardManager.Builder(application) + override val builder get() = UIKitKeyboardManager.Builder(application) - override val focusHandler: FocusHandler + override val focusHandler: UIKitFocusHandler get() = UIKitFocusHandler(textField) override fun verifyShow() { diff --git a/keyboard/src/jsMain/kotlin/FocusHandler.kt b/keyboard/src/jsMain/kotlin/FocusHandler.kt deleted file mode 100644 index 28e5a9b54..000000000 --- a/keyboard/src/jsMain/kotlin/FocusHandler.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.keyboard - -actual interface FocusHandler diff --git a/keyboard/src/jsMain/kotlin/KeyboardManager.kt b/keyboard/src/jsMain/kotlin/KeyboardManager.kt index 5c5d33ceb..930e0cf06 100644 --- a/keyboard/src/jsMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/jsMain/kotlin/KeyboardManager.kt @@ -20,10 +20,10 @@ package com.splendo.kaluga.keyboard import kotlinx.coroutines.CoroutineScope -actual class KeyboardManager : BaseKeyboardManager { +class KeyboardManager : BaseKeyboardManager { - actual class Builder : BaseKeyboardManager.Builder { - actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager() + class Builder : BaseKeyboardManager.Builder { + override fun create(coroutineScope: CoroutineScope) = KeyboardManager() } override fun show(focusHandler: FocusHandler) { diff --git a/keyboard/src/jvmMain/kotlin/FocusHandler.kt b/keyboard/src/jvmMain/kotlin/FocusHandler.kt deleted file mode 100644 index 28e5a9b54..000000000 --- a/keyboard/src/jvmMain/kotlin/FocusHandler.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.keyboard - -actual interface FocusHandler diff --git a/keyboard/src/jvmMain/kotlin/KeyboardManager.kt b/keyboard/src/jvmMain/kotlin/KeyboardManager.kt index 5c5d33ceb..1397fd243 100644 --- a/keyboard/src/jvmMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/jvmMain/kotlin/KeyboardManager.kt @@ -20,10 +20,10 @@ package com.splendo.kaluga.keyboard import kotlinx.coroutines.CoroutineScope -actual class KeyboardManager : BaseKeyboardManager { +class KeyboardManager : BaseKeyboardManager { - actual class Builder : BaseKeyboardManager.Builder { - actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager() + class Builder : BaseKeyboardManager.Builder { + override fun create(coroutineScope: CoroutineScope) = KeyboardManager() } override fun show(focusHandler: FocusHandler) { @@ -33,4 +33,4 @@ actual class KeyboardManager : BaseKeyboardManager { override fun hide() { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } -} +} \ No newline at end of file diff --git a/test-utils-keyboard/src/androidLibMain/kotlin/MockFocusHandler.kt b/test-utils-keyboard/src/androidLibMain/kotlin/MockFocusHandler.kt deleted file mode 100644 index db83ac39d..000000000 --- a/test-utils-keyboard/src/androidLibMain/kotlin/MockFocusHandler.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.test.keyboard - -import android.app.Activity -import com.splendo.kaluga.keyboard.FocusHandler - -actual class MockFocusHandler : BaseMockFocusHandler(), FocusHandler { - override fun requestFocus(activity: Activity?) { - super.giveFocus() - } - - actual fun simulateGiveFocus() { - requestFocus(activity = null) - } - - actual fun simulateRemoveFocus() { - super.removeFocus() - } -} diff --git a/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt b/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt index 67570307e..5aa6ab1cc 100644 --- a/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt +++ b/test-utils-keyboard/src/commonMain/kotlin/MockFocusHandler.kt @@ -19,23 +19,15 @@ package com.splendo.kaluga.test.keyboard import com.splendo.kaluga.keyboard.FocusHandler -open class BaseMockFocusHandler { +class MockFocusHandler : FocusHandler { var isFocused: Boolean = false private set - protected fun giveFocus() { + fun giveFocus() { isFocused = true } - protected fun removeFocus() { + fun removeFocus() { isFocused = false } } - -/** - * Mock implementation of [FocusHandler] - */ -expect class MockFocusHandler constructor() : BaseMockFocusHandler, FocusHandler { - fun simulateGiveFocus() - fun simulateRemoveFocus() -} diff --git a/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt b/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt index d01faef82..443de0cce 100644 --- a/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt +++ b/test-utils-keyboard/src/commonMain/kotlin/MockKeyboardManager.kt @@ -28,18 +28,18 @@ import kotlinx.coroutines.CoroutineScope * Mock implementation of [BaseKeyboardManager] * @param setupMocks If `true` configure mocks to display the keyboard */ -class MockKeyboardManager(setupMocks: Boolean = true) : BaseKeyboardManager { +class MockKeyboardManager(setupMocks: Boolean = true) : BaseKeyboardManager { /** * Mock implementation of [BaseKeyboardManager.Builder] * @param setupMocks If `true` sets up [createMock] to build [MockKeyboardManager] */ - class Builder(setupMocks: Boolean = true) : BaseKeyboardManager.Builder { + class Builder(setupMocks: Boolean = true) : BaseKeyboardManager.Builder { /** * List of created [MockKeyboardManager] */ - val builtKeyboardManagers = mutableListOf() + val builtKeyboardManagers = mutableListOf>() /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [create] @@ -49,20 +49,20 @@ class MockKeyboardManager(setupMocks: Boolean = true) : BaseKeyboardManager { init { if (setupMocks) { createMock.on().doExecute { _ -> - MockKeyboardManager(setupMocks).also { + MockKeyboardManager(setupMocks).also { builtKeyboardManagers.add(it) } } } } - override fun create(coroutineScope: CoroutineScope): MockKeyboardManager = createMock.call(coroutineScope) + override fun create(coroutineScope: CoroutineScope): MockKeyboardManager = createMock.call(coroutineScope) } /** * Gets the current [FocusHandler] */ - var focusHandler: FocusHandler? = null + var focusHandler: FH? = null /** * [com.splendo.kaluga.test.base.mock.BaseMethodMock] for [show] @@ -78,16 +78,16 @@ class MockKeyboardManager(setupMocks: Boolean = true) : BaseKeyboardManager { if (setupMocks) { showMock.on().doExecute { (focusHandler) -> this.focusHandler = focusHandler - (focusHandler as? MockFocusHandler)?.simulateGiveFocus() + (focusHandler as? MockFocusHandler)?.giveFocus() } hideMock.on().doExecute { - (focusHandler as? MockFocusHandler)?.simulateRemoveFocus() + (focusHandler as? MockFocusHandler)?.removeFocus() focusHandler = null } } } - override fun show(focusHandler: FocusHandler): Unit = showMock.call(focusHandler) + override fun show(focusHandler: FH): Unit = showMock.call(focusHandler) override fun hide(): Unit = hideMock.call() } diff --git a/test-utils-keyboard/src/commonTest/kotlin/MockFocusHandlerTest.kt b/test-utils-keyboard/src/commonTest/kotlin/MockFocusHandlerTest.kt index 7a7e1f569..4f24c4ec3 100644 --- a/test-utils-keyboard/src/commonTest/kotlin/MockFocusHandlerTest.kt +++ b/test-utils-keyboard/src/commonTest/kotlin/MockFocusHandlerTest.kt @@ -30,17 +30,17 @@ class MockFocusHandlerTest { @Test fun test_give_focus() = testFocusHandler { assertFalse(isFocused) - simulateGiveFocus() + giveFocus() assertTrue(isFocused) } @Test fun test_remove_focus() = testFocusHandler { assertFalse(isFocused) - simulateGiveFocus() + giveFocus() assertTrue(isFocused) - simulateRemoveFocus() + removeFocus() assertFalse(isFocused) } @@ -48,10 +48,10 @@ class MockFocusHandlerTest { fun test_give_remove_multiple_times() = testFocusHandler { assertFalse(isFocused) repeat(10) { - simulateGiveFocus() + giveFocus() assertTrue(isFocused) - simulateRemoveFocus() + removeFocus() assertFalse(isFocused) } } diff --git a/test-utils-keyboard/src/commonTest/kotlin/MockKeyboardManagerTest.kt b/test-utils-keyboard/src/commonTest/kotlin/MockKeyboardManagerTest.kt index 7ecb61f4a..571ffa946 100644 --- a/test-utils-keyboard/src/commonTest/kotlin/MockKeyboardManagerTest.kt +++ b/test-utils-keyboard/src/commonTest/kotlin/MockKeyboardManagerTest.kt @@ -18,7 +18,6 @@ package com.splendo.kaluga.test.keyboard import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.keyboard.FocusHandler import com.splendo.kaluga.test.architecture.UIThreadViewModelTest import com.splendo.kaluga.test.base.mock.verify import kotlinx.coroutines.CoroutineScope @@ -28,7 +27,7 @@ import kotlin.test.assertTrue class MockKeyboardManagerTest : UIThreadViewModelTest() { - class ViewModel(keyboardManagerBuilder: MockKeyboardManager.Builder, val focusHandler: FocusHandler) : BaseLifecycleViewModel() { + class ViewModel(keyboardManagerBuilder: MockKeyboardManager.Builder, val focusHandler: MockFocusHandler) : BaseLifecycleViewModel() { val keyboardManager = keyboardManagerBuilder.create(coroutineScope) @@ -38,7 +37,7 @@ class MockKeyboardManagerTest : UIThreadViewModelTest { val mockFocusHandler = MockFocusHandler() - val keyboardManagerBuilder = MockKeyboardManager.Builder() + val keyboardManagerBuilder = MockKeyboardManager.Builder() override val viewModel: ViewModel = ViewModel(keyboardManagerBuilder, mockFocusHandler) } diff --git a/test-utils-keyboard/src/iosMain/kotlin/MockFocusHandler.kt b/test-utils-keyboard/src/iosMain/kotlin/MockFocusHandler.kt deleted file mode 100644 index 746028c9a..000000000 --- a/test-utils-keyboard/src/iosMain/kotlin/MockFocusHandler.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.test.keyboard - -import com.splendo.kaluga.keyboard.FocusHandler - -actual class MockFocusHandler : BaseMockFocusHandler(), FocusHandler { - override fun requestFocus() { - super.giveFocus() - } - - actual fun simulateGiveFocus() { - requestFocus() - } - - actual fun simulateRemoveFocus() { - super.removeFocus() - } -} diff --git a/test-utils-keyboard/src/jsMain/kotlin/MockFocusHandler.kt b/test-utils-keyboard/src/jsMain/kotlin/MockFocusHandler.kt deleted file mode 100644 index cc2dc3dba..000000000 --- a/test-utils-keyboard/src/jsMain/kotlin/MockFocusHandler.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.test.keyboard - -import com.splendo.kaluga.keyboard.FocusHandler - -actual class MockFocusHandler : BaseMockFocusHandler(), FocusHandler { - actual fun simulateGiveFocus() { - super.giveFocus() - } - - actual fun simulateRemoveFocus() { - super.removeFocus() - } -} diff --git a/test-utils-keyboard/src/jvmMain/kotlin/MockFocusHandler.kt b/test-utils-keyboard/src/jvmMain/kotlin/MockFocusHandler.kt deleted file mode 100644 index cc2dc3dba..000000000 --- a/test-utils-keyboard/src/jvmMain/kotlin/MockFocusHandler.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2022 Splendo Consulting B.V. The Netherlands - - 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 com.splendo.kaluga.test.keyboard - -import com.splendo.kaluga.keyboard.FocusHandler - -actual class MockFocusHandler : BaseMockFocusHandler(), FocusHandler { - actual fun simulateGiveFocus() { - super.giveFocus() - } - - actual fun simulateRemoveFocus() { - super.removeFocus() - } -} From cee1f25bd8fa4970974c48b8ac4c5ba269d16427 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Tue, 29 Nov 2022 11:13:21 +0100 Subject: [PATCH 038/227] Cleanup, restructuring files & bottom sheet bugfix --- .../architecture/compose/ContextExtensions.kt | 28 ++ .../navigation/BottomSheetNavigator.kt | 163 ++++++++++ .../navigation/BottomSheetNavigatorState.kt | 37 +++ .../compose/navigation/BottomSheetRoute.kt | 47 +++ ...gator.kt => BottomSheetRouteController.kt} | 22 +- .../navigation/ModalBottomSheetNavigator.kt | 296 ----------------- .../navigation/NavigationExtensions.kt | 4 +- .../architecture/compose/navigation/Route.kt | 4 +- .../compose/navigation/RouteController.kt | 213 ++++++++++++ .../compose/navigation/RouteNavigator.kt | 304 ++++-------------- .../compose/navigation/SetupNavHost.kt | 99 ++++++ .../compose/navigation/result/HandleResult.kt | 70 ++++ .../navigation/result/NavHostResultHandler.kt | 96 ++++++ .../compose/navigation/result/setResult.kt | 27 ++ .../compose/viewModel/ViewModelComposable.kt | 19 +- .../compose/ArchitectureDetailsLayout.kt | 4 +- .../compose/ArchitectureLayout.kt | 22 +- .../architecture/compose/BottomSheetLayout.kt | 6 +- .../compose/BottomSheetSubPageLayout.kt | 6 +- .../resources/compose/ResourcesLayout.kt | 4 +- .../kotlin/ComposeKeyboardManager.kt | 4 +- .../commonMain/kotlin/BaseKeyboardManager.kt | 2 +- .../src/jvmMain/kotlin/KeyboardManager.kt | 2 +- .../system/network/NetworkManager.kt | 19 +- test-utils-system/build.gradle.kts | 2 +- .../kotlin/network/MockNetworkManager.kt | 1 - 26 files changed, 902 insertions(+), 599 deletions(-) create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt rename architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/{CombinedNavigator.kt => BottomSheetRouteController.kt} (54%) delete mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/SetupNavHost.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/setResult.kt diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt new file mode 100644 index 000000000..ec1510662 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt @@ -0,0 +1,28 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose + +import android.content.Context +import android.content.ContextWrapper +import androidx.appcompat.app.AppCompatActivity + +internal val Context.activity: AppCompatActivity? get() = when (this) { + is AppCompatActivity -> this + is ContextWrapper -> baseContext.activity // recursive lookup + else -> null +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt new file mode 100644 index 000000000..4d640ad1e --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt @@ -0,0 +1,163 @@ +package com.splendo.kaluga.architecture.compose.navigation + +import android.util.Log +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.compose.rememberNavController +import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable +import com.splendo.kaluga.architecture.compose.navigation.result.NavHostResultHandler +import com.splendo.kaluga.architecture.navigation.NavigationAction +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** + * A Navigator managed by a [BottomSheetRouteController] + * @param navigationMapper A mapper that converts an [NavigationAction] handled by this navigator into a [BottomSheetRoute] + */ +sealed class BottomSheetNavigator>( + private val navigationMapper: (A) -> BottomSheetRoute +) : Navigator, ComposableLifecycleSubscribable { + + abstract val routeController: BottomSheetRouteController + + override fun navigate(action: A) = routeController.navigate(navigationMapper(action)) +} + +/** + * A [BottomSheetNavigator] that creates and manages a [ModalBottomSheetLayout]. This should be on top of any [ModalBottomSheetNavigator] + * @param navigationMapper Maps [A] to a [BottomSheetRoute] to be navigated to. + * @param initialSheetValue The [ModalBottomSheetValue] the [ModalBottomSheetLayout] should default to. + * @param parentRouteController A [RouteController] managing the parent view of the [ModalBottomSheetLayout] + * @param contentRootResultHandlers A list of [NavHostResultHandler] to be added to the root view of the content. + * @param contentBuilder The [BottomSheetContentBuilder] describing the navigation graph of the content. + * @param sheetContentBuilder The [BottomSheetContentBuilder] describing the navigation graph of the sheet content. + */ +class RootModalBottomSheetNavigator>( + navigationMapper: (A) -> BottomSheetRoute, + private val initialSheetValue: ModalBottomSheetValue = ModalBottomSheetValue.Hidden, + parentRouteController: RouteController? = null, + contentRootResultHandlers: List> = emptyList(), + contentBuilder: BottomSheetContentBuilder, + sheetContentBuilder: BottomSheetContentBuilder +) : BottomSheetNavigator(navigationMapper) { + + private val bottomSheetNavigationState = MutableStateFlow(null) + private val sheetStateCoroutineScope = MutableStateFlow(null) + override val routeController = BottomSheetRouteController( + BottomSheetContentRouteController(bottomSheetNavigationState, parentRouteController), + BottomSheetSheetContentRouteController( + bottomSheetNavigationState, + sheetStateCoroutineScope + ) + ) + + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + val contentNavController = rememberNavController() + bottomSheetNavigationState.tryEmit( + BottomSheetNavigatorState( + contentNavController, + rememberNavController(), + rememberModalBottomSheetState(initialValue = initialSheetValue) + ) + ) + sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) + SetupNavigatingModalBottomSheetLayout( + bottomSheetNavigationState, + routeController.sheetContentRouteController, + sheetContentBuilder, + { + contentRootResultHandlers.forEach { + it.HandleResult(viewModel = this, navHostController = contentNavController) + } + content() + }, + contentBuilder + ) + } +} + +/** + * A [BottomSheetNavigator] that is managed by a [ModalBottomSheetLayout]. + * @param bottomSheetNavigatorState The [BottomSheetNavigatorState] controlling the routes. + * @param contentResultHandlers A list of [NavHostResultHandler] to be added to the content of this navigator. + * @param sheetContentResultHandlers A list of [NavHostResultHandler] to be added to the sheet content of this navigator. + * @param navigationMapper Maps [A] to a [BottomSheetRoute] to be navigated to. + */ +class ModalBottomSheetNavigator>( + bottomSheetNavigatorState: StateFlow, + contentResultHandlers: List> = emptyList(), + sheetContentResultHandlers: List> = emptyList(), + navigationMapper: (A) -> BottomSheetRoute, +) : BottomSheetNavigator(navigationMapper) { + + private val sheetStateCoroutineScope = MutableStateFlow(null) + + override val routeController = BottomSheetRouteController( + BottomSheetContentRouteController(bottomSheetNavigatorState), + BottomSheetSheetContentRouteController( + bottomSheetNavigatorState, + sheetStateCoroutineScope + ) + ) + + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) + routeController.contentRouteController.AddResultHandlers(viewModel = this, resultHandlers = contentResultHandlers) + routeController.sheetContentRouteController.sheetContentRouteController.AddResultHandlers(viewModel = this, resultHandlers = sheetContentResultHandlers) + content() + } +} + +@Composable +private fun SetupNavigatingModalBottomSheetLayout( + bottomSheetNavigationState: StateFlow, + sheetContentRouteController: RouteController, + sheetContent: BottomSheetContentBuilder, + contentRoot: @Composable () -> Unit, + content: BottomSheetContentBuilder +) { + val currentBottomSheetNavigationState by bottomSheetNavigationState.collectAsState() + currentBottomSheetNavigationState?.let { navigationState -> + ModalBottomSheetLayout( + sheetContent = { + if (navigationState.sheetState.isVisible) { + HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) + } + Box(Modifier.defaultMinSize(minHeight = 1.dp)) { + Log.d("TEST", "SetupNavigatingModalBottomSheetLayout ${navigationState.sheetContentNavHostController}") + SetupNavHost( + bottomSheetNavigatorState = bottomSheetNavigationState, + navHostController = { sheetContentNavHostController }, + ) { state -> + sheetContent( + state + ) + } + } + }, + sheetState = navigationState.sheetState, + ) { + SetupNavHost( + bottomSheetNavigatorState = bottomSheetNavigationState, + navHostController = { contentNavHostController }, + rootView = contentRoot + ) { state -> + content( + state + ) + } + } + } +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState.kt new file mode 100644 index 000000000..8bc399798 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState.kt @@ -0,0 +1,37 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation + +import androidx.compose.material.ModalBottomSheetState +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import kotlinx.coroutines.flow.StateFlow + +/** + * The State of a [BottomSheetNavigator] + * @param contentNavHostController The [NavHostController] managing the content of the bottom sheet. + * @param sheetContentNavHostController The [NavHostController] managing the sheet content of the bottom sheet. + * @param sheetState The [ModalBottomSheetState] of the bottom sheet. + */ +data class BottomSheetNavigatorState( + val contentNavHostController: NavHostController, + val sheetContentNavHostController: NavHostController, + val sheetState: ModalBottomSheetState +) + +typealias BottomSheetContentBuilder = NavGraphBuilder.(bottomSheetNavigationState: StateFlow) -> Unit diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt new file mode 100644 index 000000000..254a89db3 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt @@ -0,0 +1,47 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation + +import androidx.compose.material.ModalBottomSheetLayout + +/** + * Route for navigating a [ModalBottomSheetLayout] using a [BottomSheetSheetContentRouteController] + */ +sealed class BottomSheetRoute { + /** + * Navigates within the sheet content of the [ModalBottomSheetLayout] + * @param route The [Route] to navigate + */ + data class SheetContent(val route: Route) : BottomSheetRoute() + + /** + * Navigates within the content of the [ModalBottomSheetLayout] + * @param route The [Route] to navigate + */ + data class Content(val route: Route) : BottomSheetRoute() +} + +/** + * Converts a [Route] to a [BottomSheetRoute.SheetContent] route + */ +val Route.bottomSheetSheetContent get() = BottomSheetRoute.SheetContent(this) + +/** + * Converts a [Route] to a [BottomSheetRoute.Content] route + */ +val Route.bottomSheetContent get() = BottomSheetRoute.Content(this) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRouteController.kt similarity index 54% rename from architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt rename to architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRouteController.kt index 5f0e02193..f29530abb 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRouteController.kt @@ -17,13 +17,17 @@ package com.splendo.kaluga.architecture.compose.navigation -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable -import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscriber -import com.splendo.kaluga.architecture.navigation.ActivityNavigator -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationSpec -import com.splendo.kaluga.architecture.navigation.Navigator - +import androidx.compose.material.ModalBottomSheetLayout +/** + * A controller that handles the [Route] for a [ModalBottomSheetLayout] + */ +class BottomSheetRouteController( + internal val contentRouteController: RouteController, + internal val sheetContentRouteController: BottomSheetSheetContentRouteController +) { + fun navigate(route: BottomSheetRoute) = when (route) { + is BottomSheetRoute.SheetContent -> sheetContentRouteController.navigate(route.route) + is BottomSheetRoute.Content -> contentRouteController.navigate(route.route) + } +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt deleted file mode 100644 index 7445b907c..000000000 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt +++ /dev/null @@ -1,296 +0,0 @@ -package com.splendo.kaluga.architecture.compose.navigation - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.ModalBottomSheetLayout -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch - -/** - * Route for navigating a [ModalBottomSheetLayout] using a [BottomSheetSheetContentRouteController] - */ -sealed class BottomSheetRoute { - /** - * Navigates within the sheet content of the [ModalBottomSheetLayout] - * @param route The [Route] to navigate - */ - data class SheetContent(val route: Route) : BottomSheetRoute() - - /** - * Navigates within the content of the [ModalBottomSheetLayout] - * @param route The [Route] to navigate - */ - data class Content(val route: Route) : BottomSheetRoute() -} - -/** - * Converts a [Route] to a [BottomSheetRoute.SheetContent] route - */ -val Route.bottomSheetSheetContent get() = BottomSheetRoute.SheetContent(this) - -/** - * Converts a [Route] to a [BottomSheetRoute.Content] route - */ -val Route.bottomSheetContent get() = BottomSheetRoute.Content(this) - -/** - * A controller that handles the [Route] for a [ModalBottomSheetLayout] - */ -class BottomSheetRouteController( - internal val contentRouteController: RouteController, - internal val sheetContentRouteController: BottomSheetSheetContentRouteController -) { - fun navigate(route: BottomSheetRoute) = when (route) { - is BottomSheetRoute.SheetContent -> sheetContentRouteController.navigate(route.route) - is BottomSheetRoute.Content -> contentRouteController.navigate(route.route) - } -} - -class BottomSheetContentNavHostRouteController( - provider: StateFlow, - parent: RouteController? = null, -) : NavHostProvidingRouteController(provider, parent) { - override fun BottomSheetNavigatorState.provide(): NavHostController = contentNavHostController -} - -class BottomSheetSheetContentNavHostRouteController( - provider: StateFlow, - parent: RouteController? = null, -) : NavHostProvidingRouteController(provider, parent) { - override fun BottomSheetNavigatorState.provide(): NavHostController = sheetContentNavHostController -} - -fun > BottomSheetContentRouteNavigator( - bottomSheetNavigator: StateFlow, - parentRouteController: RouteController? = null, - navigationMapper: (A) -> Route -): NavHostProvidingContentRouteNavigator = NavHostProvidingContentRouteNavigator( - BottomSheetContentNavHostRouteController(bottomSheetNavigator, parentRouteController), - navigationMapper -) - -class BottomSheetSheetContentRouteController( - private val bottomSheetNavigatorState: StateFlow, - private val coroutineScope: StateFlow -) : RouteController { - - private val sheetContentRouteController = BottomSheetSheetContentNavHostRouteController(bottomSheetNavigatorState, this) - - override fun navigate(newRoute: Route) { - bottomSheetNavigatorState.value?.let { (_, _, sheetState) -> - if (!sheetState.isVisible) { - coroutineScope.value?.launch { - sheetState.animateTo(ModalBottomSheetValue.Expanded) - } - } - } - sheetContentRouteController.navigate(newRoute) - } - - override fun back(result: Route.Result): Boolean { - val navHostController = bottomSheetNavigatorState.value?.contentNavHostController - return if (navHostController != null && navHostController.backQueue.isNotEmpty()) { - navHostController.previousBackStackEntry?.setResult(result) - navHostController.popBackStack() - } else { - close() - true - } - } - - override fun close() { - bottomSheetNavigatorState.value?.let { (_, _, sheetState) -> - coroutineScope.value?.launch { - sheetState.hide() - } - } - } -} - -sealed class ModalBottomSheetNavigator>( - private val navigationMapper: (A) -> BottomSheetRoute -) : Navigator, ComposableLifecycleSubscribable { - - abstract val routeController: BottomSheetRouteController - - override fun navigate(action: A) = routeController.navigate(navigationMapper(action)) -} - -class NavHostModalBottomSheetNavigator>( - navigationMapper: (A) -> BottomSheetRoute, - contentBuilder: BottomSheetContentBuilder, - contentRootResultHandlers: List> = emptyList(), - sheetContentBuilder: BottomSheetContentBuilder, - private val initialSheetValue: ModalBottomSheetValue = ModalBottomSheetValue.Hidden -) : ModalBottomSheetNavigator(navigationMapper) { - - private val bottomSheetNavigationState = MutableStateFlow(null) - private val sheetStateCoroutineScope = MutableStateFlow(null) - override val routeController = BottomSheetRouteController( - BottomSheetContentNavHostRouteController(bottomSheetNavigationState), - BottomSheetSheetContentRouteController( - bottomSheetNavigationState, - sheetStateCoroutineScope - ) - ) - - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> - val contentNavController = rememberNavController() - bottomSheetNavigationState.tryEmit( - BottomSheetNavigatorState( - contentNavController, - rememberNavController(), - rememberModalBottomSheetState(initialValue = initialSheetValue) - ) - ) - sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) - SetupNavigatingModalBottomSheetLayout( - bottomSheetNavigationState, - routeController.sheetContentRouteController, - sheetContentBuilder, - { - contentRootResultHandlers.forEach { - it.HandleResult(viewModel = this, navHostController = contentNavController) - } - content() - }, - contentBuilder - ) - } -} - -class NavHostModalBottomSheetContentNavigator>( - bottomSheetNavigatorState: StateFlow, - navigationMapper: (A) -> BottomSheetRoute, -) : ModalBottomSheetNavigator(navigationMapper) { - - private val sheetStateCoroutineScope = MutableStateFlow(null) - - - override val routeController = BottomSheetRouteController( - BottomSheetContentNavHostRouteController(bottomSheetNavigatorState), - BottomSheetSheetContentRouteController( - bottomSheetNavigatorState, - sheetStateCoroutineScope - ) - ) - - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> - sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) - content() - } -} - -data class BottomSheetNavigatorState( - val contentNavHostController: NavHostController, - val sheetContentNavHostController: NavHostController, - val sheetState: ModalBottomSheetState - ) - -fun StateFlow.contentNavHostControllerState(coroutineScope: CoroutineScope) = map { it?.contentNavHostController }.stateIn(coroutineScope, SharingStarted.Lazily, null) -fun StateFlow.sheetContentNavHostControllerState(coroutineScope: CoroutineScope) = map { it?.sheetContentNavHostController }.stateIn(coroutineScope, SharingStarted.Lazily, null) - -typealias BottomSheetRootContentBuilder = (bottomSheetNavigationState: StateFlow) -> Unit -typealias BottomSheetContentBuilder = NavGraphBuilder.(bottomSheetNavigationState: StateFlow) -> Unit - -/** - * Creates a [ModalBottomSheetLayout] that supports navigation using a [ModalBottomSheetNavigator] - * @param sheetContent The [BottomSheetContentBuilder] for building the navigation sheet content of the [ModalBottomSheetLayout] - * @param contentRoot The [BottomSheetRootContentBuilder] for building the root view of the content of the [ModalBottomSheetLayout] - * @param content The [BottomSheetContentBuilder] for building the navigation content of the [ModalBottomSheetLayout] - */ -@Composable -private fun SetupNavigatingModalBottomSheetLayout( - bottomSheetNavigationState: StateFlow, - sheetContentRouteController: RouteController, - sheetContent: BottomSheetContentBuilder, - contentRoot: @Composable () -> Unit, - content: BottomSheetContentBuilder -) { - val currentBottomSheetNavigationState by bottomSheetNavigationState.collectAsState() - currentBottomSheetNavigationState?.let { navigationState -> - ModalBottomSheetLayout( - sheetContent = { - if (navigationState.sheetState.isVisible) { - HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) - } - Box(Modifier.defaultMinSize(minHeight = 1.dp)) { - SetupNavHost( - bottomSheetNavigatorState = bottomSheetNavigationState, - navHostController = { sheetContentNavHostController }, - ) {state -> - sheetContent( - state - ) - } - } - }, - sheetState = navigationState.sheetState, - ) { - SetupNavHost( - bottomSheetNavigatorState = bottomSheetNavigationState, - navHostController = { contentNavHostController }, - rootView = contentRoot - ) { state -> - content( - state - ) - } - } - } -} - -@Composable -fun SetupNavHost( - bottomSheetNavigatorState: StateFlow, - navHostController: BottomSheetNavigatorState.() -> NavHostController, - builder: BottomSheetContentBuilder -) = SetupNavHost( - bottomSheetNavigatorState = bottomSheetNavigatorState, - navHostController = navHostController, - rootView = { Spacer(modifier = Modifier.fillMaxWidth()) }, - builder -) - -@Composable -fun SetupNavHost( - bottomSheetNavigatorState: StateFlow, - navHostController: BottomSheetNavigatorState.() -> NavHostController, - rootView: @Composable () -> Unit, - builder: BottomSheetContentBuilder -) { - val currentBottomSheetNavigatorState by bottomSheetNavigatorState.collectAsState() - currentBottomSheetNavigatorState?.let { - SetupNavHost( - navHostController = it.navHostController(), - startDestination = ROOT_VIEW - ) { - composable(ROOT_VIEW, content = { rootView() }) - builder(bottomSheetNavigatorState) - } - } -} \ No newline at end of file diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/NavigationExtensions.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/NavigationExtensions.kt index f120ae28d..a8dc7374b 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/NavigationExtensions.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/NavigationExtensions.kt @@ -5,9 +5,11 @@ import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import com.splendo.kaluga.architecture.compose.activity import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable @@ -20,7 +22,7 @@ import kotlinx.coroutines.launch */ @Composable fun LifecycleSubscribable.bind(): LifecycleSubscribable { - LocalAppCompatActivity.current?.let { activity -> + LocalContext.current.activity?.let { activity -> DisposableEffect(Unit) { subscribe(activity) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt index 2aca3c577..30222f5eb 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt @@ -24,8 +24,6 @@ import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.NavigationBundleValue -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec -import com.splendo.kaluga.architecture.navigation.toBundle import com.splendo.kaluga.base.text.KalugaDateFormatter import com.splendo.kaluga.base.text.iso8601Pattern import kotlinx.serialization.builtins.BooleanArraySerializer @@ -181,7 +179,7 @@ sealed class Route { companion object { const val KEY = "com.splendo.kaluga.architecture.compose.navigation.Route.Result.KEY" } - object Empty: Result() + object Empty : Result() data class Data, Bundle : NavigationBundle>(val bundle: Bundle) : Result() } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt new file mode 100644 index 000000000..56ae84cb0 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt @@ -0,0 +1,213 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation + +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.navigation.NavDestination +import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.result.NavHostResultHandler +import com.splendo.kaluga.architecture.compose.navigation.result.setResult +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +/** + * Controller for navigating to a [Route] + */ +sealed interface RouteController { + + @Composable + fun AddResultHandlers(viewModel: BaseLifecycleViewModel, resultHandlers: List>) + + /** + * Navigates to a [Route] + * @param newRoute The [Route] to navigate to + */ + fun navigate(newRoute: Route) + + /** + * Navigates back + */ + fun back(result: Route.Result): Boolean + + /** + * Closes this RouteController + */ + fun close() +} + +/** + * Abstract [RouteController] that navigates on a [NavHostController] provided by a [Provider] + * @param provider The [Provider] of the [NavHostController] + * @param parentRouteController An optional parent [RouteController] managing this controller. + */ +sealed class ProvidingNavHostRouteController( + private val provider: StateFlow, + private val parentRouteController: RouteController? = null +) : RouteController { + + /** + * Converts [Provider] into a [NavHostController] + */ + abstract fun Provider.provide(): NavHostController + + override fun navigate(newRoute: Route) { + val navHostController = provider.value?.provide() ?: return + when (newRoute) { + is Route.NextRoute<*, *> -> navHostController.navigate(newRoute.route) { + launchSingleTop = true + } + is Route.FromRoute<*, *> -> navHostController.navigate(newRoute.route) { + popUpTo(newRoute.from) + } + is Route.Replace<*, *> -> { + navHostController.popBackStack() + navHostController.navigate(newRoute.route) + } + is Route.PopTo<*, *> -> { + navHostController.getBackStackEntry(newRoute.route).setResult(newRoute.result) + navHostController.popBackStack(newRoute.route, false) + } + is Route.PopToIncluding<*, *> -> { + navHostController.popBackStack(newRoute.route, true) + } + is Route.Back -> back(newRoute.result) + is Route.PopToRoot -> { + navHostController.getBackStackEntry(ROOT_VIEW).setResult(newRoute.result) + navHostController.popBackStack(ROOT_VIEW, false) + } + is Route.Close -> close() + is Route.Launcher<*> -> newRoute.launch() + } + } + + override fun back(result: Route.Result): Boolean { + val navHostController = provider.value?.provide() + return if (navHostController != null && navHostController.backQueue.isNotEmpty()) { + navHostController.previousBackStackEntry?.setResult(result) + navHostController.popBackStack() + } else { + parentRouteController?.back(result) ?: false + } + } + + override fun close() { + provider.value?.provide()?.popBackStack(ROOT_VIEW, true) + parentRouteController?.close() + } + + @Composable + override fun AddResultHandlers(viewModel: BaseLifecycleViewModel, resultHandlers: List>) { + if (resultHandlers.isNotEmpty()) { + val currentProvider by provider.collectAsState() + currentProvider?.provide()?.let { currentNavHostController -> + resultHandlers.forEach { + it.HandleResult(viewModel = viewModel, navHostController = currentNavHostController) + } + } + } + } +} + +/** + * Simple [ProvidingNavHostRouteController] where the provider is a flow of [NavHostController] + * @param provider The flow of [NavHostController] to be managed by this Route Controller + * @param parentRouteController An optional parent [RouteController] managing this controller. + */ +class NavHostRouteController( + provider: StateFlow, + parentRouteController: RouteController? = null +) : ProvidingNavHostRouteController(provider, parentRouteController) { + override fun NavHostController.provide(): NavHostController = this +} + +/** + * [ProvidingNavHostRouteController] where the [NavHostController] is provided by the content of a [BottomSheetNavigatorState] + * @param provider The flow of [BottomSheetNavigatorState] where the [BottomSheetNavigatorState.contentNavHostController] is to be managed by this Route Controller + * @param parentRouteController An optional parent [RouteController] managing this controller. + */ +class BottomSheetContentRouteController( + provider: StateFlow, + parentRouteController: RouteController? = null, +) : ProvidingNavHostRouteController(provider, parentRouteController) { + override fun BottomSheetNavigatorState.provide(): NavHostController = contentNavHostController +} + +/** + * [RouteController] for the sheet content of a [BottomSheetNavigatorState] + * @param bottomSheetNavigatorState The flow of [BottomSheetNavigatorState] where the [BottomSheetNavigatorState.sheetContentNavHostController] is to be managed by this Route Controller + * @param coroutineScope A flow of [CoroutineScope] for managing the [ModalBottomSheetState]. + */ +class BottomSheetSheetContentRouteController( + private val bottomSheetNavigatorState: StateFlow, + private val coroutineScope: StateFlow +) : RouteController { + + inner class NavHostRouteController : ProvidingNavHostRouteController(bottomSheetNavigatorState) { + override fun BottomSheetNavigatorState.provide(): NavHostController = sheetContentNavHostController + } + + internal val sheetContentRouteController = NavHostRouteController() + + override fun navigate(newRoute: Route) { + bottomSheetNavigatorState.value?.let { (_, _, sheetState) -> + if (!sheetState.isVisible) { + coroutineScope.value?.launch { + sheetState.animateTo(ModalBottomSheetValue.Expanded) + } + } + } + when (newRoute) { + is Route.Close -> close() + is Route.Back -> back(newRoute.result) + is Route.PopToRoot -> close() + else -> sheetContentRouteController.navigate(newRoute) + } + } + + override fun back(result: Route.Result): Boolean { + val navHostController = bottomSheetNavigatorState.value?.sheetContentNavHostController + val rootRoute = NavDestination.createRoute(ROOT_VIEW).hashCode() + return if (navHostController != null && navHostController.backQueue.isNotEmpty() && navHostController.previousBackStackEntry?.destination?.id != rootRoute) { + navHostController.previousBackStackEntry?.setResult(result) + navHostController.popBackStack() + } else { + close() + true + } + } + + override fun close() { + bottomSheetNavigatorState.value?.let { (_, sheetContentNavHostController, sheetState) -> + sheetContentNavHostController.popBackStack(ROOT_VIEW, false) + coroutineScope.value?.launch { + sheetState.hide() + } + } + } + + @Composable + override fun AddResultHandlers(viewModel: BaseLifecycleViewModel, resultHandlers: List>) { + sheetContentRouteController.AddResultHandlers(viewModel = viewModel, resultHandlers = resultHandlers) + } +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt index 616fdea97..1c9853686 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt @@ -1,114 +1,19 @@ package com.splendo.kaluga.architecture.compose.navigation -import android.os.Bundle -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.navigation.NavBackStackEntry +import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable +import com.splendo.kaluga.architecture.compose.navigation.result.NavHostResultHandler import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.NavigationBundle -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.Navigator -import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec -import com.splendo.kaluga.architecture.navigation.toBundle -import com.splendo.kaluga.architecture.navigation.toNavigationBundle -import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.serialization.KSerializer - -/** - * Controller for navigating to a [Route] - */ -sealed interface RouteController { - /** - * Navigates to a [Route] - * @param newRoute The [Route] to navigate to - */ - fun navigate(newRoute: Route) - - /** - * Navigates back - */ - fun back(result: Route.Result): Boolean - - /** - * Closes this RouteController - */ - fun close() -} - -sealed class NavHostProvidingRouteController( - private val provider: StateFlow, - private val parentRouteController: RouteController? = null -) : RouteController { - - abstract fun Provider.provide(): NavHostController - - override fun navigate(newRoute: Route) { - val navHostController = provider.value?.provide() ?: return - when (newRoute) { - is Route.NextRoute<*, *> -> navHostController.navigate(newRoute.route) { - launchSingleTop = true - } - is Route.FromRoute<*, *> -> navHostController.navigate(newRoute.route) { - popUpTo(newRoute.from) - } - is Route.Replace<*, *> -> { - navHostController.popBackStack() - navHostController.navigate(newRoute.route) - } - is Route.PopTo<*, *> -> { - navHostController.getBackStackEntry(newRoute.route).setResult(newRoute.result) - navHostController.popBackStack(newRoute.route, false) - } - is Route.PopToIncluding<*, *> -> { - navHostController.popBackStack(newRoute.route, true) - } - is Route.Back -> back(newRoute.result) - is Route.PopToRoot -> { - navHostController.getBackStackEntry(ROOT_VIEW).setResult(newRoute.result) - navHostController.popBackStack(ROOT_VIEW, false) - } - is Route.Close -> close() - is Route.Launcher<*> -> newRoute.launch() - } - } - - override fun back(result: Route.Result): Boolean { - val navHostController = provider.value?.provide() - return if (navHostController != null && navHostController.backQueue.isNotEmpty()) { - navHostController.previousBackStackEntry?.setResult(result) - navHostController.popBackStack() - } else { - parentRouteController?.back(result) ?: false - } - } - - override fun close() { - provider.value?.provide()?.popBackStack(ROOT_VIEW, true) - parentRouteController?.close() - } -} - -class NavHostRouteController( - provider: StateFlow, - parentRouteController: RouteController? = null -) : NavHostProvidingRouteController(provider, parentRouteController) { - override fun NavHostController.provide(): NavHostController = this -} typealias RouteContentBuilder = NavGraphBuilder.(StateFlow) -> Unit @@ -120,6 +25,9 @@ sealed class RouteNavigator>( private val navigationMapper: (A) -> Route ) : Navigator, ComposableLifecycleSubscribable { + /** + * [RouteController] managing the [Route] navigation + */ abstract val routeController: RouteController override fun navigate(action: A) { @@ -127,11 +35,18 @@ sealed class RouteNavigator>( } } -class NavHostRouteNavigator>( +/** + * A [RouteNavigator] that creates and manages a [NavHost]. This should be on top of any [NavHostRouteNavigator] + * @param navigationMapper Maps [A] to a [Route] to be navigated to. + * @param parentRouteController A [RouteController] managing the parent layout of the [NavHost]. + * @param rootResultHandlers A list of [NavHostResultHandler] to be added to the root view. + * @param contentBuilder The [RouteContentBuilder] describing the navigation graph + */ +class RootNavHostRouteNavigator>( navigationMapper: (A) -> Route, parentRouteController: RouteController? = null, - rootResultHandlers: List> = emptyList(), - builder: RouteContentBuilder + rootResultHandlers: List> = emptyList(), + contentBuilder: RouteContentBuilder ) : RouteNavigator(navigationMapper) { private val navHostController = MutableStateFlow(null) @@ -147,167 +62,82 @@ class NavHostRouteNavigator>( resultHandler.HandleResult(viewModel = this, navHostController = navController) } content() - }, - builder = builder + }, + builder = contentBuilder ) } } -class NavHostProvidingContentRouteNavigator, Provider>( - override val routeController: NavHostProvidingRouteController, +/** + * A [RouteNavigator] that is managed by a [NavHost] + * @param routeController The [ProvidingNavHostRouteController] controlling the route. + * @param resultHandlers The [NavHostResultHandler] to add to this navigator. + * @param navigationMapper Maps [A] to a [Route] to be navigated to. + */ +sealed class ProvidingNavHostRouteNavigator, Provider>( + override val routeController: ProvidingNavHostRouteController, + resultHandlers: List> = emptyList(), navigationMapper: (A) -> Route ) : RouteNavigator(navigationMapper) { override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + routeController.AddResultHandlers(viewModel = this, resultHandlers = resultHandlers) content() } } -fun > NavHostContentRouteNavigator( +/** + * A [RouteNavigator] that navigates for a flow of [NavHostController] + * @param navHostController The flow of [NavHostController] to manage this navigator + * @param resultHandlers The [NavHostResultHandler] to add to this navigator. + * @param navigationMapper Maps [A] to a [Route] to be navigated to. + */ +class NavHostRouteNavigator> ( navHostController: StateFlow, + resultHandlers: List> = emptyList(), parentRouteController: RouteController? = null, navigationMapper: (A) -> Route -): NavHostProvidingContentRouteNavigator = NavHostProvidingContentRouteNavigator( +) : ProvidingNavHostRouteNavigator( NavHostRouteController(navHostController, parentRouteController), + resultHandlers, navigationMapper ) -@Composable -fun SetupNavHost( - navHostController: StateFlow, - builder: RouteContentBuilder -) = SetupNavHost( - navHostController = navHostController, - rootView = { Spacer(modifier = Modifier.fillMaxWidth()) }, - builder -) - -@Composable -fun SetupNavHost( - navHostController: StateFlow, - rootView: @Composable () -> Unit, - builder: RouteContentBuilder -) { - val currentNavHostController by navHostController.collectAsState() - currentNavHostController?.let { - SetupNavHost( - navHostController = it, - startDestination = ROOT_VIEW - ) { - composable(ROOT_VIEW, content = { rootView() }) - builder(navHostController) - } - } -} - /** - * Creates a a [NavHost] for a given [RouteController] - * @param startDestination The start destination of the [NavHost] - * @param builder The [RouteContentBuilder] for building the content of the [NavHost] + * A [RouteNavigator] that navigates for the content of a flow of [BottomSheetNavigatorState] + * @param bottomSheetNavigator The flow of [BottomSheetNavigatorState] to manage this navigator + * @param resultHandlers The [NavHostResultHandler] to add to this navigator. + * @param navigationMapper Maps [A] to a [Route] to be navigated to. */ -@Composable -fun SetupNavHost( - navHostController: NavHostController, - startDestination: String, - builder: NavGraphBuilder.() -> Unit -) { - NavHost( - navController = navHostController, - startDestination = startDestination, - builder = { builder() } - ) -} - -sealed class NavHostRootResultHandler { - abstract val viewModelClass: Class - abstract val retain: Boolean - abstract val onResult: ViewModel.(R) -> Unit - @Composable - internal abstract fun NavHostController.HandleResult(callback: (R) -> Unit) - - data class Bundle>( - override val viewModelClass: Class, - val spec: NavigationBundleSpec, - override val retain: Boolean = false, - override val onResult: ViewModel.(NavigationBundle) -> Unit - ) : NavHostRootResultHandler>() { - @Composable - override fun NavHostController.HandleResult(callback: (NavigationBundle) -> Unit) = HandleResult(spec, retain) { callback(this) } - } - - data class Type( - override val viewModelClass: Class, - val spec: NavigationBundleSpecType, - override val retain: Boolean, - override val onResult: ViewModel.(R) -> Unit - ) : NavHostRootResultHandler() { - - @Composable - override fun NavHostController.HandleResult(callback: (R) -> Unit) = HandleResult(spec, retain) { callback(this) } - } - - @Composable - internal fun HandleResult(viewModel: BaseLifecycleViewModel, navHostController: NavHostController) { - viewModelClass.cast(viewModel)?.let { vm -> - navHostController.HandleResult { vm.onResult(it) } - } - } -} - -inline fun > NavigationBundleSpec.NavHostRootResultHandler( - retain: Boolean = false, - noinline onResult: ViewModel.(NavigationBundle) -> Unit -) = NavHostRootResultHandler.Bundle(ViewModel::class.java, this, retain, onResult) - -inline fun NavigationBundleSpecType.NavHostRootResultHandler( - retain: Boolean = false, - noinline onResult: ViewModel.(R) -> Unit -) = NavHostRootResultHandler.Type(ViewModel::class.java, this, retain, onResult) - -inline fun KSerializer.NavHostRootResultHandler( - retain: Boolean = false, - noinline onResult: ViewModel.(R) -> Unit -) = NavHostRootResultHandler.Type(ViewModel::class.java, NavigationBundleSpecType.SerializedType(this), retain, onResult) +class BottomSheetContentNavHostRouteNavigator> ( + bottomSheetNavigator: StateFlow, + resultHandlers: List> = emptyList(), + parentRouteController: RouteController? = null, + navigationMapper: (A) -> Route +) : ProvidingNavHostRouteNavigator( + BottomSheetContentRouteController(bottomSheetNavigator, parentRouteController), + resultHandlers, + navigationMapper +) /** - * Handles a [Route.Result] matching a given [NavigationBundleSpec] - * @param spec The [NavigationBundleSpec] used to create the [Route.Result] - * @param retain If `true` the result will be retained in the [NavBackStackEntry]. It will be deleted otherwise. - * @param onResult Method for handling the received result + * A [RouteNavigator] that navigates for the sheet content of a flow of [BottomSheetNavigatorState] + * @param bottomSheetNavigator The flow of [BottomSheetNavigatorState] to manage this navigator + * @param resultHandlers The [NavHostResultHandler] to add to this navigator. + * @param navigationMapper Maps [A] to a [Route] to be navigated to. */ -@Composable -fun > NavHostController.HandleResult( - spec: NavigationBundleSpec, - retain: Boolean = false, - onResult: NavigationBundle.() -> Unit -) = HandleResult(retain) { toNavigationBundle(spec).onResult() } +class BottomSheetSheetContentNavHostRouteNavigator> ( + bottomSheetNavigator: StateFlow, + resultHandlers: List> = emptyList(), + navigationMapper: (A) -> Route +) : RouteNavigator(navigationMapper) { -/** - * Handles a [Route.Result] matching a given [NavigationBundleSpecType] - * Requires that the [Route.Result] is described by a [SingleValueNavigationSpec] matching the [NavigationBundleSpecType] - * @param type The [NavigationBundleSpecType] stored in the result - * @param retain If `true` the result will be retained in the [NavBackStackEntry]. It will be deleted otherwise. - * @param onResult Method for handling the received result - */ -@Composable -fun NavHostController.HandleResult( - type: NavigationBundleSpecType, - retain: Boolean = false, - onResult: R.() -> Unit -) = HandleResult(retain) { toTypedProperty(type).onResult() } + private val coroutineScopeState = MutableStateFlow(null) + override val routeController = BottomSheetSheetContentRouteController(bottomSheetNavigator, coroutineScopeState) -@Composable -internal fun NavHostController.HandleResult(retain: Boolean = false, onResult: Bundle.() -> Unit) { - val result = currentBackStackEntry?.savedStateHandle?.getStateFlow(Route.Result.KEY, null)?.collectAsState() - result?.value?.let { - onResult(it) - if (!retain) { - currentBackStackEntry?.savedStateHandle?.remove(Route.Result.KEY) - } + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + coroutineScopeState.tryEmit(rememberCoroutineScope()) + routeController.sheetContentRouteController.AddResultHandlers(viewModel = this, resultHandlers = resultHandlers) + content() } } - -internal fun NavBackStackEntry.setResult(result: Route.Result) = when (result) { - is Route.Result.Empty -> savedStateHandle.remove(Route.Result.KEY) - is Route.Result.Data<*, *> -> savedStateHandle[Route.Result.KEY] = result.bundle.toBundle() -} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/SetupNavHost.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/SetupNavHost.kt new file mode 100644 index 000000000..fa62eb0f2 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/SetupNavHost.kt @@ -0,0 +1,99 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import kotlinx.coroutines.flow.StateFlow + +/** + * Creates a a [NavHost] for a given [NavHostController] flow + * @param navHostController A [StateFlow] of [NavHostController] to be managed by the [NavHost] + * @param rootView The Composable view to display as the root of the [NavHost] + * @param builder The [RouteContentBuilder] for building the content of the [NavHost] + */ +@Composable +fun SetupNavHost( + navHostController: StateFlow, + rootView: @Composable () -> Unit = { Spacer(modifier = Modifier.fillMaxWidth()) }, + builder: RouteContentBuilder +) { + val currentNavHostController by navHostController.collectAsState() + currentNavHostController?.let { + SetupNavHost( + navHostController = it, + startDestination = ROOT_VIEW + ) { + composable(ROOT_VIEW, content = { rootView() }) + builder(navHostController) + } + } +} + +/** + * Creates a a [NavHost] for a given [BottomSheetNavigatorState] flow + * @param bottomSheetNavigatorState A [StateFlow] of [BottomSheetNavigatorState] to be managed by the [NavHost] + * @param navHostController Converts a [BottomSheetNavigatorState] into a [NavHostController] + * @param rootView The Composable view to display as the root of the [NavHost] + * @param builder The [RouteContentBuilder] for building the content of the [NavHost] + */ +@Composable +fun SetupNavHost( + bottomSheetNavigatorState: StateFlow, + navHostController: BottomSheetNavigatorState.() -> NavHostController, + rootView: @Composable () -> Unit = { Spacer(modifier = Modifier.fillMaxWidth()) }, + builder: BottomSheetContentBuilder +) { + val currentBottomSheetNavigatorState by bottomSheetNavigatorState.collectAsState() + currentBottomSheetNavigatorState?.let { + SetupNavHost( + navHostController = it.navHostController(), + startDestination = ROOT_VIEW + ) { + composable(ROOT_VIEW, content = { rootView() }) + builder(bottomSheetNavigatorState) + } + } +} + +/** + * Creates a a [NavHost] for a given [NavHostController] + * @param navHostController The [NavHostController] tp be managed by the [NavHost] + * @param startDestination The start destination of the [NavHost] + * @param builder The [RouteContentBuilder] for building the content of the [NavHost] + */ +@Composable +fun SetupNavHost( + navHostController: NavHostController, + startDestination: String, + builder: NavGraphBuilder.() -> Unit +) { + NavHost( + navController = navHostController, + startDestination = startDestination, + builder = { builder() } + ) +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt new file mode 100644 index 000000000..4b82bc022 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt @@ -0,0 +1,70 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation.result + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.compose.navigation.Route +import com.splendo.kaluga.architecture.navigation.NavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec +import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.navigation.toTypedProperty + +/** + * Handles a [Route.Result] matching a given [NavigationBundleSpec] + * @param spec The [NavigationBundleSpec] used to create the [Route.Result] + * @param retain If `true` the result will be retained in the [NavBackStackEntry]. It will be deleted otherwise. + * @param onResult Method for handling the received result + */ +@Composable +fun > NavHostController.HandleResult( + spec: NavigationBundleSpec, + retain: Boolean = false, + onResult: NavigationBundle.() -> Unit +) = HandleResult(retain) { toNavigationBundle(spec).onResult() } + +/** + * Handles a [Route.Result] matching a given [NavigationBundleSpecType] + * Requires that the [Route.Result] is described by a [SingleValueNavigationSpec] matching the [NavigationBundleSpecType] + * @param type The [NavigationBundleSpecType] stored in the result + * @param retain If `true` the result will be retained in the [NavBackStackEntry]. It will be deleted otherwise. + * @param onResult Method for handling the received result + */ +@Composable +fun NavHostController.HandleResult( + type: NavigationBundleSpecType, + retain: Boolean = false, + onResult: R.() -> Unit +) = HandleResult(retain) { toTypedProperty(type).onResult() } + +@Composable +internal fun NavHostController.HandleResult(retain: Boolean = false, onResult: Bundle.() -> Unit) { + val result = currentBackStackEntry?.savedStateHandle?.getStateFlow(Route.Result.KEY, null)?.collectAsState() + result?.value?.let { + onResult(it) + if (!retain) { + currentBackStackEntry?.savedStateHandle?.remove(Route.Result.KEY) + } + } +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt new file mode 100644 index 000000000..7d20f6d68 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt @@ -0,0 +1,96 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation.result + +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import com.splendo.kaluga.architecture.navigation.NavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow +import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import kotlinx.serialization.KSerializer + +/** + * A Handler that will allow a [NavHostController] to handle Result of a given type [R] to be received by a given [ViewModel] + */ +sealed class NavHostResultHandler { + abstract val viewModelClass: Class + abstract val retain: Boolean + abstract val onResult: ViewModel.(R) -> Unit + @Composable + internal abstract fun NavHostController.HandleResult(callback: (R) -> Unit) + + /** + * [NavHostResultHandler] that handles a result of a [NavigationBundle] + */ + data class Bundle>( + override val viewModelClass: Class, + val spec: NavigationBundleSpec, + override val retain: Boolean = false, + override val onResult: ViewModel.(NavigationBundle) -> Unit + ) : NavHostResultHandler>() { + @Composable + override fun NavHostController.HandleResult(callback: (NavigationBundle) -> Unit) = HandleResult(spec, retain) { callback(this) } + } + + /** + * [NavHostResultHandler] that handles a result of a [NavigationBundleSpecType] + */ + data class Type( + override val viewModelClass: Class, + val spec: NavigationBundleSpecType, + override val retain: Boolean, + override val onResult: ViewModel.(R) -> Unit + ) : NavHostResultHandler() { + + @Composable + override fun NavHostController.HandleResult(callback: (R) -> Unit) = HandleResult(spec, retain) { callback(this) } + } + + @Composable + internal fun HandleResult(viewModel: BaseLifecycleViewModel, navHostController: NavHostController) { + viewModelClass.cast(viewModel)?.let { vm -> + navHostController.HandleResult { vm.onResult(it) } + } + } +} + +/** + * Creates a [NavHostResultHandler.Bundle] of [R] for this [NavigationBundleSpec] + */ +inline fun > NavigationBundleSpec.NavHostResultHandler( + retain: Boolean = false, + noinline onResult: ViewModel.(NavigationBundle) -> Unit +) = NavHostResultHandler.Bundle(ViewModel::class.java, this, retain, onResult) + +/** + * Creates a [NavHostResultHandler.Type] of [R] for this [NavigationBundleSpecType] + */ +inline fun NavigationBundleSpecType.NavHostResultHandler( + retain: Boolean = false, + noinline onResult: ViewModel.(R) -> Unit +) = NavHostResultHandler.Type(ViewModel::class.java, this, retain, onResult) + +/** + * Creates a [NavHostResultHandler.Type] of [R] for this [KSerializer] + */ +inline fun KSerializer.NavHostResultHandler( + retain: Boolean = false, + noinline onResult: ViewModel.(R) -> Unit +) = NavHostResultHandler.Type(ViewModel::class.java, NavigationBundleSpecType.SerializedType(this), retain, onResult) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/setResult.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/setResult.kt new file mode 100644 index 000000000..9c4768945 --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/setResult.kt @@ -0,0 +1,27 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation.result + +import androidx.navigation.NavBackStackEntry +import com.splendo.kaluga.architecture.compose.navigation.Route +import com.splendo.kaluga.architecture.navigation.toBundle + +internal fun NavBackStackEntry.setResult(result: Route.Result) = when (result) { + is Route.Result.Empty -> savedStateHandle.remove(Route.Result.KEY) + is Route.Result.Data<*, *> -> savedStateHandle[Route.Result.KEY] = result.bundle.toBundle() +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt index 5408b0ec0..1f328e64b 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt @@ -1,7 +1,5 @@ package com.splendo.kaluga.architecture.compose.viewModel -import android.content.Context -import android.content.ContextWrapper import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.DisallowComposableCalls @@ -15,6 +13,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner +import com.splendo.kaluga.architecture.compose.activity import com.splendo.kaluga.architecture.compose.lifecycle.ComposableLifecycleSubscribable import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker @@ -57,11 +56,13 @@ private fun ViewModelComposable( content: @Composable (ViewModel.() -> Unit)? = null ) { viewModel.linkLifecycle(activity, fragmentManager) - val composeLifecycleSubscribables = viewModel.ComposableLifecycleSubscribable + val composeLifecycleSubscribables = remember(viewModel) { + viewModel.ComposableLifecycleSubscribable + } if (composeLifecycleSubscribables.isEmpty()) { content?.invoke(viewModel) } else { - val modifier = viewModel.ComposableLifecycleSubscribable.reduceRight { new, acc -> + val modifier = composeLifecycleSubscribables.reduceRight { new, acc -> object : ComposableLifecycleSubscribable { override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = { content -> new.modifier(this) { acc.modifier(this, content) } @@ -79,7 +80,7 @@ private fun ViewModelComposable( @Composable @Deprecated( "Does not work for configuration changes (e.g. rotation).", -replaceWith = ReplaceWith("viewModel()", "androidx.lifecycle.viewmodel.compose.viewModel") + replaceWith = ReplaceWith("viewModel()", "androidx.lifecycle.viewmodel.compose.viewModel") ) fun store(provider: @Composable () -> VM): VM = provider().also { handleLocalViewModelStore(it) } @@ -154,7 +155,6 @@ private fun VM.linkLifecycle(activity: AppCompatAc val lifecycle = LocalLifecycleOwner.current.lifecycle DisposableEffect(Unit) { val observer = VmObserver(this@linkLifecycle, activity, fragmentManager) - lifecycle.addObserver(observer) onDispose { @@ -198,10 +198,3 @@ private class VmObserver(private val viewModel: VM, lifecycleSubscribables.forEach { it.unsubscribe() } } } - -private val Context.activity: AppCompatActivity? get() = when (this) { - is AppCompatActivity -> this - is ContextWrapper -> baseContext.activity // recursive lookup - else -> null -} - diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt index d90cfa198..68771cece 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt @@ -23,7 +23,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetContentRouteNavigator +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetContentNavHostRouteNavigator import com.splendo.kaluga.architecture.compose.navigation.BottomSheetNavigatorState import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.state @@ -40,7 +40,7 @@ fun ArchitectureDetailsLayout(inputDetails: InputDetails, bottomSheetNavigatorSt val viewModel = koinViewModel { parametersOf( inputDetails, - BottomSheetContentRouteNavigator( + BottomSheetContentNavHostRouteNavigator( bottomSheetNavigatorState, navigationMapper = ::architectureDetailsNavigationRouteMapper ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt index 1baaa606b..8e0113a22 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt @@ -30,7 +30,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -47,9 +46,9 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.navigation.compose.composable import com.google.android.material.composethemeadapter.MdcTheme import com.splendo.kaluga.architecture.compose.mutableState -import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetNavigator -import com.splendo.kaluga.architecture.compose.navigation.NavHostRootResultHandler +import com.splendo.kaluga.architecture.compose.navigation.RootModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.navigation.composable +import com.splendo.kaluga.architecture.compose.navigation.result.NavHostResultHandler import com.splendo.kaluga.architecture.compose.navigation.route import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity @@ -85,8 +84,14 @@ fun ArchitectureLayout() { MdcTheme { val viewModel = koinViewModel { parametersOf( - NavHostModalBottomSheetNavigator( + RootModalBottomSheetNavigator( navigationMapper = ::architectureNavigationRouteMapper, + contentRootResultHandlers = listOf( + InputDetails.serializer().NavHostResultHandler { + nameInput.post(it.name) + numberInput.post(it.number.toString()) + } + ), contentBuilder = { bottomSheetNavigationState -> composable( type = NavigationBundleSpecType.SerializedType(InputDetails.serializer()) @@ -94,12 +99,6 @@ fun ArchitectureLayout() { ArchitectureDetailsLayout(inputDetails, bottomSheetNavigationState) } }, - contentRootResultHandlers = listOf( - InputDetails.serializer().NavHostRootResultHandler { - nameInput.post(it.name) - numberInput.post(it.number.toString()) - } - ), sheetContentBuilder = { bottomSheetNavigationState -> composable(ArchitectureNavigationAction.BottomSheet.route()) { BottomSheetLayout( @@ -111,8 +110,7 @@ fun ArchitectureLayout() { bottomSheetNavigationState ) } - }, - initialSheetValue = ModalBottomSheetValue.Hidden + } ) ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index ddc837b76..ef48b3cca 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.splendo.kaluga.architecture.compose.navigation.BottomSheetNavigatorState import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetContentNavigator +import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel @@ -38,9 +38,9 @@ import org.koin.core.parameter.parametersOf fun BottomSheetLayout(bottomSheetNavigationState: StateFlow) { val viewModel = koinViewModel { parametersOf( - NavHostModalBottomSheetContentNavigator( + ModalBottomSheetNavigator( bottomSheetNavigationState, - ::bottomSheetNavigationRouteMapper + navigationMapper = ::bottomSheetNavigationRouteMapper ) ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt index d33cd851b..e40808ed2 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt @@ -28,7 +28,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.splendo.kaluga.architecture.compose.navigation.BottomSheetNavigatorState import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation -import com.splendo.kaluga.architecture.compose.navigation.NavHostModalBottomSheetContentNavigator +import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.example.compose.Constants import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel @@ -42,9 +42,9 @@ fun BottomSheetSubPageLayout( ) { val viewModel = koinViewModel { parametersOf( - NavHostModalBottomSheetContentNavigator( + ModalBottomSheetNavigator( bottomSheetNavigationState, - ::bottomSheetSubPageNavigationRouteMapper + navigationMapper = ::bottomSheetSubPageNavigationRouteMapper ) ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt index 3dcee4363..07d80b6a4 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt @@ -35,7 +35,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.navigation.compose.composable import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteNavigator +import com.splendo.kaluga.architecture.compose.navigation.RootNavHostRouteNavigator import com.splendo.kaluga.architecture.compose.navigation.next import com.splendo.kaluga.architecture.compose.navigation.route import com.splendo.kaluga.architecture.compose.state @@ -67,7 +67,7 @@ fun ResourcesLayout() { MdcTheme { val viewModel = koinViewModel { parametersOf( - NavHostRouteNavigator( + RootNavHostRouteNavigator( navigationMapper = { action -> when (action) { is ResourcesListNavigationAction.Button -> action.next diff --git a/keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt b/keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt index afb71838d..27638c75a 100644 --- a/keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt +++ b/keyboard-compose/src/androidLibMain/kotlin/ComposeKeyboardManager.kt @@ -28,8 +28,7 @@ import java.util.WeakHashMap class ComposeKeyboardManager(internal var currentFocusManager: FocusManager? = null) : BaseKeyboardManager { - class Builder : BaseKeyboardManager.Builder, - ComposableLifecycleSubscribable { + class Builder : BaseKeyboardManager.Builder, ComposableLifecycleSubscribable { private val builtManagers = WeakHashMap() @@ -44,7 +43,6 @@ class ComposeKeyboardManager(internal var currentFocusManager: FocusManager? = n } content() } - } override fun show(focusHandler: ComposeFocusHandler) { diff --git a/keyboard/src/commonMain/kotlin/BaseKeyboardManager.kt b/keyboard/src/commonMain/kotlin/BaseKeyboardManager.kt index 71b892de8..54a699a26 100644 --- a/keyboard/src/commonMain/kotlin/BaseKeyboardManager.kt +++ b/keyboard/src/commonMain/kotlin/BaseKeyboardManager.kt @@ -34,7 +34,7 @@ interface BaseKeyboardManager { /** * Base KeyboardManager builder class, which used to create a [BaseKeyboardManager] */ - interface Builder : LifecycleSubscribableMarker { + interface Builder : LifecycleSubscribableMarker { /** * Creates KeyboardManager object diff --git a/keyboard/src/jvmMain/kotlin/KeyboardManager.kt b/keyboard/src/jvmMain/kotlin/KeyboardManager.kt index 1397fd243..930e0cf06 100644 --- a/keyboard/src/jvmMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/jvmMain/kotlin/KeyboardManager.kt @@ -33,4 +33,4 @@ class KeyboardManager : BaseKeyboardManager { override fun hide() { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } -} \ No newline at end of file +} diff --git a/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt b/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt index 0cb610db7..c34a5f09b 100644 --- a/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt +++ b/system/src/iosMain/kotlin/com.splendo.kaluga/system/network/NetworkManager.kt @@ -25,7 +25,6 @@ import kotlinx.cinterop.alloc import kotlinx.cinterop.asStableRef import kotlinx.cinterop.nativeHeap import kotlinx.cinterop.ptr -import kotlinx.cinterop.reinterpret import kotlinx.cinterop.staticCFunction import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -59,14 +58,9 @@ import platform.SystemConfiguration.SCNetworkReachabilitySetDispatchQueue import platform.SystemConfiguration.kSCNetworkReachabilityFlagsIsWWAN import platform.SystemConfiguration.kSCNetworkReachabilityFlagsReachable import platform.darwin.DISPATCH_QUEUE_PRIORITY_DEFAULT -import platform.darwin.DISPATCH_QUEUE_SERIAL -import platform.darwin.DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL import platform.darwin.dispatch_get_main_queue -import platform.darwin.dispatch_qos_class_t import platform.darwin.dispatch_queue_attr_make_with_qos_class -import platform.darwin.dispatch_queue_attr_tVar import platform.darwin.dispatch_queue_create -import platform.darwin.dispatch_queue_serial_t import platform.posix.QOS_CLASS_UTILITY actual class DefaultNetworkManager internal constructor( @@ -100,11 +94,14 @@ actual class DefaultNetworkManager internal constructor( } } private val nwPathMonitor: nw_path_monitor_t = nw_path_monitor_create().apply { - val queue = dispatch_queue_create("com.splendo.kaluga.system.network", dispatch_queue_attr_make_with_qos_class( - null, - QOS_CLASS_UTILITY, - DISPATCH_QUEUE_PRIORITY_DEFAULT - )) + val queue = dispatch_queue_create( + "com.splendo.kaluga.system.network", + dispatch_queue_attr_make_with_qos_class( + null, + QOS_CLASS_UTILITY, + DISPATCH_QUEUE_PRIORITY_DEFAULT + ) + ) nw_path_monitor_set_queue( this, queue diff --git a/test-utils-system/build.gradle.kts b/test-utils-system/build.gradle.kts index 5a488a166..3ed49f0a7 100644 --- a/test-utils-system/build.gradle.kts +++ b/test-utils-system/build.gradle.kts @@ -20,4 +20,4 @@ kotlin { dependencies {} } } -} \ No newline at end of file +} diff --git a/test-utils-system/src/commonMain/kotlin/network/MockNetworkManager.kt b/test-utils-system/src/commonMain/kotlin/network/MockNetworkManager.kt index 321404f15..33ee86793 100644 --- a/test-utils-system/src/commonMain/kotlin/network/MockNetworkManager.kt +++ b/test-utils-system/src/commonMain/kotlin/network/MockNetworkManager.kt @@ -78,4 +78,3 @@ class MockBaseNetworkManager(initialNetworkConnectionType: NetworkConnectionType val stopMonitoringMock = ::stopMonitoring.mock() override suspend fun stopMonitoring(): Unit = stopMonitoringMock.call() } - From d255b8b8740972323785d4ab7a0b194256464dc8 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Tue, 29 Nov 2022 17:50:59 +0100 Subject: [PATCH 039/227] Added proper non-NavHost navigation for compose --- architecture-compose/build.gradle.kts | 1 + .../architecture/compose/ContextExtensions.kt | 2 +- .../navigation/BottomSheetNavigator.kt | 40 +- .../compose/navigation/BottomSheetRoute.kt | 11 +- .../compose/navigation/ComposableNavSpec.kt | 412 ++++++++++++++++++ ...uteNavigator.kt => ComposableNavigator.kt} | 83 ++-- .../architecture/compose/navigation/Route.kt | 18 +- .../compose/navigation/RouteController.kt | 1 - .../navigation/result/NavHostResultHandler.kt | 16 +- .../kotlin/navigation/NavigationSpec.kt | 148 ++++++- .../kotlin/navigation/Navigator.kt | 91 +--- 11 files changed, 654 insertions(+), 169 deletions(-) create mode 100644 architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt rename architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/{RouteNavigator.kt => ComposableNavigator.kt} (58%) diff --git a/architecture-compose/build.gradle.kts b/architecture-compose/build.gradle.kts index a0d6d2170..bd978363d 100644 --- a/architecture-compose/build.gradle.kts +++ b/architecture-compose/build.gradle.kts @@ -32,4 +32,5 @@ dependencies { implementationDependency(Dependencies.AndroidX.Compose.Material) implementationDependency(Dependencies.AndroidX.Navigation.Compose) implementationDependency(Dependencies.KotlinX.Coroutines.Core) + implementationDependency(Dependencies.AndroidX.Browser) } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt index ec1510662..2097c341b 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/ContextExtensions.kt @@ -21,7 +21,7 @@ import android.content.Context import android.content.ContextWrapper import androidx.appcompat.app.AppCompatActivity -internal val Context.activity: AppCompatActivity? get() = when (this) { +val Context.activity: AppCompatActivity? get() = when (this) { is AppCompatActivity -> this is ContextWrapper -> baseContext.activity // recursive lookup else -> null diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt index 4d640ad1e..d152d26a6 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt @@ -27,17 +27,41 @@ import kotlinx.coroutines.flow.StateFlow * @param navigationMapper A mapper that converts an [NavigationAction] handled by this navigator into a [BottomSheetRoute] */ sealed class BottomSheetNavigator>( - private val navigationMapper: (A) -> BottomSheetRoute + private val navigationMapper: @Composable (A) -> BottomSheetComposableNavSpec ) : Navigator, ComposableLifecycleSubscribable { abstract val routeController: BottomSheetRouteController - override fun navigate(action: A) = routeController.navigate(navigationMapper(action)) + private val actionState = MutableStateFlow(null) + + override fun navigate(action: A) { + actionState.value = action + } + + final override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + SetupNavigationAction() + contentModifier(content) + } + + abstract val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit + + @Composable + protected fun BaseLifecycleViewModel.SetupNavigationAction() { + val currentAction by actionState.collectAsState() + currentAction?.let { action -> + when (val spec = navigationMapper(action)) { + is BottomSheetRoute -> routeController.navigate(spec) + is BottomSheetLaunchedNavigation -> spec.launchedNavigation.Launch(this) + } + actionState.tryEmit(null) + } + } + } /** * A [BottomSheetNavigator] that creates and manages a [ModalBottomSheetLayout]. This should be on top of any [ModalBottomSheetNavigator] - * @param navigationMapper Maps [A] to a [BottomSheetRoute] to be navigated to. + * @param navigationMapper Maps [A] to a [BottomSheetComposableNavSpec] to be navigated to. * @param initialSheetValue The [ModalBottomSheetValue] the [ModalBottomSheetLayout] should default to. * @param parentRouteController A [RouteController] managing the parent view of the [ModalBottomSheetLayout] * @param contentRootResultHandlers A list of [NavHostResultHandler] to be added to the root view of the content. @@ -45,7 +69,7 @@ sealed class BottomSheetNavigator>( * @param sheetContentBuilder The [BottomSheetContentBuilder] describing the navigation graph of the sheet content. */ class RootModalBottomSheetNavigator>( - navigationMapper: (A) -> BottomSheetRoute, + navigationMapper: @Composable (A) -> BottomSheetComposableNavSpec, private val initialSheetValue: ModalBottomSheetValue = ModalBottomSheetValue.Hidden, parentRouteController: RouteController? = null, contentRootResultHandlers: List> = emptyList(), @@ -63,7 +87,7 @@ class RootModalBottomSheetNavigator>( ) ) - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> val contentNavController = rememberNavController() bottomSheetNavigationState.tryEmit( BottomSheetNavigatorState( @@ -93,13 +117,13 @@ class RootModalBottomSheetNavigator>( * @param bottomSheetNavigatorState The [BottomSheetNavigatorState] controlling the routes. * @param contentResultHandlers A list of [NavHostResultHandler] to be added to the content of this navigator. * @param sheetContentResultHandlers A list of [NavHostResultHandler] to be added to the sheet content of this navigator. - * @param navigationMapper Maps [A] to a [BottomSheetRoute] to be navigated to. + * @param navigationMapper Maps [A] to a [BottomSheetComposableNavSpec] to be navigated to. */ class ModalBottomSheetNavigator>( bottomSheetNavigatorState: StateFlow, contentResultHandlers: List> = emptyList(), sheetContentResultHandlers: List> = emptyList(), - navigationMapper: (A) -> BottomSheetRoute, + navigationMapper: @Composable (A) -> BottomSheetComposableNavSpec ) : BottomSheetNavigator(navigationMapper) { private val sheetStateCoroutineScope = MutableStateFlow(null) @@ -112,7 +136,7 @@ class ModalBottomSheetNavigator>( ) ) - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) routeController.contentRouteController.AddResultHandlers(viewModel = this, resultHandlers = contentResultHandlers) routeController.sheetContentRouteController.sheetContentRouteController.AddResultHandlers(viewModel = this, resultHandlers = sheetContentResultHandlers) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt index 254a89db3..311115e3b 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute.kt @@ -19,10 +19,12 @@ package com.splendo.kaluga.architecture.compose.navigation import androidx.compose.material.ModalBottomSheetLayout +sealed class BottomSheetComposableNavSpec + /** * Route for navigating a [ModalBottomSheetLayout] using a [BottomSheetSheetContentRouteController] */ -sealed class BottomSheetRoute { +sealed class BottomSheetRoute : BottomSheetComposableNavSpec() { /** * Navigates within the sheet content of the [ModalBottomSheetLayout] * @param route The [Route] to navigate @@ -36,6 +38,8 @@ sealed class BottomSheetRoute { data class Content(val route: Route) : BottomSheetRoute() } +data class BottomSheetLaunchedNavigation(val launchedNavigation: ComposableNavSpec.LaunchedNavigation) : BottomSheetComposableNavSpec() + /** * Converts a [Route] to a [BottomSheetRoute.SheetContent] route */ @@ -45,3 +49,8 @@ val Route.bottomSheetSheetContent get() = BottomSheetRoute.SheetContent(this) * Converts a [Route] to a [BottomSheetRoute.Content] route */ val Route.bottomSheetContent get() = BottomSheetRoute.Content(this) + +/** + * Converts a [ComposableNavSpec.LaunchedNavigation] to a [BottomSheetLaunchedNavigation] + */ +val ComposableNavSpec.LaunchedNavigation.bottomSheetComposable get() = BottomSheetLaunchedNavigation(this) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt new file mode 100644 index 000000000..bb0e2be3d --- /dev/null +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt @@ -0,0 +1,412 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.architecture.compose.navigation + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContract +import androidx.activity.result.contract.ActivityResultContracts +import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.fragment.app.DialogFragment +import com.splendo.kaluga.architecture.compose.activity +import com.splendo.kaluga.architecture.navigation.IntentFlag +import com.splendo.kaluga.architecture.navigation.NavigationAction +import com.splendo.kaluga.architecture.navigation.NavigationBundle +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction +import com.splendo.kaluga.architecture.navigation.toBundle +import com.splendo.kaluga.architecture.navigation.toFlags +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import java.net.URL +import kotlin.reflect.KClass +import kotlin.reflect.safeCast + +/** + * Spec for navigating with a [ComposableNavigator] + */ +sealed class ComposableNavSpec { + + companion object { + /** + * Creates a [ComposableNavSpec.Launcher] to open the camera to take a picture. + * @param uri The [Uri] where the taken picture should be stored. + * @param onResult Returns `true` if a picture was taken successfully. + */ + inline fun TakePicture(uri: Uri?, noinline onResult: VM.(Boolean) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.TakePicture(), + { uri }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to open the camera to capture a video. + * @param uri The [Uri] where the captured video should be stored. + * @param onResult Returns `true` if a video was captured successfully. + */ + inline fun CaptureVideo(uri: Uri?, noinline onResult: VM.(Boolean) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.CaptureVideo(), + { uri }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.IntentLauncher] to open the email app. + * @param settings The [NavigationSpec.Email.EmailSettings] to apply to the email. + */ + fun SendEmail(settings: NavigationSpec.Email.EmailSettings) = IntentLauncher { settings.intent } + + /** + * Creates a [ComposableNavSpec.Launcher] to open Photo app to pick an image or video + * @param type The [ActivityResultContracts.PickVisualMedia.VisualMediaType] to be selected. + * @param onResult Returns an [Uri] of the image or video selected, or `null` if nothing was selected. + */ + inline fun PickVisualMedia(type: ActivityResultContracts.PickVisualMedia.VisualMediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo, + noinline onResult: VM.(Uri?) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.PickVisualMedia(), + { PickVisualMediaRequest(mediaType = type) }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to open Photo app to pick a number of images or videos + * @param type The [ActivityResultContracts.PickVisualMedia.VisualMediaType] to be selected. + * @param onResult Returns a list of [Uri] of the images or videos selected. + */ + inline fun PickMultipleVisualMedia(maxItems: Int? = null, type: ActivityResultContracts.PickVisualMedia.VisualMediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo, + noinline onResult: VM.(List) -> Unit) = Launcher( + VM::class, + maxItems?.let { ActivityResultContracts.PickMultipleVisualMedia(it) } ?: ActivityResultContracts.PickMultipleVisualMedia(), + { PickVisualMediaRequest(mediaType = type) }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to open the file manager app to pick a file. + * @param types List of mime types allowed to be selected. + * @param onResult Returns an [Uri] of the file selected, or `null` if nothing was selected. + */ + inline fun OpenDocument(types: List, noinline onResult: VM.(Uri?) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.OpenDocument(), + { types.toTypedArray() }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to open the file manager app to pick a number of files. + * @param types List of mime types allowed to be selected. + * @param onResult Returns a list of [Uri] of the documents selected. + */ + inline fun OpenMultipleDocument(types: List, noinline onResult: VM.(List) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.OpenMultipleDocuments(), + { types.toTypedArray() }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to open create a new file. + * @param fileName The suggested name of the file. + * @param mimeType The mime type of the file. + * @param onResult Returns the [Uri] of the document created. + */ + inline fun CreateDocument(fileName: String, mimeType: String, noinline onResult: VM.(Uri?) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.CreateDocument(mimeType), + { fileName }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to open the file manager app to pick a folder. + * @param startingLocation Optional [Uri] of the folder to start in. + * @param onResult Returns an [Uri] of the folder selected, or `null` if nothing was selected. + */ + inline fun OpenDocumentTree(startingLocation: Uri? = null, noinline onResult: VM.(Uri?) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.OpenDocumentTree(), + { startingLocation }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to open the contacts list to select a contact. + * @param onResult Returns an [Uri] of the contact selected, or `null` if nothing was selected. + */ + inline fun PickContact(noinline onResult: VM.(Uri?) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.PickContact(), + { null }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to request a permission. + * @param permission Key of the permission to be requested. E.g. [android.Manifest.permission.ACCESS_COARSE_LOCATION]. + * @param onResult Returns `true` if the permission was granted, `false` otherwise. + */ + inline fun RequestPermission(permission: String, noinline onResult: VM.(Boolean) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.RequestPermission(), + { permission }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.Launcher] to request multiple permissions. + * @param permissions Keys of the permission to be requested. E.g. [android.Manifest.permission.ACCESS_COARSE_LOCATION]. + * @param onResult Returns map containing whether the permission was granted for each permission in [permissions]. + */ + inline fun RequestMultiplePermissions(permissions: List, noinline onResult: VM.(Map) -> Unit) = Launcher( + VM::class, + ActivityResultContracts.RequestMultiplePermissions(), + { permissions.toTypedArray() }, + onResult + ) + + /** + * Creates a [ComposableNavSpec.IntentLauncher] to the phone screen. + * @param phoneNumber The phoneNumber to dial. + * @param type The [NavigationSpec.Phone.Type] to apply. + */ + fun Phone(phoneNumber: String, type: NavigationSpec.Phone.Type) = IntentLauncher { + val intent = when (type) { + is NavigationSpec.Phone.Type.Dial -> Intent(Intent.ACTION_DIAL) + is NavigationSpec.Phone.Type.Call -> Intent(Intent.ACTION_CALL) + }.apply { + data = Uri.parse("tel:${phoneNumber}") + } + if (intent.resolveActivity(packageManager) != null) intent else null + } + + /** + * Creates a [ComposableNavSpec.IntentLauncher] to the settings screen. + * @param type The [NavigationSpec.Settings.Type] to apply. + */ + fun Settings(type: NavigationSpec.Settings.Type) = IntentLauncher { + val intent = type.intent(this) + if (intent.resolveActivity(packageManager) != null) intent else null + } + + /** + * Creates a [ComposableNavSpec.IntentLauncher] to the text messages screen. + * @param settings The [NavigationSpec.TextMessenger.TextMessengerSettings] to apply. + */ + fun Messenger(settings: NavigationSpec.TextMessenger.TextMessengerSettings) = IntentLauncher { + val intent = settings.intent + if (intent.resolveActivity(packageManager) != null) intent else null + } + + /** + * Creates a [ComposableNavSpec.IntentLauncher] to the browser. + * @param url The [URL] to open. + * @param type The [NavigationSpec.Browser.Type] to apply. + */ + fun Browser(url: URL, type: NavigationSpec.Browser.Type = NavigationSpec.Browser.Type.Normal) = IntentLauncher { + val intent = when (type) { + is NavigationSpec.Browser.Type.CustomTab -> { + val builder = CustomTabsIntent.Builder() + builder.build().intent.apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + data = Uri.parse(url.toURI().toString()) + } + } + is NavigationSpec.Browser.Type.Normal -> { + val uri = Uri.parse(url.toURI().toString()) + Intent(Intent.ACTION_VIEW, uri) + } + } + if (intent.resolveActivity(packageManager) != null) intent else null + } + } + + /** + * A [ComposableNavSpec] that launched into a new screen. + */ + sealed class LaunchedNavigation : ComposableNavSpec() { + @Composable + protected abstract fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit + + /** + * Launch this [LaunchedNavigation] for a given [viewModel]. + */ + @Composable + fun Launch(viewModel: BaseLifecycleViewModel) { + val launcher = createLauncher(viewModel) + LaunchedEffect(launcher) { + launcher.invoke() + } + } + } + + /** + * Navigates using an [ActivityResultLauncher] and a valid input. + * @param viewModelClass The class of the ViewModel the result of this launcher is to be applied to. + * @param activityResultContract The [ActivityResultContract] to launch with. + * @param getInput The input to be provided to [activityResultContract] + * @param onResult Callback to handle the result of [activityResultContract] + */ + data class Launcher( + val viewModelClass: KClass, + val activityResultContract: ActivityResultContract, + val getInput: @Composable () -> I?, + val onResult: VM.(O) -> Unit + ) : LaunchedNavigation() { + + @Composable + override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { + val launcher = rememberLauncherForActivityResult(activityResultContract) { result -> + viewModelClass.safeCast(viewModel)?.let { + it.onResult(result) + } + } + val input = getInput() + return { + input?.let { + launcher.launch(it) + } + } + } + } + + /** + * Closes the current [android.app.Activity] + * @param result Optional result code and + */ + data class CloseActivity(val result: Pair?>? = null) : LaunchedNavigation() { + @Composable + override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { + val activity = LocalContext.current.activity ?: return {} + return { + result?.let { (resultCode, bundle) -> + val data = Intent().apply { + bundle?.let { + putExtras(it.toBundle()) + } + } + activity.setResult(resultCode, data) + } + activity.finish() + } + } + } + + /** + * Shows a [DialogFragment] + * @param tag Optional tag to add to the Dialog + * @param createDialog Function to create the [DialogFragment] to display + */ + data class Dialog( + val tag: String? = null, + val createDialog: () -> DialogFragment + ) : LaunchedNavigation() { + + @Composable + override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { + val fragmentManager = LocalContext.current.activity?.supportFragmentManager ?: return {} + return { + createDialog().show(fragmentManager, tag) + } + } + } + + /** + * Navigates according to a given [Intent] + * @param createIntent Creates the [Intent] to navigate to + */ + data class IntentLauncher(val createIntent: @Composable Activity.() -> Intent?) : LaunchedNavigation() { + + @Composable + override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { + val context = LocalContext.current + val intent = context.activity?.intent ?: return {} + return { + context.startActivity(intent) + } + } + } +} + +/** + * Creates a [ComposableNavSpec.Launcher] that takes the [SingleValueNavigationAction.value] as the input of its [ActivityResultContract]. + * @param activityResultContract The [ActivityResultContract] to launch. + * @param onResult The result received after the launched screen has completed. + */ +inline fun SingleValueNavigationAction.Launcher( + activityResultContract: ActivityResultContract, + noinline onResult: VM.(O) -> Unit +) = ComposableNavSpec.Launcher(VM::class, activityResultContract, { value }, onResult) + +/** + * Creates a [ComposableNavSpec.Launcher] that takes an [Intent] containing the [NavigationAction.bundle] as the input of its [ActivityResultContract]. + * @param activityResultContract The [ActivityResultContract] to launch. + * @param flags A set of [IntentFlag] to be applied to the next screen. + * @param onResult The result received after the launched screen has completed. + */ +inline fun NavigationAction<*>.Launcher( + activityResultContract: ActivityResultContract, + flags: Set = emptySet(), + noinline onResult: VM.(O) -> Unit +) { + val getInput = @Composable { + LocalContext.current.activity?.let { activity -> + Intent(activity, A::class.java).apply { + bundle?.let { + putExtras(it.toBundle()) + } + this.flags = flags.toFlags() + } + } + } + ComposableNavSpec.Launcher(VM::class, activityResultContract, getInput, onResult) +} + +/** + * Creates a [ComposableNavSpec.IntentLauncher] to show a given [Activity] + * @param flags A set of [IntentFlag] to be applied to the next screen. + */ +inline fun NavigationAction<*>.ShowActivity( + flags: Set = emptySet() +) = ComposableNavSpec.IntentLauncher { + Intent(this, A::class.java).apply { + bundle?.let { + putExtras(it.toBundle()) + } + this.flags = flags.toFlags() + } +} + +/** + * Creates a [ComposableNavSpec.CloseActivity] for a given result code. + * @param resultCode If not `null`, this will set the [NavigationAction.bundle] as the result of the activity. + */ +fun NavigationAction<*>.CloseActivity(resultCode: Int?) = ComposableNavSpec.CloseActivity( + resultCode?.let { it to bundle } +) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt similarity index 58% rename from architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt rename to architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt index 1c9853686..33613cc13 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt @@ -1,6 +1,8 @@ package com.splendo.kaluga.architecture.compose.navigation import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController @@ -21,8 +23,8 @@ typealias RouteContentBuilder = NavGraphBuilder.(StateFlow) * A Navigator managed by a [RouteController] * @param navigationMapper A mapper that converts an [NavigationAction] handled by this navigator into a [Route] */ -sealed class RouteNavigator>( - private val navigationMapper: (A) -> Route +sealed class ComposableNavigator>( + private val navigationMapper: @Composable (A) -> ComposableNavSpec ) : Navigator, ComposableLifecycleSubscribable { /** @@ -30,29 +32,50 @@ sealed class RouteNavigator>( */ abstract val routeController: RouteController + private val actionState = MutableStateFlow(null) + override fun navigate(action: A) { - routeController.navigate(navigationMapper(action)) + actionState.value = action + } + + final override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + SetupNavigationAction() + contentModifier(content) + } + + abstract val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit + + @Composable + protected fun BaseLifecycleViewModel.SetupNavigationAction() { + val currentAction by actionState.collectAsState() + currentAction?.let { action -> + when (val spec = navigationMapper(action)) { + is Route -> routeController.navigate(spec) + is ComposableNavSpec.LaunchedNavigation -> spec.Launch(this) + } + actionState.tryEmit(null) + } } } /** - * A [RouteNavigator] that creates and manages a [NavHost]. This should be on top of any [NavHostRouteNavigator] - * @param navigationMapper Maps [A] to a [Route] to be navigated to. + * A [ComposableNavigator] that creates and manages a [NavHost]. This should be on top of any [NavHostComposableNavigator] + * @param navigationMapper Maps [A] to a [ComposableNavSpec] to be navigated to. * @param parentRouteController A [RouteController] managing the parent layout of the [NavHost]. * @param rootResultHandlers A list of [NavHostResultHandler] to be added to the root view. * @param contentBuilder The [RouteContentBuilder] describing the navigation graph */ -class RootNavHostRouteNavigator>( - navigationMapper: (A) -> Route, +class RootNavHostComposableNavigator>( + navigationMapper: @Composable (A) -> ComposableNavSpec, parentRouteController: RouteController? = null, rootResultHandlers: List> = emptyList(), contentBuilder: RouteContentBuilder -) : RouteNavigator(navigationMapper) { +) : ComposableNavigator(navigationMapper) { private val navHostController = MutableStateFlow(null) override val routeController: RouteController = NavHostRouteController(navHostController, parentRouteController) - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> val navController = rememberNavController() navHostController.tryEmit(navController) SetupNavHost( @@ -69,73 +92,73 @@ class RootNavHostRouteNavigator>( } /** - * A [RouteNavigator] that is managed by a [NavHost] + * A [ComposableNavigator] that is managed by a [NavHost] * @param routeController The [ProvidingNavHostRouteController] controlling the route. * @param resultHandlers The [NavHostResultHandler] to add to this navigator. * @param navigationMapper Maps [A] to a [Route] to be navigated to. */ -sealed class ProvidingNavHostRouteNavigator, Provider>( +sealed class ProvidingNavHostComposableNavigator, Provider>( override val routeController: ProvidingNavHostRouteController, resultHandlers: List> = emptyList(), - navigationMapper: (A) -> Route -) : RouteNavigator(navigationMapper) { + navigationMapper: @Composable (A) -> ComposableNavSpec +) : ComposableNavigator(navigationMapper) { - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> routeController.AddResultHandlers(viewModel = this, resultHandlers = resultHandlers) content() } } /** - * A [RouteNavigator] that navigates for a flow of [NavHostController] + * A [ComposableNavigator] that navigates for a flow of [NavHostController] * @param navHostController The flow of [NavHostController] to manage this navigator * @param resultHandlers The [NavHostResultHandler] to add to this navigator. - * @param navigationMapper Maps [A] to a [Route] to be navigated to. + * @param navigationMapper Maps [A] to a [ComposableNavSpec] to be navigated to. */ -class NavHostRouteNavigator> ( +class NavHostComposableNavigator> ( navHostController: StateFlow, resultHandlers: List> = emptyList(), parentRouteController: RouteController? = null, - navigationMapper: (A) -> Route -) : ProvidingNavHostRouteNavigator( + navigationMapper: @Composable (A) -> ComposableNavSpec +) : ProvidingNavHostComposableNavigator( NavHostRouteController(navHostController, parentRouteController), resultHandlers, navigationMapper ) /** - * A [RouteNavigator] that navigates for the content of a flow of [BottomSheetNavigatorState] + * A [ComposableNavigator] that navigates for the content of a flow of [BottomSheetNavigatorState] * @param bottomSheetNavigator The flow of [BottomSheetNavigatorState] to manage this navigator * @param resultHandlers The [NavHostResultHandler] to add to this navigator. - * @param navigationMapper Maps [A] to a [Route] to be navigated to. + * @param navigationMapper Maps [A] to a [ComposableNavSpec] to be navigated to. */ -class BottomSheetContentNavHostRouteNavigator> ( +class BottomSheetContentNavHostComposableNavigator> ( bottomSheetNavigator: StateFlow, resultHandlers: List> = emptyList(), parentRouteController: RouteController? = null, - navigationMapper: (A) -> Route -) : ProvidingNavHostRouteNavigator( + navigationMapper: @Composable (A) -> ComposableNavSpec +) : ProvidingNavHostComposableNavigator( BottomSheetContentRouteController(bottomSheetNavigator, parentRouteController), resultHandlers, navigationMapper ) /** - * A [RouteNavigator] that navigates for the sheet content of a flow of [BottomSheetNavigatorState] + * A [ComposableNavigator] that navigates for the sheet content of a flow of [BottomSheetNavigatorState] * @param bottomSheetNavigator The flow of [BottomSheetNavigatorState] to manage this navigator * @param resultHandlers The [NavHostResultHandler] to add to this navigator. - * @param navigationMapper Maps [A] to a [Route] to be navigated to. + * @param navigationMapper Maps [A] to a [ComposableNavSpec] to be navigated to. */ -class BottomSheetSheetContentNavHostRouteNavigator> ( +class BottomSheetSheetContentNavHostComposableNavigator> ( bottomSheetNavigator: StateFlow, resultHandlers: List> = emptyList(), - navigationMapper: (A) -> Route -) : RouteNavigator(navigationMapper) { + navigationMapper: @Composable (A) -> ComposableNavSpec +) : ComposableNavigator(navigationMapper) { private val coroutineScopeState = MutableStateFlow(null) override val routeController = BottomSheetSheetContentRouteController(bottomSheetNavigator, coroutineScopeState) - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> coroutineScopeState.tryEmit(rememberCoroutineScope()) routeController.sheetContentRouteController.AddResultHandlers(viewModel = this, resultHandlers = resultHandlers) content() diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt index 30222f5eb..c9f985f70 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt @@ -17,7 +17,6 @@ package com.splendo.kaluga.architecture.compose.navigation -import androidx.activity.result.ActivityResultLauncher import com.splendo.kaluga.architecture.navigation.NavigationAction import com.splendo.kaluga.architecture.navigation.NavigationBundle import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec @@ -168,7 +167,7 @@ private val NavigationBundleValue<*>.routeArgument: String? /** * Route for navigating within a [RouteController]. */ -sealed class Route { +sealed class Route : ComposableNavSpec() { companion object { val Back = Back() @@ -259,19 +258,6 @@ sealed class Route { * Closes all screens in the navigation stack */ object Close : Route() - - /** - * Navigates using an [ActivityResultLauncher] and a valid [input]. - * @param activityResultLauncher The launcher to launch with. This should have been created using [androidx.activity.compose.rememberLauncherForActivityResult] - * @param input The input to be provided to [activityResultLauncher] - */ - data class Launcher(val activityResultLauncher: ActivityResultLauncher, val input: I) : Route() { - - /** - * Launches the [activityResultLauncher] with [input] - */ - fun launch() = activityResultLauncher.launch(input) - } } /** @@ -284,7 +270,7 @@ val , Action : NavigationAction> A /** * Creates a [Route.FromRoute] from [Action] - * @param from The string route to navigate from + * @param route The string route to navigate from */ fun , Action : NavigationAction> Action.from(route: String) = Route.FromRoute(this, route) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt index 56ae84cb0..5b131bb9f 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt @@ -97,7 +97,6 @@ sealed class ProvidingNavHostRouteController( navHostController.popBackStack(ROOT_VIEW, false) } is Route.Close -> close() - is Route.Launcher<*> -> newRoute.launch() } } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt index 7d20f6d68..18bcd6fde 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt @@ -25,12 +25,14 @@ import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import kotlinx.serialization.KSerializer +import kotlin.reflect.KClass +import kotlin.reflect.safeCast /** * A Handler that will allow a [NavHostController] to handle Result of a given type [R] to be received by a given [ViewModel] */ sealed class NavHostResultHandler { - abstract val viewModelClass: Class + abstract val viewModelClass: KClass abstract val retain: Boolean abstract val onResult: ViewModel.(R) -> Unit @Composable @@ -40,7 +42,7 @@ sealed class NavHostResultHandler { * [NavHostResultHandler] that handles a result of a [NavigationBundle] */ data class Bundle>( - override val viewModelClass: Class, + override val viewModelClass: KClass, val spec: NavigationBundleSpec, override val retain: Boolean = false, override val onResult: ViewModel.(NavigationBundle) -> Unit @@ -53,7 +55,7 @@ sealed class NavHostResultHandler { * [NavHostResultHandler] that handles a result of a [NavigationBundleSpecType] */ data class Type( - override val viewModelClass: Class, + override val viewModelClass: KClass, val spec: NavigationBundleSpecType, override val retain: Boolean, override val onResult: ViewModel.(R) -> Unit @@ -65,7 +67,7 @@ sealed class NavHostResultHandler { @Composable internal fun HandleResult(viewModel: BaseLifecycleViewModel, navHostController: NavHostController) { - viewModelClass.cast(viewModel)?.let { vm -> + viewModelClass.safeCast(viewModel)?.let { vm -> navHostController.HandleResult { vm.onResult(it) } } } @@ -77,7 +79,7 @@ sealed class NavHostResultHandler { inline fun > NavigationBundleSpec.NavHostResultHandler( retain: Boolean = false, noinline onResult: ViewModel.(NavigationBundle) -> Unit -) = NavHostResultHandler.Bundle(ViewModel::class.java, this, retain, onResult) +) = NavHostResultHandler.Bundle(ViewModel::class, this, retain, onResult) /** * Creates a [NavHostResultHandler.Type] of [R] for this [NavigationBundleSpecType] @@ -85,7 +87,7 @@ inline fun NavigationBundleSpecType.NavHostResultHandler( retain: Boolean = false, noinline onResult: ViewModel.(R) -> Unit -) = NavHostResultHandler.Type(ViewModel::class.java, this, retain, onResult) +) = NavHostResultHandler.Type(ViewModel::class, this, retain, onResult) /** * Creates a [NavHostResultHandler.Type] of [R] for this [KSerializer] @@ -93,4 +95,4 @@ inline fun NavigationBundleSpecT inline fun KSerializer.NavHostResultHandler( retain: Boolean = false, noinline onResult: ViewModel.(R) -> Unit -) = NavHostResultHandler.Type(ViewModel::class.java, NavigationBundleSpecType.SerializedType(this), retain, onResult) +) = NavHostResultHandler.Type(ViewModel::class, NavigationBundleSpecType.SerializedType(this), retain, onResult) diff --git a/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt b/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt index 634062f68..67bf7dbbf 100644 --- a/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt +++ b/architecture/src/androidLibMain/kotlin/navigation/NavigationSpec.kt @@ -273,7 +273,44 @@ sealed class NavigationSpec { val subject: String? = null, val body: String? = null, val attachments: List = emptyList() - ) + ) { + val intent get() = when (attachments.size) { + 0 -> Intent(Intent.ACTION_SEND) + 1 -> Intent(Intent.ACTION_SEND).apply { + putExtra( + Intent.EXTRA_STREAM, + attachments[0] + ) + } + else -> Intent(Intent.ACTION_SEND_MULTIPLE).apply { + putExtra( + Intent.EXTRA_STREAM, + ArrayList( + attachments + ) + ) + } + }.apply { + setDataAndType( + Uri.parse("mailto:"), + when (this@EmailSettings.type) { + is Type.Plain -> "text/plain" + is Type.Stylized -> "*/*" + } + ) + if (to.isNotEmpty()) { + putExtra(Intent.EXTRA_EMAIL, to.toTypedArray()) + } + if (cc.isNotEmpty()) { + putExtra(Intent.EXTRA_CC, cc.toTypedArray()) + } + if (bcc.isNotEmpty()) { + putExtra(Intent.EXTRA_BCC, bcc.toTypedArray()) + } + subject?.let { putExtra(Intent.EXTRA_SUBJECT, it) } + body?.let { putExtra(Intent.EXTRA_TEXT, it) } + } + } } /** @@ -320,21 +357,68 @@ sealed class NavigationSpec { */ data class Settings(val type: Type) : NavigationSpec() { sealed class Type { - object General : Type() - object Wireless : Type() - object AirplaneMode : Type() - object Wifi : Type() - object Apn : Type() - object Bluetooth : Type() - object Date : Type() - object Locale : Type() - object InputMethod : Type() - object Display : Type() - object Security : Type() - object LocationSource : Type() - object InternalStorage : Type() - object MemoryCard : Type() - object AppDetails : Type() + + abstract fun intent(activity: android.app.Activity): Intent + + object General : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_SETTINGS) + } + object Wireless : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS) + } + object AirplaneMode : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_AIRPLANE_MODE_SETTINGS) + } + object Wifi : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_WIFI_SETTINGS) + } + + object Apn : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_APN_SETTINGS) + } + + object Bluetooth : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS) + } + + object Date : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_DATE_SETTINGS) + } + + object Locale : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_LOCALE_SETTINGS) + } + + object InputMethod : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_INPUT_METHOD_SETTINGS) + } + + object Display : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_DISPLAY_SETTINGS) + } + + object Security : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS) + } + + object LocationSource : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS) + } + + object InternalStorage : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_INTERNAL_STORAGE_SETTINGS) + } + + object MemoryCard : Type() { + override fun intent(activity: android.app.Activity): Intent = Intent(android.provider.Settings.ACTION_MEMORY_CARD_SETTINGS) + } + + object AppDetails : Type() { + override fun intent(activity: android.app.Activity): Intent { + val uri = Uri.fromParts("package", activity.packageName, null) + return Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri) + } + } } } @@ -360,7 +444,37 @@ sealed class NavigationSpec { * @param body Optional body of the message * @param attachments List of [Uri] pointing to attachments to add */ - data class TextMessengerSettings(val type: Type = Type.Plain, val recipients: List, val subject: String? = null, val body: String? = null, val attachments: List = emptyList()) + data class TextMessengerSettings(val type: Type = Type.Plain, val recipients: List, val subject: String? = null, val body: String? = null, val attachments: List = emptyList()) { + val intent: Intent = when (attachments.size) { + 0 -> Intent(Intent.ACTION_SEND) + 1 -> Intent(Intent.ACTION_SEND).apply { + putExtra( + Intent.EXTRA_STREAM, + attachments[0] + ) + } + else -> Intent(Intent.ACTION_SEND_MULTIPLE).apply { + putExtra( + Intent.EXTRA_STREAM, + ArrayList( + attachments + ) + ) + } + }.apply { + val recipients = recipients.fold("") { acc, recipient -> if (acc.isNotEmpty()) "$acc;$recipient" else recipient } + setDataAndType( + Uri.parse("smsto:$recipients"), + when (this@TextMessengerSettings.type) { + is Type.Plain -> "text/plain" + is Type.Image -> "image/*" + is Type.Video -> "video/*" + } + ) + subject?.let { putExtra("subject", it) } + body?.let { putExtra("sms_body", it) } + } + } } /** diff --git a/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt b/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt index e91a5f52a..b81aec084 100644 --- a/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt +++ b/architecture/src/androidLibMain/kotlin/navigation/Navigator.kt @@ -21,7 +21,6 @@ import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.provider.MediaStore -import android.provider.Settings import androidx.browser.customtabs.CustomTabsIntent import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager @@ -204,43 +203,7 @@ class ActivityNavigator>(private val navigationMapper: ( private fun navigateToEmail(emailSpec: NavigationSpec.Email) { assert(manager?.activity != null) val activity = manager?.activity ?: return - val settings = emailSpec.emailSettings - val intent = when (settings.attachments.size) { - 0 -> Intent(Intent.ACTION_SEND) - 1 -> Intent(Intent.ACTION_SEND).apply { - putExtra( - Intent.EXTRA_STREAM, - settings.attachments[0] - ) - } - else -> Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra( - Intent.EXTRA_STREAM, - ArrayList( - settings.attachments - ) - ) - } - }.apply { - setDataAndType( - Uri.parse("mailto:"), - when (settings.type) { - is NavigationSpec.Email.Type.Plain -> "text/plain" - is NavigationSpec.Email.Type.Stylized -> "*/*" - } - ) - if (settings.to.isNotEmpty()) { - putExtra(Intent.EXTRA_EMAIL, settings.to.toTypedArray()) - } - if (settings.cc.isNotEmpty()) { - putExtra(Intent.EXTRA_CC, settings.cc.toTypedArray()) - } - if (settings.bcc.isNotEmpty()) { - putExtra(Intent.EXTRA_BCC, settings.bcc.toTypedArray()) - } - settings.subject?.let { putExtra(Intent.EXTRA_SUBJECT, it) } - settings.body?.let { putExtra(Intent.EXTRA_TEXT, it) } - } + val intent = emailSpec.emailSettings.intent intent.resolveActivity(activity.packageManager)?.let { activity.startActivity(intent) @@ -281,26 +244,7 @@ class ActivityNavigator>(private val navigationMapper: ( private fun navigateToSettings(settingsSpec: NavigationSpec.Settings) { assert(manager?.activity != null) val activity = manager?.activity ?: return - val intent = when (settingsSpec.type) { - is NavigationSpec.Settings.Type.General -> Intent(Settings.ACTION_SETTINGS) - is NavigationSpec.Settings.Type.Wireless -> Intent(Settings.ACTION_WIRELESS_SETTINGS) - is NavigationSpec.Settings.Type.AirplaneMode -> Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS) - is NavigationSpec.Settings.Type.Wifi -> Intent(Settings.ACTION_WIFI_SETTINGS) - is NavigationSpec.Settings.Type.Apn -> Intent(Settings.ACTION_APN_SETTINGS) - is NavigationSpec.Settings.Type.Bluetooth -> Intent(Settings.ACTION_BLUETOOTH_SETTINGS) - is NavigationSpec.Settings.Type.Date -> Intent(Settings.ACTION_DATE_SETTINGS) - is NavigationSpec.Settings.Type.Locale -> Intent(Settings.ACTION_LOCALE_SETTINGS) - is NavigationSpec.Settings.Type.InputMethod -> Intent(Settings.ACTION_INPUT_METHOD_SETTINGS) - is NavigationSpec.Settings.Type.Display -> Intent(Settings.ACTION_DISPLAY_SETTINGS) - is NavigationSpec.Settings.Type.Security -> Intent(Settings.ACTION_SECURITY_SETTINGS) - is NavigationSpec.Settings.Type.LocationSource -> Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) - is NavigationSpec.Settings.Type.InternalStorage -> Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS) - is NavigationSpec.Settings.Type.MemoryCard -> Intent(Settings.ACTION_MEMORY_CARD_SETTINGS) - is NavigationSpec.Settings.Type.AppDetails -> { - val uri = Uri.fromParts("package", activity.packageName, null) - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri) - } - } + val intent = settingsSpec.type.intent(activity) intent.resolveActivity(activity.packageManager)?.let { activity.startActivity(intent) @@ -310,36 +254,7 @@ class ActivityNavigator>(private val navigationMapper: ( private fun navigateToMessenger(messengerSpec: NavigationSpec.TextMessenger) { assert(manager?.activity != null) val activity = manager?.activity ?: return - val settings = messengerSpec.settings - val intent = when (settings.attachments.size) { - 0 -> Intent(Intent.ACTION_SEND) - 1 -> Intent(Intent.ACTION_SEND).apply { - putExtra( - Intent.EXTRA_STREAM, - settings.attachments[0] - ) - } - else -> Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra( - Intent.EXTRA_STREAM, - ArrayList( - settings.attachments - ) - ) - } - }.apply { - val recipients = settings.recipients.fold("") { acc, recipient -> if (acc.isNotEmpty()) "$acc;$recipient" else recipient } - setDataAndType( - Uri.parse("smsto:$recipients"), - when (settings.type) { - is NavigationSpec.TextMessenger.Type.Plain -> "text/plain" - is NavigationSpec.TextMessenger.Type.Image -> "image/*" - is NavigationSpec.TextMessenger.Type.Video -> "video/*" - } - ) - settings.subject?.let { putExtra("subject", it) } - settings.body?.let { putExtra("sms_body", it) } - } + val intent = messengerSpec.settings.intent intent.resolveActivity(activity.packageManager)?.let { activity.startActivity(intent) From 2bee0d6df001961e6a7751a02329dbc7547f9d72 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Tue, 29 Nov 2022 19:52:42 +0100 Subject: [PATCH 040/227] Fix example --- .../architecture/compose/ArchitectureDetailsLayout.kt | 7 ++++--- .../example/architecture/compose/ArchitectureLayout.kt | 4 ++-- .../example/architecture/compose/BottomSheetLayout.kt | 5 +++-- .../architecture/compose/BottomSheetSubPageLayout.kt | 5 +++-- .../kaluga/example/resources/compose/ResourcesLayout.kt | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt index 68771cece..08db0afed 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt @@ -23,11 +23,12 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import com.splendo.kaluga.architecture.compose.navigation.BottomSheetContentNavHostRouteNavigator +import com.splendo.kaluga.architecture.compose.navigation.BottomSheetContentNavHostComposableNavigator import com.splendo.kaluga.architecture.compose.navigation.BottomSheetNavigatorState import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation import com.splendo.kaluga.architecture.compose.state import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsNavigationAction import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails import com.splendo.kaluga.resources.compose.Composable @@ -40,9 +41,9 @@ fun ArchitectureDetailsLayout(inputDetails: InputDetails, bottomSheetNavigatorSt val viewModel = koinViewModel { parametersOf( inputDetails, - BottomSheetContentNavHostRouteNavigator( + BottomSheetContentNavHostComposableNavigator>( bottomSheetNavigatorState, - navigationMapper = ::architectureDetailsNavigationRouteMapper + navigationMapper = { architectureDetailsNavigationRouteMapper(it) } ) ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt index 8e0113a22..4d9723070 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt @@ -84,8 +84,8 @@ fun ArchitectureLayout() { MdcTheme { val viewModel = koinViewModel { parametersOf( - RootModalBottomSheetNavigator( - navigationMapper = ::architectureNavigationRouteMapper, + RootModalBottomSheetNavigator>( + navigationMapper = { architectureNavigationRouteMapper(it) }, contentRootResultHandlers = listOf( InputDetails.serializer().NavHostResultHandler { nameInput.post(it.name) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index ef48b3cca..c42a353fd 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt @@ -28,6 +28,7 @@ import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavi import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetViewModel import com.splendo.kaluga.resources.compose.Composable import kotlinx.coroutines.flow.StateFlow @@ -38,9 +39,9 @@ import org.koin.core.parameter.parametersOf fun BottomSheetLayout(bottomSheetNavigationState: StateFlow) { val viewModel = koinViewModel { parametersOf( - ModalBottomSheetNavigator( + ModalBottomSheetNavigator( bottomSheetNavigationState, - navigationMapper = ::bottomSheetNavigationRouteMapper + navigationMapper = { bottomSheetNavigationRouteMapper(it) } ) ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt index e40808ed2..6eee11da9 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt @@ -31,6 +31,7 @@ import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavi import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageNavigation import com.splendo.kaluga.example.shared.viewmodel.architecture.BottomSheetSubPageViewModel import kotlinx.coroutines.flow.StateFlow import org.koin.androidx.compose.koinViewModel @@ -42,9 +43,9 @@ fun BottomSheetSubPageLayout( ) { val viewModel = koinViewModel { parametersOf( - ModalBottomSheetNavigator( + ModalBottomSheetNavigator( bottomSheetNavigationState, - navigationMapper = ::bottomSheetSubPageNavigationRouteMapper + navigationMapper = { bottomSheetSubPageNavigationRouteMapper(it) } ) ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt index 07d80b6a4..a46d6f9cc 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt @@ -35,7 +35,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.navigation.compose.composable import com.google.android.material.composethemeadapter.MdcTheme -import com.splendo.kaluga.architecture.compose.navigation.RootNavHostRouteNavigator +import com.splendo.kaluga.architecture.compose.navigation.RootNavHostComposableNavigator import com.splendo.kaluga.architecture.compose.navigation.next import com.splendo.kaluga.architecture.compose.navigation.route import com.splendo.kaluga.architecture.compose.state @@ -67,7 +67,7 @@ fun ResourcesLayout() { MdcTheme { val viewModel = koinViewModel { parametersOf( - RootNavHostRouteNavigator( + RootNavHostComposableNavigator( navigationMapper = { action -> when (action) { is ResourcesListNavigationAction.Button -> action.next From 39af7542e1c381d9d7569ca0776cb62fbe51c277 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 30 Nov 2022 00:07:38 +0100 Subject: [PATCH 041/227] Added support for non-NavHost navigation on Compose --- .../navigation/BottomSheetNavigator.kt | 13 ++-- .../compose/navigation/ComposableNavSpec.kt | 67 ++++++++++++------- .../compose/navigation/ComposableNavigator.kt | 10 ++- .../example/alerts/compose/AlertsLayout.kt | 2 +- .../example/alerts/xml/XMLAlertsActivity.kt | 2 +- .../architecture/ArchitectureActivity.kt | 6 +- .../compose/ArchitectureDetailsLayout.kt | 2 +- .../compose/ArchitectureLayout.kt | 2 +- .../architecture/compose/BottomSheetLayout.kt | 8 ++- .../xml/BottomSheetRootDialogFragment.kt | 1 - .../xml/XMLArchitectureActivity.kt | 6 +- .../example/compose/ComposeOrXMLActivity.kt | 2 +- .../compose/DateTimePickerLayout.kt | 2 +- .../xml/XMLDateTimePickerActivity.kt | 2 +- .../keyboard/compose/KeyboardLayout.kt | 2 +- .../keyboard/xml/XMLKeyboardActivity.kt | 2 +- .../kaluga/example/link/LinksActivity.kt | 1 - .../example/loading/compose/LoadingLayout.kt | 2 +- .../example/loading/xml/XMLLoadingActivity.kt | 2 +- .../example/permissions/PermissionActivity.kt | 1 - .../example/resources/compose/ColorsLayout.kt | 45 ++++++++----- .../resources/compose/ResourcesLayout.kt | 5 +- .../architecture/ArchitectureViewModel.kt | 1 - .../datetimepicker/DateTimePickerViewModel.kt | 1 - 24 files changed, 108 insertions(+), 79 deletions(-) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt index d152d26a6..31e05c28c 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt @@ -1,12 +1,12 @@ package com.splendo.kaluga.architecture.compose.navigation -import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope @@ -48,15 +48,17 @@ sealed class BottomSheetNavigator>( @Composable protected fun BaseLifecycleViewModel.SetupNavigationAction() { val currentAction by actionState.collectAsState() + val onDispose: () -> Unit = { actionState.compareAndSet(currentAction, null) } currentAction?.let { action -> when (val spec = navigationMapper(action)) { - is BottomSheetRoute -> routeController.navigate(spec) - is BottomSheetLaunchedNavigation -> spec.launchedNavigation.Launch(this) + is BottomSheetRoute -> LaunchedEffect(spec) { + routeController.navigate(spec) + onDispose() + } + is BottomSheetLaunchedNavigation -> spec.launchedNavigation.Launch(this, onDispose) } - actionState.tryEmit(null) } } - } /** @@ -160,7 +162,6 @@ private fun SetupNavigatingModalBottomSheetLayout( HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) } Box(Modifier.defaultMinSize(minHeight = 1.dp)) { - Log.d("TEST", "SetupNavigatingModalBottomSheetLayout ${navigationState.sheetContentNavHostController}") SetupNavHost( bottomSheetNavigatorState = bottomSheetNavigationState, navHostController = { sheetContentNavHostController }, diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt index bb0e2be3d..e224dd173 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt @@ -20,7 +20,6 @@ package com.splendo.kaluga.architecture.compose.navigation import android.app.Activity import android.content.Intent import android.net.Uri -import android.os.Bundle import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.PickVisualMediaRequest @@ -28,8 +27,7 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContracts import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember +import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.platform.LocalContext import androidx.fragment.app.DialogFragment import com.splendo.kaluga.architecture.compose.activity @@ -86,8 +84,10 @@ sealed class ComposableNavSpec { * @param type The [ActivityResultContracts.PickVisualMedia.VisualMediaType] to be selected. * @param onResult Returns an [Uri] of the image or video selected, or `null` if nothing was selected. */ - inline fun PickVisualMedia(type: ActivityResultContracts.PickVisualMedia.VisualMediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo, - noinline onResult: VM.(Uri?) -> Unit) = Launcher( + inline fun PickVisualMedia( + type: ActivityResultContracts.PickVisualMedia.VisualMediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo, + noinline onResult: VM.(Uri?) -> Unit + ) = Launcher( VM::class, ActivityResultContracts.PickVisualMedia(), { PickVisualMediaRequest(mediaType = type) }, @@ -99,8 +99,11 @@ sealed class ComposableNavSpec { * @param type The [ActivityResultContracts.PickVisualMedia.VisualMediaType] to be selected. * @param onResult Returns a list of [Uri] of the images or videos selected. */ - inline fun PickMultipleVisualMedia(maxItems: Int? = null, type: ActivityResultContracts.PickVisualMedia.VisualMediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo, - noinline onResult: VM.(List) -> Unit) = Launcher( + inline fun PickMultipleVisualMedia( + maxItems: Int? = null, + type: ActivityResultContracts.PickVisualMedia.VisualMediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo, + noinline onResult: VM.(List) -> Unit + ) = Launcher( VM::class, maxItems?.let { ActivityResultContracts.PickMultipleVisualMedia(it) } ?: ActivityResultContracts.PickMultipleVisualMedia(), { PickVisualMediaRequest(mediaType = type) }, @@ -201,7 +204,7 @@ sealed class ComposableNavSpec { is NavigationSpec.Phone.Type.Dial -> Intent(Intent.ACTION_DIAL) is NavigationSpec.Phone.Type.Call -> Intent(Intent.ACTION_CALL) }.apply { - data = Uri.parse("tel:${phoneNumber}") + data = Uri.parse("tel:$phoneNumber") } if (intent.resolveActivity(packageManager) != null) intent else null } @@ -252,16 +255,20 @@ sealed class ComposableNavSpec { */ sealed class LaunchedNavigation : ComposableNavSpec() { @Composable - protected abstract fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit + protected abstract fun createLauncher(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit): () -> Unit /** * Launch this [LaunchedNavigation] for a given [viewModel]. */ @Composable - fun Launch(viewModel: BaseLifecycleViewModel) { - val launcher = createLauncher(viewModel) - LaunchedEffect(launcher) { + fun Launch(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit) { + val launcher = createLauncher(viewModel, onDispose) + // Ensure only launched once + DisposableEffect(viewModel) { launcher.invoke() + onDispose { + onDispose() + } } } } @@ -281,10 +288,11 @@ sealed class ComposableNavSpec { ) : LaunchedNavigation() { @Composable - override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { + override fun createLauncher(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit): () -> Unit { val launcher = rememberLauncherForActivityResult(activityResultContract) { result -> viewModelClass.safeCast(viewModel)?.let { it.onResult(result) + onDispose() } } val input = getInput() @@ -302,8 +310,8 @@ sealed class ComposableNavSpec { */ data class CloseActivity(val result: Pair?>? = null) : LaunchedNavigation() { @Composable - override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { - val activity = LocalContext.current.activity ?: return {} + override fun createLauncher(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit): () -> Unit { + val activity = LocalContext.current.activity return { result?.let { (resultCode, bundle) -> val data = Intent().apply { @@ -311,9 +319,10 @@ sealed class ComposableNavSpec { putExtras(it.toBundle()) } } - activity.setResult(resultCode, data) + activity?.setResult(resultCode, data) } - activity.finish() + activity?.finish() + onDispose() } } } @@ -329,10 +338,13 @@ sealed class ComposableNavSpec { ) : LaunchedNavigation() { @Composable - override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { - val fragmentManager = LocalContext.current.activity?.supportFragmentManager ?: return {} + override fun createLauncher(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit): () -> Unit { + val fragmentManager = LocalContext.current.activity?.supportFragmentManager return { - createDialog().show(fragmentManager, tag) + fragmentManager?.let { + createDialog().show(it, tag) + } + onDispose() } } } @@ -344,11 +356,14 @@ sealed class ComposableNavSpec { data class IntentLauncher(val createIntent: @Composable Activity.() -> Intent?) : LaunchedNavigation() { @Composable - override fun createLauncher(viewModel: BaseLifecycleViewModel): () -> Unit { + override fun createLauncher(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit): () -> Unit { val context = LocalContext.current - val intent = context.activity?.intent ?: return {} + val intent = context.activity?.intent return { - context.startActivity(intent) + intent?.let { + context.startActivity(it) + } + onDispose() } } } @@ -374,7 +389,7 @@ inline fun Naviga activityResultContract: ActivityResultContract, flags: Set = emptySet(), noinline onResult: VM.(O) -> Unit -) { +): ComposableNavSpec.Launcher { val getInput = @Composable { LocalContext.current.activity?.let { activity -> Intent(activity, A::class.java).apply { @@ -385,14 +400,14 @@ inline fun Naviga } } } - ComposableNavSpec.Launcher(VM::class, activityResultContract, getInput, onResult) + return ComposableNavSpec.Launcher(VM::class, activityResultContract, getInput, onResult) } /** * Creates a [ComposableNavSpec.IntentLauncher] to show a given [Activity] * @param flags A set of [IntentFlag] to be applied to the next screen. */ -inline fun NavigationAction<*>.ShowActivity( +inline fun NavigationAction<*>.ShowActivity( flags: Set = emptySet() ) = ComposableNavSpec.IntentLauncher { Intent(this, A::class.java).apply { diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt index 33613cc13..6f6776607 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt @@ -1,6 +1,7 @@ package com.splendo.kaluga.architecture.compose.navigation import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope @@ -48,12 +49,15 @@ sealed class ComposableNavigator>( @Composable protected fun BaseLifecycleViewModel.SetupNavigationAction() { val currentAction by actionState.collectAsState() + val onDispose: () -> Unit = { actionState.compareAndSet(currentAction, null) } currentAction?.let { action -> when (val spec = navigationMapper(action)) { - is Route -> routeController.navigate(spec) - is ComposableNavSpec.LaunchedNavigation -> spec.Launch(this) + is Route -> LaunchedEffect(spec) { + routeController.navigate(spec) + onDispose() + } + is ComposableNavSpec.LaunchedNavigation -> spec.Launch(this, onDispose) } - actionState.tryEmit(null) } } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt index d4c536a79..2b66b51f4 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.kt @@ -70,4 +70,4 @@ fun AlertsLayout() { } } } -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt index b5d877ed5..d4063c934 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt @@ -21,4 +21,4 @@ class XMLAlertsActivity : KalugaViewModelActivity() { binding.lifecycleOwner = this setContentView(binding.root) } -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt index dcaef43b6..a1832ff92 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt @@ -22,9 +22,9 @@ import com.splendo.kaluga.example.architecture.xml.XMLArchitectureActivity import com.splendo.kaluga.example.compose.ComposeOrXMLActivity class ArchitectureActivity : ComposeOrXMLActivity< - ComposeArchitectureActivity, - XMLArchitectureActivity - >( + ComposeArchitectureActivity, + XMLArchitectureActivity + >( ComposeArchitectureActivity::class.java, XMLArchitectureActivity::class.java ) diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt index 08db0afed..59ece7342 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt @@ -59,4 +59,4 @@ fun ArchitectureDetailsLayout(inputDetails: InputDetails, bottomSheetNavigatorSt finishButton.Composable(modifier = Modifier.fillMaxWidth()) } } -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt index 4d9723070..d106a51bf 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt @@ -166,4 +166,4 @@ fun ArchitectureLayout() { } } } -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index c42a353fd..b597575f4 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt @@ -54,9 +54,11 @@ fun BottomSheetLayout(bottomSheetNavigationState: StateFlow() { - inner class Contract : ActivityResultContract() { + class Contract : ActivityResultContract() { override fun createIntent(context: Context, input: Intent): Intent = input override fun parseResult( resultCode: Int, intent: Intent? - ): InputDetails? = intent?.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(InputDetails.serializer())) + ): InputDetails? { + return intent?.extras?.toTypedProperty(NavigationBundleSpecType.SerializedType(InputDetails.serializer())) + } } override val viewModel: ArchitectureViewModel by viewModel { diff --git a/example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt index 91ab8e40a..498a9baee 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/compose/ComposeOrXMLActivity.kt @@ -98,4 +98,4 @@ class UITypesAdapter(private val viewModel: ComposeOrXMLSelectionViewModel) : Re holder.button.setOnClickListener(null) } } -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt index 09307a1b3..c46df491b 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt @@ -79,4 +79,4 @@ fun DateTimePickerLayout() { } } } -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt index 8f64071ae..7eacb2047 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/xml/XMLDateTimePickerActivity.kt @@ -35,4 +35,4 @@ class XMLDateTimePickerActivity : KalugaViewModelActivity() { binding.lifecycleOwner = this setContentView(binding.root) } -} \ No newline at end of file +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt index f822454de..19143a8e0 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt @@ -23,7 +23,6 @@ import android.widget.Toast import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType import com.splendo.kaluga.architecture.navigation.toTypedProperty import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity -import com.splendo.kaluga.example.R import com.splendo.kaluga.example.databinding.ActivityPermissionBinding import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt index 00eef8809..efa799999 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt @@ -1,18 +1,18 @@ /* Copyright 2022 Splendo Consulting B.V. The Netherlands - + 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 com.splendo.kaluga.example.resources.compose @@ -54,7 +54,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun ColorsLayout() { val viewModel = koinViewModel() - + ViewModelComposable(viewModel) { Column( verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), @@ -82,9 +82,11 @@ fun ColorsLayout() { Row(Modifier.fillMaxWidth()) { Column(Modifier.width(mainColorSize)) { var backdropTempText by remember { mutableStateOf(backdropText) } - Box(modifier = Modifier - .size(mainColorSize) - .backgroundStyle(backdropColorBackground)) + Box( + modifier = Modifier + .size(mainColorSize) + .backgroundStyle(backdropColorBackground) + ) OutlinedTextField( value = backdropTempText, onValueChange = { backdropTempText = it }, @@ -98,15 +100,19 @@ fun ColorsLayout() { ) } Spacer(modifier = Modifier.weight(1.0f)) - Box(modifier = Modifier - .size(mainColorSize) - .backgroundStyle(blendedColorBackground)) + Box( + modifier = Modifier + .size(mainColorSize) + .backgroundStyle(blendedColorBackground) + ) Spacer(modifier = Modifier.weight(1.0f)) Column(Modifier.width(mainColorSize)) { var sourceTempText by remember { mutableStateOf(sourceText) } - Box(modifier = Modifier - .size(mainColorSize) - .backgroundStyle(sourceColorBackground)) + Box( + modifier = Modifier + .size(mainColorSize) + .backgroundStyle(sourceColorBackground) + ) OutlinedTextField( value = sourceTempText, onValueChange = { sourceTempText = it }, @@ -139,11 +145,14 @@ fun ListOfColors(list: List) { Row( Modifier .fillMaxWidth() - .horizontalScroll(rememberScrollState())) { + .horizontalScroll(rememberScrollState()) + ) { list.forEach { - Box(modifier = Modifier - .size(40.dp) - .backgroundStyle(it)) + Box( + modifier = Modifier + .size(40.dp) + .backgroundStyle(it) + ) } } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt index a46d6f9cc..5da2a0cf0 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.kt @@ -94,11 +94,12 @@ fun ResourcesLayout() { resources.forEach { Button( modifier = Modifier.fillMaxWidth(), - onClick = { onResourceSelected(it) }) { + onClick = { onResourceSelected(it) } + ) { Text(it.title) } } } } } -} \ No newline at end of file +} diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt index 53a89bc80..8a0e2755f 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt @@ -23,7 +23,6 @@ import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.ObservableOptional import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.observable.toInitializedSubject -import com.splendo.kaluga.architecture.observable.toUninitializedObservable import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel import com.splendo.kaluga.example.shared.stylable.ButtonStyles import com.splendo.kaluga.resources.localized diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt index 8bfd03c1f..7aa6d0265 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetimepicker/DateTimePickerViewModel.kt @@ -1,7 +1,6 @@ package com.splendo.kaluga.example.shared.viewmodel.datetimepicker import com.splendo.kaluga.architecture.observable.toInitializedObservable -import com.splendo.kaluga.architecture.observable.toUninitializedObservable import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import com.splendo.kaluga.base.text.DateFormatStyle import com.splendo.kaluga.base.text.KalugaDateFormatter From 77b3e101cc78f94d875851b0650b0f08fb52686a Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 30 Nov 2022 13:03:23 +0100 Subject: [PATCH 042/227] More documentation + Some convenience methods --- architecture-compose/README.md | 297 ++---------------- .../navigation/BottomSheetNavigator.kt | 30 +- .../compose/navigation/ComposableNavSpec.kt | 7 +- .../compose/navigation/ComposableNavigator.kt | 43 ++- .../architecture/compose/navigation/Route.kt | 11 +- .../compose/navigation/RouteController.kt | 5 + .../compose/navigation/result/HandleResult.kt | 2 + .../navigation/result/NavHostResultHandler.kt | 2 +- .../KalugaViewModelComposeActivity.kt | 32 +- .../compose/viewModel/ViewModelComposable.kt | 25 +- .../architecture/compose/NavigationMappers.kt | 5 +- 11 files changed, 164 insertions(+), 295 deletions(-) diff --git a/architecture-compose/README.md b/architecture-compose/README.md index 6aa131ba8..1a86c1f00 100644 --- a/architecture-compose/README.md +++ b/architecture-compose/README.md @@ -12,284 +12,49 @@ repositories { // ... dependencies { // ... - implementation("com.splendo.kaluga.architecture.compose:$kalugaVersion") + implementation("com.splendo.kaluga.architecture-compose:$kalugaVersion") } ``` ## Usage -### Examples -#### Display content using a local data model +Compose architecture can be easily used by any Composable function. +To do so, simply create a `BaseLifecycleViewModel`, preferably using a composable method that retains the viewModel on configuration changes such as [`androidx.lifecycle.viewmodel.compose.viewModel()`](https://developer.android.com/reference/kotlin/androidx/lifecycle/viewmodel/compose/package-summary#viewModel(androidx.lifecycle.ViewModelStoreOwner,kotlin.String,androidx.lifecycle.ViewModelProvider.Factory,androidx.lifecycle.viewmodel.CreationExtras)) or [`org.koin.androidx.compose.koinViewModel`](https://insert-koin.io/docs/reference/koin-android/compose#viewmodel-for-composable) +Then wrap the ViewModel using the `ViewModelComposable` wrapping function. -```kotlin -// View model that expose contact details fields and handle user actions -class ContactDetailsViewModel( - //... -) { - val contactEmail: String // ... - - fun sendEmail() { - // send an email - } - - fun onBackPressed() { - // handle the back button - } -} +`ViewModelComposable` will automatically bind to all `LifecycleSubscribables` publicly exposed by the ViewModel using the activity that hosts the Composable. +`FragmentViewModelComposable` can be called instead to provide an additional `FragmentManager` to bind to. -// Contact details UI -@Composable -fun ContactDetailsLayout( - contactDetails: ContactDetails, - navigator: Navigator> -) { - // Create a view model and store it in a local ViewModelStore to ensure cleanup - val viewModel = store { - remember { - ContactDetailsViewModel(contactDetails, navigator) - } - } +A `KalugaViewModelComposeActivity` can be declared to automatically create a `BaseLifecycleViewModel` and wrap it in `ViewModelComposable` as the Content of the activity. - ViewModelComposable(viewModel) { - // Add a hardware back button handler - HardwareBackButtonNavigation(::onBackPressed) - - Column { - // Use the view model fields to render content - Text(text = contactEmail) - - // Use the view model methods to handle user actions - Button(onClick = ::sendEmail) { - // ... - } - } - } -} -``` +### ComposableLifecycleSubscribable +This Kaluga library adds a new `LifecycleSubscribableMarker`: `ComposableLifecycleSubscribable`. +The `ComposableLifecycleSubscribable` provides a modifying function to wrap around a composable view. +`ViewModelComposable` will automatically modify its content with all `ComposableLifecycleSubscribable` publicly exposed by the provided `BaseLifecycleViewModel`. -#### Set up NavHost navigation +### Navigation +Navigation in Compose is quite different from regular Android navigation. Therefore `com.splendo.kaluga.architecture.navigation.ActivityNavigator` is not recommended as a `Navigator`. +Instead this library introduces several new Navigators. -```kotlin -// Define an action mapper for a route navigator -fun listRouteMapper(action: ContactsListNavigation<*>): String = - when (action) { - is ContactsListNavigation.ShowContactDetails -> action.next - //... - } +#### ComposableNavigator +The `ComposableNavigator` navigates by mapping the `com.splendo.kaluga.architecture.navigation.NavigationAction` to a `ComposableNavSpec`. +A `ComposableNavSpec` can either be a `Route` that is managed by a `NavHost` or a `ComposableNavSpec.LaunchedNavigation` that launches into a new screen. +For `Route` convenience methods exist as extensions of the NavigationAction to easily declare them (e.g. `next`, `from()`, `back`, etc). -fun detailRouteMapper(action: ContactDetailsNavigation<*>): Route { - return when (action) { - is ContactDetailsNavigation.Close -> Route.Back - } -} +When `Route` is included in the Navigator a `NavHost` needs to be set up to support the Routes. +This can be done by declaring `RootNavHostComposableNavigator` as the `ComposableNavigator` of the top view in the navigation graph. +Using `com.splendo.kaluga.architecture.compose.navigation.composable` can easily add `com.splendo.kaluga.architecture.navigation.NavigationAction` to the navigation graph. +The route string of a `NavigationAction` can be generated using `com.splendo.kaluga.architecture.compose.navigation.route()`. -// Root view of the Activity. Contains a navigation graph with destinations -@Composable -fun ContactsLayout() { - val routeController = NavHostRouteController(rememberNavHostController()) - // set up nav host with routes - routeController.SetupNavHost( - rootView = { navHostController -> - // Display a contact list - ContactsListLayout(navHostController) - } - ) { navHostController -> - composable( - type = NavigationBundleSpecType.SerializedType( - ContactDetails.serializer() - ) - ) { details -> - ContactDetailsLayout(details, navHostController) - } - } -} - -@Composable -fun ContactsListLayout(navHostController: NavHostController) { - // Construct a route navigator - val routeNavigator = RouteNavigator( - rememberNavController(), - ::listRouteMapper - ) - - // Setup Contact List view -} - -@Composable -fun ContactDetailsLayout(contactDetails: ContactDetails, navHostController: NavHostController) { - val routeNavigator = RouteNavigator( - navHostController, - ::detailRouteMapper - ) - - // Setup Details View - // No need to set up NavHost since it is managed by the parent -} -``` - -You can also call `SetupNavHost` directly from a `RouteNavigator`, bearing in mind that the navHostController linked to the Navigator can only be setup once. - -#### Mix NavHost and Kaluga navigation - -Sometimes it's necessary to mix a route navigation within the same activity and navigate to -other activities - -```kotlin -internal fun activityMapper(action: ContactsNavigation<*>): NavigationSpec = - when (action) { - is SendEmail -> NavigationSpec.Email(/* ... */) - //... - } - -@Composable -fun ContactsLayout() { - //... - - // create a mixed route-activity navigator - val combinedNavigator = rememberCombinedNavigator { action: ContactsNavigation<*> -> - when (action) { - // actions which are redirected to `routeNavigator` - is ShowContactsList, is ShowContactDetails, is Close -> routeNavigator - // actions which are redirected to an activity navigator - is SendEmail -> ::activityMapper.toActivityNavigator() - } - } - - routeNavigator.SetupNavHost( - //... - composable(route("{json}")) { - //... - ContactDetailsLayout(details, combinedNavigator) - } - ) -} - -class ContactDetailsViewModel( - private val contactDetails: ContactDetails, - private val navigator: Navigator> -) { - //... - // opens another activity - fun sendEmail() = navigator.navigate(SendEmail(contactDetails.email)) - // navigates NavHost graph - fun onBackPressed() = navigator.navigate(Close) -} -``` +Subviews of the NavHost should use `NavHostComposableNavigator` as a Navigator instead, where `navHostController` is the StateFlow provided by `RootNavHostComposableNavigator.contentBuilder`. -#### Modal Bottom Sheet +If no `Route` navigation is used, it is recommended to use `LaunchedComposableNavigator` since this does not set up the unnecessary NavHost. -A special case is provided for using `ModalBottomSheet` navigation - -````kotlin -internal fun bottomSheetParentNavigationRouteMapper(action: BottomSheetParentNavigation): BottomSheetRoute { - return when (action) { - is BottomSheetParentNavigation.SubPage -> action.next.bottomSheetContent - is BottomSheetParentNavigation.ShowSheet -> action.next.bottomSheetSheetContent - } -} - -internal fun bottomSheetParentSubPageNavigationRouteMapper(action: BottomSheetParentSubPageNavigation): Route { - return when (action) { - is BottomSheetParentSubPageNavigation.Back -> Route.Back - } -} - -internal fun bottomSheetNavigationRouteMapper(action: BottomSheetNavigation): BottomSheetRoute { - return when (action) { - is BottomSheetNavigation.Close -> Route.Close.bottomSheetSheetContent - is BottomSheetNavigation.SubPage -> action.next.bottomSheetSheetContent - } -} - -internal fun bottomSheetSubPageNavigationRouteMapper(action: BottomSheetSubPageNavigation): BottomSheetRoute { - return when (action) { - is BottomSheetSubPageNavigation.Close -> Route.Close.bottomSheetSheetContent - is BottomSheetSubPageNavigation.Back -> Route.Back.bottomSheetSheetContent - } -} - -@Composable -fun BottomSheetParentLayout() { - val bottomSheetRouteController = BottomSheetRouteController( - NavHostRouteController(rememberNavController()), - BottomSheetSheetContentRouteController( - rememberNavController(), - rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), - rememberCoroutineScope() - ) - ) - - bottomSheetRouteController.NavigatingModalBottomSheetLayout( - sheetContent = { contentNavHostController, sheetContentNavHostController, sheetState -> - composable(BottomSheetParentNavigation.ShowSheet.route()) { - BottomSheetLayout(contentNavHostController, sheetContentNavHostController, sheetState) - } - composable(BottomSheetNavigation.SubPage.route()) { - BottomSheetSubPageLayout( - contentNavHostController, sheetContentNavHostController, sheetState - ) - } - }, - contentRoot = { contentNavHostController, sheetContentNavHostController, sheetState -> - BottomSheetParentLayoutContent(contentNavHostController, sheetContentNavHostController, sheetState) - }, - content = { contentNavHostController, _, _ -> - composable(BottomSheetParentNavigation.SubPage.route()) { - BottomSheetParentSubPageLayout(contentNavHostController) - } - } - ) -} - -@Composable -fun BottomSheetParentLayoutContent(contentNavHostController: NavHostController, sheetNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - - val navigator = ModalBottomSheetNavigator( - NavHostRouteController(contentNavHostController), - BottomSheetSheetContentRouteController( - sheetNavHostController, - sheetState, - rememberCoroutineScope() - ), - ::bottomSheetParentNavigationRouteMapper - ) - // Show Content View -} - -@Composable -fun BottomSheetParentSubPageLayout(navHostController: NavHostController) { - val navigator = RouteNavigator( - navHostController, - ::bottomSheetParentSubPageNavigationRouteMapper - ) - - // Show Content Sub View -} - -@Composable -fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetNavigationRouteMapper, - ) - - // Show Bottom Sheet Content -} - -@Composable -fun BottomSheetSubPageLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetSubPageNavigationRouteMapper - ) - - // Show Bottom Sheet Sub Content -} -```` +#### BottomSheetNavigator +In addition to `ComposableNavigator` a `BottomSheetNavigator` can be declared. This navigator hosts a `ModalBottomSheetLayout` that has a NavHost for both its content and sheetContent. +Instead of `ComposableNavSpec` the `BottomSheetNavigator` maps actions to `BottomSheetComposableNavSpec`. +The equivalent of `RootNavHostComposableNavigator` is `RootModalBottomSheetNavigator` and of `NavHostComposableNavigator` is `ModalBottomSheetNavigator`. +In addition the subviews of both content and sheetContent in `RootModalBottomSheetNavigator` can declare a `ComposableNavigator` to only navigate within their own NavHost by using `BottomSheetContentNavHostComposableNavigator` and `BottomSheetSheetContentNavHostComposableNavigator` respectively. +#### Result +When navigating back in a NavHost the bundle of the NavigationAction will be provided to the parent view. To handle these results, provide a (list of) `NavHostResultHandler` to the view that should receive the result. diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt index 31e05c28c..dc8e578ce 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator.kt @@ -39,21 +39,29 @@ sealed class BottomSheetNavigator>( } final override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + // Make sure Navigation actions are launched SetupNavigationAction() + // Actually modify the content contentModifier(content) } - abstract val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit + protected abstract val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit @Composable protected fun BaseLifecycleViewModel.SetupNavigationAction() { val currentAction by actionState.collectAsState() - val onDispose: () -> Unit = { actionState.compareAndSet(currentAction, null) } + // If there is a current action, render it currentAction?.let { action -> + // We should only set the actionState back to null when navigation has completed. + // Otherwise some logic may break, such as with rememberLauncherForActivityResult. + val onDispose: () -> Unit = { actionState.compareAndSet(currentAction, null) } when (val spec = navigationMapper(action)) { - is BottomSheetRoute -> LaunchedEffect(spec) { - routeController.navigate(spec) - onDispose() + is BottomSheetRoute -> { + // Navigate the route controller using a LaunchedEffect to ensure it only fires once. + LaunchedEffect(this, spec) { + routeController.navigate(spec) + onDispose() + } } is BottomSheetLaunchedNavigation -> spec.launchedNavigation.Launch(this, onDispose) } @@ -91,6 +99,7 @@ class RootModalBottomSheetNavigator>( override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> val contentNavController = rememberNavController() + // Update the bottomSheetNavigationState to the current state. bottomSheetNavigationState.tryEmit( BottomSheetNavigatorState( contentNavController, @@ -99,11 +108,14 @@ class RootModalBottomSheetNavigator>( ) ) sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) + + // Show a ModalBottomSheetLayout for the content SetupNavigatingModalBottomSheetLayout( bottomSheetNavigationState, routeController.sheetContentRouteController, sheetContentBuilder, { + // Handle results of the Content Root contentRootResultHandlers.forEach { it.HandleResult(viewModel = this, navHostController = contentNavController) } @@ -140,8 +152,11 @@ class ModalBottomSheetNavigator>( override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> sheetStateCoroutineScope.tryEmit(rememberCoroutineScope()) + // Handle results for both content and sheetContent routeController.contentRouteController.AddResultHandlers(viewModel = this, resultHandlers = contentResultHandlers) routeController.sheetContentRouteController.sheetContentRouteController.AddResultHandlers(viewModel = this, resultHandlers = sheetContentResultHandlers) + + // Render content content() } } @@ -158,10 +173,14 @@ private fun SetupNavigatingModalBottomSheetLayout( currentBottomSheetNavigationState?.let { navigationState -> ModalBottomSheetLayout( sheetContent = { + // Add a default BackButton behaviour that closes the sheet content if (navigationState.sheetState.isVisible) { HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) } + + // Need a small Root Layout to render the NavHost properly Box(Modifier.defaultMinSize(minHeight = 1.dp)) { + // Add NavHost to manage the SheetContent navigation SetupNavHost( bottomSheetNavigatorState = bottomSheetNavigationState, navHostController = { sheetContentNavHostController }, @@ -174,6 +193,7 @@ private fun SetupNavigatingModalBottomSheetLayout( }, sheetState = navigationState.sheetState, ) { + // Add NavHost to manage the Content navigation SetupNavHost( bottomSheetNavigatorState = bottomSheetNavigationState, navHostController = { contentNavHostController }, diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt index e224dd173..6f224ad71 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec.kt @@ -264,7 +264,7 @@ sealed class ComposableNavSpec { fun Launch(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit) { val launcher = createLauncher(viewModel, onDispose) // Ensure only launched once - DisposableEffect(viewModel) { + DisposableEffect(viewModel, this) { launcher.invoke() onDispose { onDispose() @@ -289,12 +289,17 @@ sealed class ComposableNavSpec { @Composable override fun createLauncher(viewModel: BaseLifecycleViewModel, onDispose: () -> Unit): () -> Unit { + // Create a ManagedActivityResultLauncher to hadle the contract. + // This launcher needs to remain in the Compose stack since otherwise onResult will not return. + // Therefore only call onDispose when onResult is received. val launcher = rememberLauncherForActivityResult(activityResultContract) { result -> viewModelClass.safeCast(viewModel)?.let { it.onResult(result) onDispose() } } + + // Get the input val input = getInput() return { input?.let { diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt index 6f6776607..fb6ca09d8 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator.kt @@ -31,7 +31,7 @@ sealed class ComposableNavigator>( /** * [RouteController] managing the [Route] navigation */ - abstract val routeController: RouteController + abstract val routeController: RouteController? private val actionState = MutableStateFlow(null) @@ -40,7 +40,9 @@ sealed class ComposableNavigator>( } final override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + // Make sure Navigation actions are launched SetupNavigationAction() + // Actually modify the content contentModifier(content) } @@ -49,12 +51,18 @@ sealed class ComposableNavigator>( @Composable protected fun BaseLifecycleViewModel.SetupNavigationAction() { val currentAction by actionState.collectAsState() - val onDispose: () -> Unit = { actionState.compareAndSet(currentAction, null) } + // If there is a current action, render it currentAction?.let { action -> + // We should only set the actionState back to null when navigation has completed. + // Otherwise some logic may break, such as with rememberLauncherForActivityResult. + val onDispose: () -> Unit = { actionState.compareAndSet(currentAction, null) } when (val spec = navigationMapper(action)) { - is Route -> LaunchedEffect(spec) { - routeController.navigate(spec) - onDispose() + is Route -> { + // Navigate the route controller using a LaunchedEffect to ensure it only fires once. + LaunchedEffect(this, spec) { + routeController?.navigate(spec) + onDispose() + } } is ComposableNavSpec.LaunchedNavigation -> spec.Launch(this, onDispose) } @@ -81,13 +89,19 @@ class RootNavHostComposableNavigator>( override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> val navController = rememberNavController() + // Update the NavHostController to the current state navHostController.tryEmit(navController) + + // Show a NavHost displaying the content SetupNavHost( navHostController = navHostController, rootView = { + // Handle results of the Root View rootResultHandlers.forEach { resultHandler -> resultHandler.HandleResult(viewModel = this, navHostController = navController) } + + // Render original content content() }, builder = contentBuilder @@ -108,6 +122,7 @@ sealed class ProvidingNavHostComposableNavigator, Provid ) : ComposableNavigator(navigationMapper) { override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> + // Add Result Handlers routeController.AddResultHandlers(viewModel = this, resultHandlers = resultHandlers) content() } @@ -119,7 +134,7 @@ sealed class ProvidingNavHostComposableNavigator, Provid * @param resultHandlers The [NavHostResultHandler] to add to this navigator. * @param navigationMapper Maps [A] to a [ComposableNavSpec] to be navigated to. */ -class NavHostComposableNavigator> ( +class NavHostComposableNavigator>( navHostController: StateFlow, resultHandlers: List> = emptyList(), parentRouteController: RouteController? = null, @@ -136,7 +151,7 @@ class NavHostComposableNavigator> ( * @param resultHandlers The [NavHostResultHandler] to add to this navigator. * @param navigationMapper Maps [A] to a [ComposableNavSpec] to be navigated to. */ -class BottomSheetContentNavHostComposableNavigator> ( +class BottomSheetContentNavHostComposableNavigator>( bottomSheetNavigator: StateFlow, resultHandlers: List> = emptyList(), parentRouteController: RouteController? = null, @@ -153,7 +168,7 @@ class BottomSheetContentNavHostComposableNavigator> ( * @param resultHandlers The [NavHostResultHandler] to add to this navigator. * @param navigationMapper Maps [A] to a [ComposableNavSpec] to be navigated to. */ -class BottomSheetSheetContentNavHostComposableNavigator> ( +class BottomSheetSheetContentNavHostComposableNavigator>( bottomSheetNavigator: StateFlow, resultHandlers: List> = emptyList(), navigationMapper: @Composable (A) -> ComposableNavSpec @@ -164,7 +179,19 @@ class BottomSheetSheetContentNavHostComposableNavigator> override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> coroutineScopeState.tryEmit(rememberCoroutineScope()) + // Add Result Handlers for SheetContent routeController.sheetContentRouteController.AddResultHandlers(viewModel = this, resultHandlers = resultHandlers) content() } } + +/** + * A [ComposableNavigator] that only supports [ComposableNavSpec.LaunchedNavigation] navigation. + * @param navigationMapper Maps [A] to a [ComposableNavSpec.LaunchedNavigation] to be navigated to. + */ +class LaunchedComposableNavigator>( + navigationMapper: @Composable (A) -> ComposableNavSpec.LaunchedNavigation +) : ComposableNavigator(navigationMapper) { + override val routeController: RouteController? = null + override val contentModifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = @Composable { content -> content() } +} diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt index c9f985f70..7ed6977af 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/Route.kt @@ -289,7 +289,8 @@ val , Action : NavigationAction> A */ val , Action : NavigationAction> Action.popTo get() = Route.PopTo( - this + this, + bundle?.let { Route.Result.Data(it) } ?: Route.Result.Empty ) /** @@ -307,3 +308,11 @@ val , Action : NavigationAction> A get() = Route.Replace( this ) + +/** + * Creates a [Route.Back] from [Action] + */ +val , Action : NavigationAction> Action.back + get() = Route.Back( + bundle?.let { Route.Result.Data(it) } ?: Route.Result.Empty + ) diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt index 5b131bb9f..7465d0774 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteController.kt @@ -176,6 +176,8 @@ class BottomSheetSheetContentRouteController( } } } + + // We should call our own back/close mechanisms since the ROOT_VIEW needs to be retained. when (newRoute) { is Route.Close -> close() is Route.Back -> back(newRoute.result) @@ -187,6 +189,8 @@ class BottomSheetSheetContentRouteController( override fun back(result: Route.Result): Boolean { val navHostController = bottomSheetNavigatorState.value?.sheetContentNavHostController val rootRoute = NavDestination.createRoute(ROOT_VIEW).hashCode() + // If navigating back to anything but the ROOT_VIEW, just go back + // Otherwise, close the BottomSheet return if (navHostController != null && navHostController.backQueue.isNotEmpty() && navHostController.previousBackStackEntry?.destination?.id != rootRoute) { navHostController.previousBackStackEntry?.setResult(result) navHostController.popBackStack() @@ -198,6 +202,7 @@ class BottomSheetSheetContentRouteController( override fun close() { bottomSheetNavigatorState.value?.let { (_, sheetContentNavHostController, sheetState) -> + // Make sure we're back at the ROOT_VIEW before hiding the sheet sheetContentNavHostController.popBackStack(ROOT_VIEW, false) coroutineScope.value?.launch { sheetState.hide() diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt index 4b82bc022..fdb05af4a 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/HandleResult.kt @@ -60,9 +60,11 @@ fun NavHostController.HandleResult( @Composable internal fun NavHostController.HandleResult(retain: Boolean = false, onResult: Bundle.() -> Unit) { + // Check if we have a result in the current BackStack. val result = currentBackStackEntry?.savedStateHandle?.getStateFlow(Route.Result.KEY, null)?.collectAsState() result?.value?.let { onResult(it) + // If retain is set we keep the result, otherwise clean up. if (!retain) { currentBackStackEntry?.savedStateHandle?.remove(Route.Result.KEY) } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt index 18bcd6fde..6a34866d9 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler.kt @@ -29,7 +29,7 @@ import kotlin.reflect.KClass import kotlin.reflect.safeCast /** - * A Handler that will allow a [NavHostController] to handle Result of a given type [R] to be received by a given [ViewModel] + * A Handler that will allow a [NavHostController] to handle [Route.Result] of a given type [R] to be received by a given [ViewModel] */ sealed class NavHostResultHandler { abstract val viewModelClass: KClass diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/KalugaViewModelComposeActivity.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/KalugaViewModelComposeActivity.kt index c9e3187f2..b69797c79 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/KalugaViewModelComposeActivity.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/KalugaViewModelComposeActivity.kt @@ -26,16 +26,15 @@ import androidx.compose.runtime.CompositionLocal import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel -import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity /** A [CompositionLocal] containing the current activity. */ val LocalAppCompatActivity = staticCompositionLocalOf { null } /** - * An implementation of [KalugaViewModelActivity] which provides a [CompositionLocal] containing - * the current activity. + * An implementation of [AppCompatActivity] which creates a [VM] and renders it using [ViewModelComposable]. + * Also provides a reference to this [AppCompatActivity] using [LocalAppCompatActivity]. */ -abstract class KalugaViewModelComposeActivity : KalugaViewModelActivity() { +abstract class KalugaViewModelComposeActivity : AppCompatActivity() { @SuppressLint("MissingSuperCall") // Lint bug override fun onCreate(savedInstanceState: Bundle?) { @@ -45,11 +44,32 @@ abstract class KalugaViewModelComposeActivity : Kal CompositionLocalProvider( LocalAppCompatActivity provides this ) { - Layout(viewModel) + RootView { + ViewModelComposable(viewModel = createViewModel()) { + Layout() + } + } } } } + /** + * Wrapper view to contain the ViewModel. + */ @Composable - protected abstract fun Layout(viewModel: VM) + protected open fun RootView(content: @Composable () -> Unit) { + content() + } + + /** + * Creates the [VM]. Vms should be stores so they can be retained, e.g using [androidx.lifecycle.viewmodel.compose.viewModel] + */ + @Composable + protected abstract fun createViewModel(): VM + + /** + * Creates the layout associated with a [VM] + */ + @Composable + protected abstract fun VM.Layout() } diff --git a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt index 1f328e64b..3b2d84f20 100644 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt +++ b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt @@ -27,8 +27,9 @@ import kotlin.reflect.full.memberProperties import kotlin.reflect.full.starProjectedType /** - * Composable which manages [viewModel] lifecycle and optionally adds it to local [ViewModelStore]. - * @param viewModel view model to manage + * Composable which manages [viewModel] lifecycle and binds to all its publically exposed [LifecycleSubscribable]. + * This automatically modifies the content using [ComposableLifecycleSubscribable.modifier]. + * @param viewModel [BaseLifecycleViewModel] to manage * @param content content based on [viewModel] */ @Composable @@ -41,10 +42,17 @@ fun ViewModelComposable( } } +/** + * Composable which manages [viewModel] lifecycle and binds to all its [LifecycleSubscribable] using [fragmentManager]. + * This automatically modifies the content using [ComposableLifecycleSubscribable.modifier]. + * @param viewModel [BaseLifecycleViewModel] to manage + * @param fragmentManager The [FragmentManager] to bind to all [LifecycleSubscribable] publically exposed by the [viewModel]. + * @param content content based on [viewModel] + */ @Composable fun FragmentViewModelComposable( - fragmentManager: FragmentManager, viewModel: ViewModel, + fragmentManager: FragmentManager, content: @Composable (ViewModel.() -> Unit)? = null ) = ViewModelComposable(LocalContext.current.activity, fragmentManager, viewModel, content) @@ -55,17 +63,24 @@ private fun ViewModelComposable( viewModel: ViewModel, content: @Composable (ViewModel.() -> Unit)? = null ) { + // Link the ViewModel to existing LifecycleSubsctibable viewModel.linkLifecycle(activity, fragmentManager) + + // Get a List of all ComposableLifecycleSubscribable of the viewModel. + // Since these are static properties, a remember can be used. val composeLifecycleSubscribables = remember(viewModel) { viewModel.ComposableLifecycleSubscribable } + // If no ComposableLifecycleSubscribable available, just show content if (composeLifecycleSubscribables.isEmpty()) { content?.invoke(viewModel) } else { + // Otherwise, modify the content using the modifier of all ComposableLifecycleSubscribable + // Reduce right so the first ComposableLifecycleSubscribable acts as the first modifier (since we're wrapping) val modifier = composeLifecycleSubscribables.reduceRight { new, acc -> object : ComposableLifecycleSubscribable { - override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = { content -> - new.modifier(this) { acc.modifier(this, content) } + override val modifier: @Composable BaseLifecycleViewModel.(@Composable BaseLifecycleViewModel.() -> Unit) -> Unit = { modifiedContent -> + new.modifier(this) { acc.modifier(this, modifiedContent) } } } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt index 4f8b2cc52..ae73898ec 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt @@ -19,6 +19,7 @@ package com.splendo.kaluga.example.architecture.compose import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRoute import com.splendo.kaluga.architecture.compose.navigation.Route +import com.splendo.kaluga.architecture.compose.navigation.back import com.splendo.kaluga.architecture.compose.navigation.bottomSheetContent import com.splendo.kaluga.architecture.compose.navigation.bottomSheetSheetContent import com.splendo.kaluga.architecture.compose.navigation.next @@ -38,8 +39,8 @@ internal fun architectureNavigationRouteMapper(action: ArchitectureNavigationAct /** Maps a navigation action to a route string. */ internal fun architectureDetailsNavigationRouteMapper(action: ArchitectureDetailsNavigationAction<*>): Route { return when (action) { - is ArchitectureDetailsNavigationAction.FinishWithDetails -> Route.Back(Route.Result.Data(action.bundle!!)) - is ArchitectureDetailsNavigationAction.Close -> Route.Back + is ArchitectureDetailsNavigationAction.FinishWithDetails -> action.back + is ArchitectureDetailsNavigationAction.Close -> action.back } } From 01b8b3d3c969f083870f60097cc36b064c09cc73 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Fri, 25 Nov 2022 10:01:04 +0100 Subject: [PATCH 043/227] Tracking example changes on iOS --- example/ios/Demo.xcodeproj/project.pbxproj | 4 +- .../Demo/Alerts/AlertsViewController.swift | 31 +- .../ArchitectureDetailsViewController.swift | 15 +- .../ArchitectureInputViewController.swift | 35 +-- .../ios/Demo/Base.lproj/Localizable.strings | 1 + example/ios/Demo/Base.lproj/Main.storyboard | 289 +++++++----------- .../DateTimePickerViewController.swift | 17 +- .../FeaturesListViewController.swift | 33 +- .../KeyboardManagerViewController.swift | 16 +- .../LoadingViewController.swift | 16 +- .../Demo/System/SystemViewController.swift | 18 +- .../shared/viewmodel/alert/AlertViewModel.kt | 6 +- .../ArchitectureDetailsNavigator.kt | 33 ++ .../architecture/ArchitectureNavigator.kt | 33 ++ .../architecture/BottomSheetNavigator.kt | 33 ++ .../BottomSheetSubPageNavigator.kt | 33 ++ 16 files changed, 332 insertions(+), 281 deletions(-) create mode 100644 example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsNavigator.kt create mode 100644 example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureNavigator.kt create mode 100644 example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetNavigator.kt create mode 100644 example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetSubPageNavigator.kt diff --git a/example/ios/Demo.xcodeproj/project.pbxproj b/example/ios/Demo.xcodeproj/project.pbxproj index 3a1573fc1..0fa9bd9d5 100644 --- a/example/ios/Demo.xcodeproj/project.pbxproj +++ b/example/ios/Demo.xcodeproj/project.pbxproj @@ -675,7 +675,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Demo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "$(inherited)", @@ -807,7 +807,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = Demo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/example/ios/Demo/Alerts/AlertsViewController.swift b/example/ios/Demo/Alerts/AlertsViewController.swift index 96e81b313..11c301548 100644 --- a/example/ios/Demo/Alerts/AlertsViewController.swift +++ b/example/ios/Demo/Alerts/AlertsViewController.swift @@ -19,7 +19,21 @@ Copyright 2022 Splendo Consulting B.V. The Netherlands import UIKit import KalugaExampleShared -class AlertsViewController: UITableViewController { +class AlertsViewController: UIViewController { + + struct Const { + static let storyboard = UIStoryboard(name: "Main", bundle: nil) + static let storyboardId = "AlertsViewController" + } + + static func instantiate() -> AlertsViewController { + Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! AlertsViewController + } + + @IBOutlet var showAlertButton: UIButton! + @IBOutlet var showAndDismissButton: UIButton! + @IBOutlet var showAlertWithInputButton: UIButton! + @IBOutlet var showAlertWithListButton: UIButton! private lazy var viewModel = AlertViewModel(builder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! @@ -32,16 +46,9 @@ class AlertsViewController: UITableViewController { super.viewDidLoad() lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - switch indexPath.row { - case 0: viewModel.showAlert() - case 1: viewModel.showAndDismissAfter(timeSecs: 3) - case 2: viewModel.showAlertWithList() - case 3: viewModel.showAlertWithInput() - default: () - } + ButtonStyleKt.bindButton(showAlertButton, button: viewModel.showAlertButton) + ButtonStyleKt.bindButton(showAndDismissButton, button: viewModel.showAndDismissAfter3SecondsButton) + ButtonStyleKt.bindButton(showAlertWithInputButton, button: viewModel.showAlertWithInputButton) + ButtonStyleKt.bindButton(showAlertWithListButton, button: viewModel.showAlertWithListButton) } } diff --git a/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift b/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift index cc6b9fcfc..49382fa68 100644 --- a/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift +++ b/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift @@ -30,10 +30,12 @@ class ArchitectureDetailsViewController: UIViewController { if #available(iOS 13.0, *) { vc.isModalInPresentation = true } - let navigator = ViewControllerNavigator(parentVC: vc) { action in + let navigator = ArchitectureDetailsNavigatorKt.ArchitectureDetailsViewControllerNavigator(parent: vc) { details in NavigationSpec.Dismiss(toDismiss: { $0 }, animated: true) { - onDismiss(action.value!) + onDismiss(details) } + } onClose: { + NavigationSpec.Dismiss(toDismiss: { $0 }, animated: true) } vc.viewModel = ArchitectureDetailsViewModel(initialDetail: inputDetails, navigator: navigator) return vc @@ -69,14 +71,9 @@ class ArchitectureDetailsViewController: UIViewController { } ] } - } - - @objc @IBAction func onInversePressed(sender: Any?) { - viewModel.onInversePressed() - } - @objc @IBAction func onCloseButtonPressed(sender: Any?) { - viewModel.onClosePressed() + ButtonStyleKt.bindButton(inverseButton, button: viewModel.inverseButton) + ButtonStyleKt.bindButton(closeButton, button: viewModel.finishButton) } } diff --git a/example/ios/Demo/Architecture/ArchitectureInputViewController.swift b/example/ios/Demo/Architecture/ArchitectureInputViewController.swift index 9317cd640..3df2de0dc 100644 --- a/example/ios/Demo/Architecture/ArchitectureInputViewController.swift +++ b/example/ios/Demo/Architecture/ArchitectureInputViewController.swift @@ -18,7 +18,7 @@ import UIKit import KalugaExampleShared -class ArchitectureInputViewController: UIViewController { +class ArchitectureViewController: UIViewController { @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var nameInput: UITextField! @@ -30,14 +30,18 @@ class ArchitectureInputViewController: UIViewController { @IBOutlet weak var detailsButton: UIButton! - lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - NavigationSpec.Present(animated: true, presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue)) { - ArchitectureDetailsViewController.create(inputDetails: action.value!) { [weak self] inputDetails in - self?.onDetailsDismissed(inputDetails: inputDetails) + lazy var navigator = ArchitectureNavigatorKt.ArchitectureViewControllerNavigator( + parent: self, + onDetails: { inputDetails in + NavigationSpec.Present(animated: true, presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue)) { + ArchitectureDetailsViewController.create(inputDetails: inputDetails) { [weak self] resultDetails in + self?.onDetailsDismissed(inputDetails: resultDetails) + } } - } - } - lazy var viewModel = ArchitectureInputViewModel(navigator: navigator) + }, + onBottomSheet: { fatalError("ToDo") } + ) + lazy var viewModel = ArchitectureViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! deinit { @@ -54,18 +58,12 @@ class ArchitectureInputViewController: UIViewController { } return [ - viewModel.nameHeader.observeInitialized { header in - self?.nameLabel.text = header as String? - }, viewModel.nameInput.observeInitialized { name in self?.nameInput.text = name as String? }, viewModel.isNameValid.observe { isValid in self?.nameError.isHidden = isValid?.boolValue ?? false }, - viewModel.numberHeader.observeInitialized { header in - self?.numberLabel.text = header as String? - }, viewModel.numberInput.observeInitialized { number in self?.numberInput.text = number as String? }, @@ -74,10 +72,9 @@ class ArchitectureInputViewController: UIViewController { } ] } - } - - @objc @IBAction func onShowDetailsPressed(sender: Any?) { - viewModel.onShowDetailsPressed() + nameLabel.text = viewModel.namePlaceholder + numberLabel.text = viewModel.numberPlaceholder + ButtonStyleKt.bindButton(detailsButton, button: viewModel.showDetailsButton) } private func onDetailsDismissed(inputDetails: InputDetails) { @@ -87,7 +84,7 @@ class ArchitectureInputViewController: UIViewController { } -extension ArchitectureInputViewController : UITextFieldDelegate { +extension ArchitectureViewController : UITextFieldDelegate { func textFieldDidEndEditing(_ textField: UITextField) { postInput(text: textField.text ?? "", fromTextField: textField) diff --git a/example/ios/Demo/Base.lproj/Localizable.strings b/example/ios/Demo/Base.lproj/Localizable.strings index 120761c50..1fda4e7ee 100644 --- a/example/ios/Demo/Base.lproj/Localizable.strings +++ b/example/ios/Demo/Base.lproj/Localizable.strings @@ -18,6 +18,7 @@ "show_alert" = "Show Alert"; "dismissible_alert" = "Dismissible Alert"; "alert_list" = "Show Alert List"; +"alert_input" = "Show Alert with input"; "show_loading_indicator_system" = "Show Loading Indicator (System)"; "show_loading_indicator_custom" = "Show Loading Indicator (Custom)"; diff --git a/example/ios/Demo/Base.lproj/Main.storyboard b/example/ios/Demo/Base.lproj/Main.storyboard index c07aeb629..9e5381194 100644 --- a/example/ios/Demo/Base.lproj/Main.storyboard +++ b/example/ios/Demo/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -11,10 +11,10 @@ - + - + @@ -45,9 +45,6 @@ @@ -132,42 +129,34 @@ - - + + + - - - - + @@ -317,8 +306,6 @@ - - @@ -329,12 +316,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -803,16 +848,10 @@ @@ -831,6 +870,8 @@ + + @@ -918,156 +959,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + @@ -1219,16 +1148,10 @@ - diff --git a/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift b/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift index 9ee2d520e..f189e466c 100644 --- a/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift +++ b/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift @@ -19,8 +19,11 @@ import UIKit import KalugaExampleShared class DateTimePickerViewController : UIViewController { - + + @IBOutlet private var currentTimeLabel: UILabel! @IBOutlet private var timeLabel: UILabel! + @IBOutlet private var dateButton: UIButton! + @IBOutlet private var timeButton: UIButton! lazy var viewModel = DateTimePickerViewModel(dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! @@ -42,15 +45,9 @@ class DateTimePickerViewController : UIViewController { } ] } - } - - @IBAction - func selectDatePressed() { - viewModel.onSelectDatePressed() - } - @IBAction - func selectTimePressed() { - viewModel.onSelectTimePressed() + currentTimeLabel.text = viewModel.currentTimeTitle + ButtonStyleKt.bindButton(dateButton, button: viewModel.selectDateButton) + ButtonStyleKt.bindButton(timeButton, button: viewModel.selectTimeButton) } } diff --git a/example/ios/Demo/FeaturesList/FeaturesListViewController.swift b/example/ios/Demo/FeaturesList/FeaturesListViewController.swift index e520eaa27..ffc992232 100644 --- a/example/ios/Demo/FeaturesList/FeaturesListViewController.swift +++ b/example/ios/Demo/FeaturesList/FeaturesListViewController.swift @@ -21,7 +21,7 @@ import KalugaExampleShared class FeaturesListViewController : UITableViewController { private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in - NavigationSpec.Segue(identifier: action.segueKey) + action.spec } private lazy var viewModel: FeatureListViewModel = FeatureListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! @@ -81,23 +81,24 @@ class FeaturesListCell : UITableViewCell { } private extension FeatureListNavigationAction { - var segueKey: String { + var spec: NavigationSpec { get { + let storyboard = UIStoryboard(name: "Main", bundle: nil) switch self { - case is FeatureListNavigationAction.Alerts: return "showAlerts" - case is FeatureListNavigationAction.Architecture: return "showArchitecture" - case is FeatureListNavigationAction.Beacons: return "showBeacons" - case is FeatureListNavigationAction.Bluetooth: return "showBluetooth" - case is FeatureListNavigationAction.DateTimePicker: return "showDateTimePicker" - case is FeatureListNavigationAction.Keyboard: return "showKeyboard" - case is FeatureListNavigationAction.Links: return "showLinks" - case is FeatureListNavigationAction.LoadingIndicator: return "showHUD" - case is FeatureListNavigationAction.Location: return "showLocation" - case is FeatureListNavigationAction.Permissions: return "showPermissions" - case is FeatureListNavigationAction.PlatformSpecific: return "showPlatformSpecific" - case is FeatureListNavigationAction.Resources: return "showResources" - case is FeatureListNavigationAction.System: return "showSystem" - default: return "" + case is FeatureListNavigationAction.Alerts: return NavigationSpec.Push(animated: true) { AlertsViewController.instantiate() } + case is FeatureListNavigationAction.Architecture: return NavigationSpec.Segue(identifier: "showArchitecture") + case is FeatureListNavigationAction.Beacons: return NavigationSpec.Segue(identifier: "showBeacons") + case is FeatureListNavigationAction.Bluetooth: return NavigationSpec.Segue(identifier: "showBluetooth") + case is FeatureListNavigationAction.DateTimePicker: return NavigationSpec.Segue(identifier: "showDateTimePicker") + case is FeatureListNavigationAction.Keyboard: return NavigationSpec.Segue(identifier: "showKeyboard") + case is FeatureListNavigationAction.Links: return NavigationSpec.Segue(identifier: "showLinks") + case is FeatureListNavigationAction.LoadingIndicator: return NavigationSpec.Segue(identifier: "showHUD") + case is FeatureListNavigationAction.Location: return NavigationSpec.Segue(identifier: "showLocation") + case is FeatureListNavigationAction.Permissions: return NavigationSpec.Segue(identifier: "showPermissions") + case is FeatureListNavigationAction.PlatformSpecific: return NavigationSpec.Segue(identifier: "showPlatformSpecific") + case is FeatureListNavigationAction.Resources: return NavigationSpec.Segue(identifier: "showResources") + case is FeatureListNavigationAction.System: return NavigationSpec.Segue(identifier: "showSystem") + default: fatalError("Unknown action") } } } diff --git a/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift b/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift index 8e0d31743..94d32eda7 100644 --- a/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift +++ b/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift @@ -21,11 +21,13 @@ import KalugaExampleShared class KeyboardManagerViewController : UIViewController { @IBOutlet private var editField: UITextField! + @IBOutlet private var showButton: UIButton! + @IBOutlet private var hideButton: UIButton! private lazy var editFieldFocusHandler = { return UIKitFocusHandler(view: self.editField) }() - lazy var viewModel = KeyboardViewModel(keyboardManagerBuilder: KeyboardManager.Builder(application: UIApplication.shared), editFieldFocusHandler: editFieldFocusHandler) + lazy var viewModel = KeyboardViewModel(keyboardManagerBuilder: UIKitKeyboardManager.Builder(application: UIApplication.shared), editFieldFocusHandler: editFieldFocusHandler) private var lifecycleManager: LifecycleManager! deinit { @@ -36,15 +38,7 @@ class KeyboardManagerViewController : UIViewController { super.viewDidLoad() lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } - } - - @IBAction - func showButtonPressed() { - viewModel.onShowPressed() - } - - @IBAction - func hideButtonPressed() { - viewModel.onHidePressed() + ButtonStyleKt.bindButton(showButton, button: viewModel.showButton) + ButtonStyleKt.bindButton(hideButton, button: viewModel.hideButton) } } diff --git a/example/ios/Demo/LoadingIndicator/LoadingViewController.swift b/example/ios/Demo/LoadingIndicator/LoadingViewController.swift index 94ce6baad..73dce2fe1 100644 --- a/example/ios/Demo/LoadingIndicator/LoadingViewController.swift +++ b/example/ios/Demo/LoadingIndicator/LoadingViewController.swift @@ -18,7 +18,10 @@ import UIKit import KalugaExampleShared -class LoadingViewController: UITableViewController { +class LoadingViewController: UIViewController { + + @IBOutlet private var systemButton: UIButton! + @IBOutlet private var customButton: UIButton! private lazy var viewModel = HudViewModel(builder: HUD.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! @@ -31,14 +34,7 @@ class LoadingViewController: UITableViewController { super.viewDidLoad() lifecycleManager = viewModel.addLifecycleManager(parent: self) { return [] } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - switch (indexPath.row) { - case 0: viewModel.onShowSystemPressed() - case 1: viewModel.onShowCustomPressed() - default: () - } + ButtonStyleKt.bindButton(systemButton, button: viewModel.showSystemButton) + ButtonStyleKt.bindButton(customButton, button: viewModel.showCustomButton) } } diff --git a/example/ios/Demo/System/SystemViewController.swift b/example/ios/Demo/System/SystemViewController.swift index 782e14c5b..0f8cf0bcb 100644 --- a/example/ios/Demo/System/SystemViewController.swift +++ b/example/ios/Demo/System/SystemViewController.swift @@ -29,8 +29,8 @@ class SystemViewController : UITableViewController { } private lazy var viewModel: SystemViewModel = SystemViewModel(navigator: navigator) - private var modules = [String]() - private var onModuleTapped: ((Int) -> Void)? = nil + private var systemFeatures = [String]() + private var onSystemFeatureTapped: ((Int) -> Void)? = nil private var lifecycleManager: LifecycleManager! deinit { @@ -45,10 +45,10 @@ class SystemViewController : UITableViewController { return [] } return [ - viewModel.modules.observeInitialized { next in - let modules = next?.compactMap { $0 as? SystemFeatures } ?? [] - self?.modules = modules.map{ $0.name } - self?.onModuleTapped = { (index: Int) in viewModel.onButtonTapped(systemFeatures: modules[index]) } + viewModel.systemFeatures.observeInitialized { next in + let systemFeatures = next?.compactMap { $0 as? SystemFeatures } ?? [] + self?.systemFeatures = systemFeatures.map{ $0.name } + self?.onSystemFeatureTapped = { (index: Int) in viewModel.onButtonTapped(systemFeatures: systemFeatures[index]) } self?.tableView.reloadData() } ] @@ -60,17 +60,17 @@ class SystemViewController : UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return modules.count + return systemFeatures.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: SystemListCell.Const.identifier, for: indexPath) as! SystemListCell - cell.label.text = modules[indexPath.row] + cell.label.text = systemFeatures[indexPath.row] return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onModuleTapped?(indexPath.row) + let _ = onSystemFeatureTapped?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt index 8c0a48ef4..3f5616681 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt @@ -33,7 +33,11 @@ import kotlinx.coroutines.launch import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -class AlertViewModel(val builder: BaseAlertPresenter.Builder, dismissTime: Duration = 3.seconds) : BaseLifecycleViewModel() { +class AlertViewModel(val builder: BaseAlertPresenter.Builder) : BaseLifecycleViewModel() { + + companion object { + private val dismissTime: Duration = 3.seconds + } val showAlertButton = KalugaButton.Plain("show_alert".localized(), ButtonStyles.default) { coroutineScope.launch { diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsNavigator.kt new file mode 100644 index 000000000..e61aae475 --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsNavigator.kt @@ -0,0 +1,33 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.viewmodel.architecture + +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.UIKit.UIViewController + +fun ArchitectureDetailsViewControllerNavigator( + parent: UIViewController, + onFinishWithDetails: (InputDetails) -> NavigationSpec, + onClose: () -> NavigationSpec +) = ViewControllerNavigator>(parent) { action -> + when (action) { + is ArchitectureDetailsNavigationAction.FinishWithDetails -> onFinishWithDetails(action.value) + is ArchitectureDetailsNavigationAction.Close -> onClose() + } +} diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureNavigator.kt new file mode 100644 index 000000000..0c0e85735 --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureNavigator.kt @@ -0,0 +1,33 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.viewmodel.architecture + +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.UIKit.UIViewController + +fun ArchitectureViewControllerNavigator( + parent: UIViewController, + onDetails: (InputDetails) -> NavigationSpec, + onBottomSheet: () -> NavigationSpec +) = ViewControllerNavigator>(parent) { action -> + when (action) { + is ArchitectureNavigationAction.Details -> onDetails(action.value) + is ArchitectureNavigationAction.BottomSheet -> onBottomSheet() + } +} diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetNavigator.kt new file mode 100644 index 000000000..de8dbee20 --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetNavigator.kt @@ -0,0 +1,33 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.viewmodel.architecture + +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.UIKit.UIViewController + +fun BottomSheetViewControllerNavigator( + parent: UIViewController, + onClose: () -> NavigationSpec, + onSubPage: () -> NavigationSpec +) = ViewControllerNavigator(parent) { action -> + when (action) { + is BottomSheetNavigation.Close -> onClose() + is BottomSheetNavigation.SubPage -> onSubPage() + } +} diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetSubPageNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetSubPageNavigator.kt new file mode 100644 index 000000000..b322bca78 --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/BottomSheetSubPageNavigator.kt @@ -0,0 +1,33 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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 com.splendo.kaluga.example.shared.viewmodel.architecture + +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.UIKit.UIViewController + +fun BottomSheetSubPageViewControllerNavigator( + parent: UIViewController, + onClose: () -> NavigationSpec, + onBack: () -> NavigationSpec +) = ViewControllerNavigator(parent) { action -> + when (action) { + is BottomSheetSubPageNavigation.Close -> onClose() + is BottomSheetSubPageNavigation.Back -> onBack() + } +} From 4e3cb4f8cbbacaaa09ad8df27d9e8468c31e45a0 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 30 Nov 2022 16:30:00 +0100 Subject: [PATCH 044/227] Added remaining Architecture UIKit logic --- .../kotlin/navigation/NavigationSpec.kt | 6 +- .../iosMain/kotlin/navigation/Navigator.kt | 11 ++ example/ios/Demo.xcodeproj/project.pbxproj | 8 ++ .../ArchitectureDetailsViewController.swift | 12 +- .../ArchitectureInputViewController.swift | 22 +++- .../BottomSheetSubPageViewController.swift | 74 ++++++++++++ .../BottomSheetViewController.swift | 72 ++++++++++++ .../ios/Demo/Base.lproj/Localizable.strings | 7 ++ example/ios/Demo/Base.lproj/Main.storyboard | 111 +++++++++++++++--- 9 files changed, 300 insertions(+), 23 deletions(-) create mode 100644 example/ios/Demo/Architecture/BottomSheetSubPageViewController.swift create mode 100644 example/ios/Demo/Architecture/BottomSheetViewController.swift diff --git a/architecture/src/iosMain/kotlin/navigation/NavigationSpec.kt b/architecture/src/iosMain/kotlin/navigation/NavigationSpec.kt index 0954f120e..083c57078 100644 --- a/architecture/src/iosMain/kotlin/navigation/NavigationSpec.kt +++ b/architecture/src/iosMain/kotlin/navigation/NavigationSpec.kt @@ -62,15 +62,17 @@ sealed class NavigationSpec { * Navigates to a new view controller by pushing it on top of the parents [UINavigationController] * @param animated Specifies whether transition should be animated * @param push Function to create the [UIViewController] to push + * @param completion Optional function to call when push has completed */ - data class Push(val animated: Boolean = true, val push: () -> UIViewController) : NavigationSpec() + data class Push(val animated: Boolean = true, val push: () -> UIViewController, val completion: (() -> Unit)? = null) : NavigationSpec() /** * Pops the viewController on the parents [UINavigationController]. * @param to Optional [UIViewController] to pop to. If not provided the viewcontroller on top is popped. * @param animated Specifies whether transition should be animated + * @param completion Optional function to call when pop has completed */ - data class Pop(val to: UIViewController? = null, val animated: Boolean = true) : NavigationSpec() + data class Pop(val to: UIViewController? = null, val animated: Boolean = true, val completion: (() -> Unit)? = null) : NavigationSpec() /** * Lets the parent present a [UIViewController] using [UIViewController.presentViewController]. diff --git a/architecture/src/iosMain/kotlin/navigation/Navigator.kt b/architecture/src/iosMain/kotlin/navigation/Navigator.kt index 260578141..2cebdf3f8 100644 --- a/architecture/src/iosMain/kotlin/navigation/Navigator.kt +++ b/architecture/src/iosMain/kotlin/navigation/Navigator.kt @@ -25,6 +25,7 @@ import platform.Foundation.NSURL import platform.Foundation.numberWithInt import platform.MessageUI.MFMailComposeViewController import platform.MessageUI.MFMessageComposeViewController +import platform.QuartzCore.CATransaction import platform.SafariServices.SFSafariViewController import platform.StoreKit.SKStoreProductParameterAdvertisingPartnerToken import platform.StoreKit.SKStoreProductParameterAffiliateToken @@ -112,15 +113,25 @@ class ViewControllerNavigator>( private fun pushViewController(pushSpec: NavigationSpec.Push) { val parent = assertParent() ?: return assert(parent.navigationController != null) + CATransaction.begin() + CATransaction.setCompletionBlock { + pushSpec.completion?.invoke() + } parent.navigationController?.pushViewController(pushSpec.push(), pushSpec.animated) + CATransaction.commit() } private fun popViewController(popSpec: NavigationSpec.Pop) { val parent = assertParent() ?: return assert(parent.navigationController != null) + CATransaction.begin() + CATransaction.setCompletionBlock { + popSpec.completion?.invoke() + } popSpec.to?.let { parent.navigationController?.popToViewController(it, popSpec.animated) } ?: parent.navigationController?.popViewControllerAnimated(popSpec.animated) + CATransaction.commit() } private fun presentViewController(presentSpec: NavigationSpec.Present) { diff --git a/example/ios/Demo.xcodeproj/project.pbxproj b/example/ios/Demo.xcodeproj/project.pbxproj index 0fa9bd9d5..e8c51a639 100644 --- a/example/ios/Demo.xcodeproj/project.pbxproj +++ b/example/ios/Demo.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ 20C7D1D52469B25B00C9A63F /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D1D42469B25B00C9A63F /* InfoViewController.swift */; }; 20C7D1D8246A93A600C9A63F /* ArchitectureInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D1D7246A93A600C9A63F /* ArchitectureInputViewController.swift */; }; 20C7D1DA246A944A00C9A63F /* ArchitectureDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D1D9246A944A00C9A63F /* ArchitectureDetailsViewController.swift */; }; + 20CA2905293796C5007E4079 /* BottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20CA2904293796C5007E4079 /* BottomSheetViewController.swift */; }; + 20CA2907293796D7007E4079 /* BottomSheetSubPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20CA2906293796D7007E4079 /* BottomSheetSubPageViewController.swift */; }; 20D8CD3726FE8FE200D14AC9 /* ResourcesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D8CD3626FE8FE200D14AC9 /* ResourcesListViewController.swift */; }; 20D8CD3926FF550100D14AC9 /* LabelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D8CD3826FF550100D14AC9 /* LabelViewController.swift */; }; 4B02E504236B221B003F0B36 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B02E503236B221B003F0B36 /* LoadingViewController.swift */; }; @@ -90,6 +92,8 @@ 20C7D1D42469B25B00C9A63F /* InfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = ""; }; 20C7D1D7246A93A600C9A63F /* ArchitectureInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchitectureInputViewController.swift; sourceTree = ""; }; 20C7D1D9246A944A00C9A63F /* ArchitectureDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchitectureDetailsViewController.swift; sourceTree = ""; }; + 20CA2904293796C5007E4079 /* BottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetViewController.swift; sourceTree = ""; }; + 20CA2906293796D7007E4079 /* BottomSheetSubPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetSubPageViewController.swift; sourceTree = ""; }; 20D8CD3626FE8FE200D14AC9 /* ResourcesListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesListViewController.swift; sourceTree = ""; }; 20D8CD3826FF550100D14AC9 /* LabelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelViewController.swift; sourceTree = ""; }; 4B02E503236B221B003F0B36 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; @@ -205,6 +209,8 @@ children = ( 20C7D1D7246A93A600C9A63F /* ArchitectureInputViewController.swift */, 20C7D1D9246A944A00C9A63F /* ArchitectureDetailsViewController.swift */, + 20CA2904293796C5007E4079 /* BottomSheetViewController.swift */, + 20CA2906293796D7007E4079 /* BottomSheetSubPageViewController.swift */, ); path = Architecture; sourceTree = ""; @@ -520,6 +526,7 @@ 20D8CD3926FF550100D14AC9 /* LabelViewController.swift in Sources */, 4BC0CBE0269F1D25000512A9 /* BeaconsViewCell.swift in Sources */, 20C7D1D8246A93A600C9A63F /* ArchitectureInputViewController.swift in Sources */, + 20CA2905293796C5007E4079 /* BottomSheetViewController.swift in Sources */, 20C7D1D22469A3D600C9A63F /* FeaturesListViewController.swift in Sources */, 20C7D1DA246A944A00C9A63F /* ArchitectureDetailsViewController.swift in Sources */, 649979D325CC638C00348419 /* SystemViewController.swift in Sources */, @@ -529,6 +536,7 @@ 7425815FF35195035A8991B0 /* LocationViewController.swift in Sources */, 209471E624C07ED9001426CD /* BluetoothDeviceDetailsViewController.swift in Sources */, 209471E424C07EAD001426CD /* BluetoothViewController.swift in Sources */, + 20CA2907293796D7007E4079 /* BottomSheetSubPageViewController.swift in Sources */, 4B54CB212369DA6600B69B2D /* AlertsViewController.swift in Sources */, 2073D0F0270303B3002CB856 /* ButtonViewController.swift in Sources */, 20C7D1CC246968AA00C9A63F /* ExampleViewController.swift in Sources */, diff --git a/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift b/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift index 49382fa68..f4ca69754 100644 --- a/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift +++ b/example/ios/Demo/Architecture/ArchitectureDetailsViewController.swift @@ -31,11 +31,11 @@ class ArchitectureDetailsViewController: UIViewController { vc.isModalInPresentation = true } let navigator = ArchitectureDetailsNavigatorKt.ArchitectureDetailsViewControllerNavigator(parent: vc) { details in - NavigationSpec.Dismiss(toDismiss: { $0 }, animated: true) { + NavigationSpec.Pop(to: nil, animated: true) { onDismiss(details) } } onClose: { - NavigationSpec.Dismiss(toDismiss: { $0 }, animated: true) + NavigationSpec.Pop(to: nil, animated: true) } vc.viewModel = ArchitectureDetailsViewModel(initialDetail: inputDetails, navigator: navigator) return vc @@ -56,6 +56,10 @@ class ArchitectureDetailsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + navigationItem.hidesBackButton = true + let newBackButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(didDismiss)) + self.navigationItem.leftBarButtonItem = newBackButton + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { @@ -75,5 +79,9 @@ class ArchitectureDetailsViewController: UIViewController { ButtonStyleKt.bindButton(inverseButton, button: viewModel.inverseButton) ButtonStyleKt.bindButton(closeButton, button: viewModel.finishButton) } + + @objc func didDismiss() { + viewModel.onBackPressed() + } } diff --git a/example/ios/Demo/Architecture/ArchitectureInputViewController.swift b/example/ios/Demo/Architecture/ArchitectureInputViewController.swift index 3df2de0dc..04149aefc 100644 --- a/example/ios/Demo/Architecture/ArchitectureInputViewController.swift +++ b/example/ios/Demo/Architecture/ArchitectureInputViewController.swift @@ -29,17 +29,34 @@ class ArchitectureViewController: UIViewController { @IBOutlet weak var numberError: UIImageView! @IBOutlet weak var detailsButton: UIButton! + @IBOutlet weak var bottomSheetButton: UIButton! lazy var navigator = ArchitectureNavigatorKt.ArchitectureViewControllerNavigator( parent: self, onDetails: { inputDetails in - NavigationSpec.Present(animated: true, presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue)) { + NavigationSpec.Push( + animated: true + ) { ArchitectureDetailsViewController.create(inputDetails: inputDetails) { [weak self] resultDetails in self?.onDetailsDismissed(inputDetails: resultDetails) } } }, - onBottomSheet: { fatalError("ToDo") } + onBottomSheet: { + NavigationSpec.Present( + animated: true, + presentationStyle: Int64(UIModalPresentationStyle.automatic.rawValue), + transitionStyle: Int64(UIModalTransitionStyle.coverVertical.rawValue) + ) { + let vc = BottomSheetViewController.create() + let nav = UINavigationController(rootViewController: vc) + if let sheet = nav.sheetPresentationController { + sheet.detents = [.medium()] + } + nav.isModalInPresentation = true + return nav + } + } ) lazy var viewModel = ArchitectureViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! @@ -75,6 +92,7 @@ class ArchitectureViewController: UIViewController { nameLabel.text = viewModel.namePlaceholder numberLabel.text = viewModel.numberPlaceholder ButtonStyleKt.bindButton(detailsButton, button: viewModel.showDetailsButton) + ButtonStyleKt.bindButton(bottomSheetButton, button: viewModel.showBottomSheetButton) } private func onDetailsDismissed(inputDetails: InputDetails) { diff --git a/example/ios/Demo/Architecture/BottomSheetSubPageViewController.swift b/example/ios/Demo/Architecture/BottomSheetSubPageViewController.swift new file mode 100644 index 000000000..3d5cddee7 --- /dev/null +++ b/example/ios/Demo/Architecture/BottomSheetSubPageViewController.swift @@ -0,0 +1,74 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KalugaExampleShared + +class BottomSheetSubPageViewController: UIViewController, UISheetPresentationControllerDelegate { + + struct Const { + static let storyboard = UIStoryboard(name: "Main", bundle: nil) + static let storyboardId = "BottomSheetSubPage" + } + + static func create() -> BottomSheetSubPageViewController { + let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! BottomSheetSubPageViewController + let navigator = BottomSheetSubPageNavigatorKt.BottomSheetSubPageViewControllerNavigator(parent: vc, onClose: { + NavigationSpec.Dismiss(toDismiss: { viewController in + viewController.navigationController! + }, animated: true) + }, onBack: { + NavigationSpec.Pop(to: nil, animated: true) + }) + vc.viewModel = BottomSheetSubPageViewModel(navigator: navigator) + return vc + } + + var viewModel: BottomSheetSubPageViewModel! + private var lifecycleManager: LifecycleManager! + + @IBOutlet weak var label: UILabel! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.hidesBackButton = true + let newBackButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(didDismiss)) + self.navigationItem.leftBarButtonItem = newBackButton + let closeButton = UIBarButtonItem(title: "Close", style: .plain, target: self, action: #selector(didClose)) + self.navigationItem.rightBarButtonItem = closeButton + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { + return [] + } + + label.text = viewModel.text + } + + @objc func didDismiss() { + viewModel.onBackPressed() + } + + @objc func didClose() { + viewModel.onClosePressed() + } + +} diff --git a/example/ios/Demo/Architecture/BottomSheetViewController.swift b/example/ios/Demo/Architecture/BottomSheetViewController.swift new file mode 100644 index 000000000..c34aa3b71 --- /dev/null +++ b/example/ios/Demo/Architecture/BottomSheetViewController.swift @@ -0,0 +1,72 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +import UIKit +import KalugaExampleShared + +class BottomSheetViewController: UIViewController, UISheetPresentationControllerDelegate { + + struct Const { + static let storyboard = UIStoryboard(name: "Main", bundle: nil) + static let storyboardId = "BottomSheet" + } + + static func create() -> BottomSheetViewController { + let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! BottomSheetViewController + let navigator = BottomSheetNavigatorKt.BottomSheetViewControllerNavigator(parent: vc, onClose: { + NavigationSpec.Dismiss(toDismiss: { viewController in + viewController.navigationController! + }, animated: true) + }, onSubPage: { + NavigationSpec.Push(animated: true) { + BottomSheetSubPageViewController.create() + } + }) + vc.viewModel = BottomSheetViewModel(navigator: navigator) + return vc + } + + var viewModel: BottomSheetViewModel! + private var lifecycleManager: LifecycleManager! + + @IBOutlet weak var label: UILabel! + @IBOutlet weak var button: UIButton! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.hidesBackButton = true + let newBackButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(didDismiss)) + self.navigationItem.leftBarButtonItem = newBackButton + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { + return [] + } + + label.text = viewModel.text + ButtonStyleKt.bindButton(button, button: viewModel.button) + } + + @objc func didDismiss() { + viewModel.onClosePressed() + } + +} diff --git a/example/ios/Demo/Base.lproj/Localizable.strings b/example/ios/Demo/Base.lproj/Localizable.strings index 1fda4e7ee..91aa9e3c2 100644 --- a/example/ios/Demo/Base.lproj/Localizable.strings +++ b/example/ios/Demo/Base.lproj/Localizable.strings @@ -51,6 +51,7 @@ "architecture_number_input_placeholder" = "Enter a number"; "architecture_details" = "Show Details"; "architecture_details_inverse" = "Inverse Input"; +"architecture_finish" = "Finish"; "keyboard_manager" = "Keyboard"; "show_keyboard" = "Show Keyboard"; "hide_keyboard" = "Hide Keyboard"; @@ -93,6 +94,12 @@ "bluetooth_permissions_read_encrypted" = "Read Encrypted"; "bluetooth_permissions_write_encrypted" = "Write Encrypted"; +"bottom_sheet_show_sheet" = "Open Bottom Sheet"; +"bottom_sheet_show_sub_page" = "Open Sub Page"; +"bottom_sheet_sub_page_title" = "Sub Page"; +"bottom_sheet_sheet_content" = "This is a bottom sheet"; +"bottom_sheet_sheet_sub_page_title" = "Sheet Sub Page"; + "beacons_start_monitoring" = "Start Monitoring"; "beacons_stop_monitoring" = "Stop Monitoring"; diff --git a/example/ios/Demo/Base.lproj/Main.storyboard b/example/ios/Demo/Base.lproj/Main.storyboard index 9e5381194..9bb0564f9 100644 --- a/example/ios/Demo/Base.lproj/Main.storyboard +++ b/example/ios/Demo/Base.lproj/Main.storyboard @@ -39,13 +39,18 @@ - - + + + @@ -96,6 +101,7 @@ + @@ -166,6 +172,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -273,14 +350,14 @@ - + - + - + - - @@ -1043,15 +1043,15 @@ - - @@ -1222,22 +1222,22 @@ - -