diff --git a/.github/workflows/emulator.yaml b/.github/workflows/emulator.yaml index 5456f9754..9db27bb1b 100644 --- a/.github/workflows/emulator.yaml +++ b/.github/workflows/emulator.yaml @@ -4,6 +4,7 @@ on: pull_request env: GRADLE_OPTS: "-Dorg.gradle.jvmargs=\"-Xmx8G -XX:MaxMetaspaceSize=512m -Dorg.gradle.daemon=false -Dkotlin.incremental=false\" -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dkotlin.daemon.jvm.options=-Xmx8G,-XX:MaxMetaspaceSize=512m,-Dorg.gradle.daemon=false,-Dkotlin.incremental=false" JAVA_OPTS: "-Xmx8G -XX:MaxMetaspaceSize=512m -Dorg.gradle.daemon=false -Dkotlin.incremental=false" + ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 120 jobs: build: @@ -54,7 +55,7 @@ jobs: strategy: fail-fast: false matrix: - api-level: [23, 32] + api-level: [23, 33] # some custom gradle magic makes tests run grouped for `$module-*` and `*-$module` on CI so there is no need to add these to the matrix module: [ "base", diff --git a/.gitignore b/.gitignore index a8f2d60d7..27701c557 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,5 @@ projectFilesBackup*/ google-services.json *.framework + +example/ios/Generated/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..1a8a51182 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "example/ios/Kaluga-SwiftUI"] + path = example/ios/Kaluga-SwiftUI + url = https://github.com/splendo/kaluga-swiftui.git diff --git a/DEVELOP.md b/DEVELOP.md index 665935e71..51cd55fdb 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 [`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,22 +84,24 @@ 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/ +kaluga.exampleEmbeddingMethod=repo +kaluga.exampleMavenRepo=https://oss.sonatype.org/service/local/repositories/comsplendo-REPO_NUMBER/ kaluga.libraryVersion=LIBRARY_VERSION ``` Where -`REPO_NUMBER` is the last one created after merging to master -`LIBRARY_VERSION` is the one that we want to publish +`REPO_NUMBER` is the id of the staging repository created to do the release (normally done automatically by GitHub Actions upon a `master` commit, the number can be found on the https://oss.sonatype.org console under "staging repositories") +`LIBRARY_VERSION` is the version of the library that we are publish + +Don't forget to remove these when you are done. #### Publishing locally @@ -125,20 +127,22 @@ If these values are present as environment variables they will also be picked up #### Publishing via CI -Bitrise automatically publishes every branch to the Sonatype snapshot repository (`https://oss.sonatype.org/content/repositories/snapshots/`). This is done using a (private) Bitrise project. A Maven Central release can be done by manually starting the `publisMavenCentralRelease` workflow for the appropriate release branch. +GitHub Actions automatically publishes every branch (except `master`) to the Sonatype snapshot repository (`https://oss.sonatype.org/content/repositories/snapshots/`). Commits on `master` are send to the Sonatype staging repository as the first step of a Maven Central release. #### Releasing to Maven Central -Projects publishing to Sonatype's release repository need to be manually closed and released before they will appear on Maven Central. This can only be done by people with access. +Projects publishing to Sonatype's staging repository need to be manually closed and released (promoted) before they will appear on Maven Central. This can only be done by people with access to https://oss.sonatype.org. #### 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 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" ``` +Also update the version numbers in the README to the just released version and the next version. + ## Code conventions The project uses regular Kotlin code conventions. This includes not creating `com/splendo/kaluga` directories, since they are common to all other folders. diff --git a/NOTICE b/NOTICE index f293bc349..ea4b7d28d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -Copyright 2019 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/README.md b/README.md index aba90dba7..319c3675f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ To reach this goal it uses Kotlin, specifically [Kotlin Multiplatform](https://k Where appropriate coroutines and `Flow` are used in the API. This enables developers to use [cold streams](https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9) for a modern and efficient design. -While kaluga modules can be used individually, together they form a comprehensive approach to cross-platform development with [shared native code](https://kotlinlang.org/docs/mpp-share-on-platforms.html) and native UIs, including SwiftUI and Compose. +While Kaluga modules can be used individually, together they form a comprehensive approach to cross-platform development with [shared native code](https://kotlinlang.org/docs/mpp-share-on-platforms.html) and native UIs, including SwiftUI and Compose. ### Short examples @@ -49,14 +49,12 @@ builder.subscribe(activity) // this needs be done in the Android source-set to b builder.unsubscribe(activity) // when the Activity is stopped ``` -However kaluga's [architecture module](architecture/) offers a cross-platform [`ViewModel`](architecture/src/commonMain/kotlin/viewmodel/ViewModel.kt) class (which extends `androidx.lifecycle.ViewModel` on Android) that will automatically bind the builder to its lifecycle: +However Kaluga's [architecture module](architecture/) offers a cross-platform [`LifecycleViewModel`](architecture/src/commonMain/kotlin/viewmodel/LifecycleViewModel.kt) class (which extends `androidx.lifecycle.ViewModel` on Android) that will automatically bind the builder to its lifecycle: ```kotlin // this can just be in the commonMain source -class HudViewModel: BaseViewModel() { - - val hudBuilder = HUD.Builder() - +class HudViewModel(private val hudBuilder: HUD.Builder): BaseLifecycleViewModel(hudBuilder) { + suspend fun doWork() = hudBuilder.presentDuring { delay(1000) @@ -69,7 +67,7 @@ Kaluga contains [an example project](example/) that is used to test the develope ## Using Kaluga -For starting a new project based on kaluga see the [kaluga-starter repo](https://github.com/splendo/kaluga-starter), which shows how to do this step by step. +For starting a new project based on Kaluga see the [kaluga-starter repo](https://github.com/splendo/kaluga-starter), which shows how to do this step by step. Kaluga is available on Maven Central. For example the Kaluga Alerts can be imported like this: @@ -78,7 +76,7 @@ repositories { mavenCentral() } dependencies { - implementation("com.splendo.kaluga:alerts:0.2.1") + implementation("com.splendo.kaluga:alerts:1.0.0") } ``` @@ -89,72 +87,77 @@ repositories { maven("https://oss.sonatype.org/content/repositories/snapshots/") } dependencies { - implementation("com.splendo.kaluga:alerts:0.2.2-SNAPSHOT") + implementation("com.splendo.kaluga:alerts:1.0.0-SNAPSHOT") } ``` -**Please mind** that in order for kaluga to work properly on iOS it declares a [strict dependency](https://docs.gradle.org/7.0.2/userguide/rich_versions.html#sec:strict-version) on the [`native-mt` version of the `kotlinx.coroutines` library](https://github.com/Kotlin/kotlinx.coroutines/tree/287a931d3b8ce#native). If you want to use a different version make your own strict dependency declaration. This can be done simply by adding `!!`, for example: `implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt!!")`. - - To use kaluga with SwiftUI and/or Combine we have a [repo with Sourcery templates](https://github.com/splendo/kaluga-swiftui) to generate some Swift code to help get you started. ### Available Modules -Module | Usage | Artifact Name ---- | --- | --- -[alerts](alerts/) | Used for Showing Alert Dialogs | com.splendo.kaluga:alerts -[architecture](architecture/) | MVVM architecture | com.splendo.kaluga:architecture -[architecture-compose](architecture-compose/) | Compose extensions for architecture | com.splendo.kaluga:architecture-compose -[base](base/) | Core components of Kaluga. Contains threading, flowables and localization features | com.splendo.kaluga.base -[date-time](date-time/) | Contains multiplatform classes to work with date and time | com.splendo.kaluga.date-time -[date-timepicker](date-time-picker/) | Used for showing a Date or Time Picker | com.splendo.kaluga.date-time-picker -[hud](hud/) | Used for showing a Loading indicator HUD | com.splendo.kaluga.hud -[keyboard](keyboard/) | Used for showing and hiding the keyboard | com.splendo.kaluga.keyboard -[links](links/) | Used for decoding url into an object | com.splendo.kaluga.links -[location](location/) | Provides the User' geolocation | com.splendo.kaluga.location -[logging](logging/) | Shared console logging | com.splendo.kaluga.logging -[base-permissions](base-permissions/) | Managing permissions, used in conjunction with modules below | com.splendo.kaluga:base-permissions -[bluetooth-permissions](bluetooth-permissions/) | Managing bluetooth permissions | com.splendo.kaluga:bluetooth-permissions -[calendar-permissions](calendar-permissions/) | Managing calendar permissions | com.splendo.kaluga:calendar-permissions -[camera-permissions](camera-permissions/) | Managing camera permissions | com.splendo.kaluga:camera-permissions -[contacts-permissions](contacts-permissions/) | Managing contacts permissions | com.splendo.kaluga:contacts-permissions -[location-permissions](location-permissions/) | Managing location permissions | com.splendo.kaluga:location-permissions -[microphone-permissions](microphone-permissions/) | Managing microphone permissions | com.splendo.kaluga:microphone-permissions -[notifications-permissions](notifications-permissions/) | Managing notifications permissions | com.splendo.kaluga:notifications-permissions -[storage-permissions](storage-permissions/) | Managing storage permissions | com.splendo.kaluga:storage-permissions -[resources](resources/) | Provides shared Strings, Images, Colors and Fonts | com.splendo.kaluga.resources -[review](review/) | Used for requesting the user to review the app | com.splendo.kaluga.review -[scientific](scientific/) | Scientific units and conversions | com.splendo.kaluga.scientific -[system](system/) | System APIs such as network, audio, battery | com.splendo.kaluga.system -[test-utils](test-utils/) | Enables easier testing of Kaluga components | com.splendo.kaluga.test-utils -[test-utils-base](test-utils-base/) | Enables easier testing of Kaluga components | com.splendo.kaluga.test-utils-base -[test-utils-alerts](test-utils-alerts/) | Enables easier testing of Alerts module | com.splendo.kaluga.test-utils-alerts -[test-utils-architecture](test-utils-architecture/) | Enables easier testing of Architecture module | com.splendo.kaluga.test-utils-architecture -[test-utils-bluetooth](test-utils-bluetooth/) | Enables easier testing of Bluetooth module | com.splendo.kaluga.test-utils-bluetooth -[test-utils-hud](test-utils-hud/) | Enables easier testing of HUD module | com.splendo.kaluga.test-utils-hud -[test-utils-keyboard](test-utils-keyboard/) | Enables easier testing of Keyboard module | com.splendo.kaluga.test-utils-keyboard -[test-utils-koin](test-utils-koin/) | Enables easier testing with Koin | com.splendo.kaluga.test-utils-koin -[test-utils-location](test-utils-location/) | Enables easier testing of Location module | com.splendo.kaluga.test-utils-location -[test-utils-permissions](test-utils-permissions/) | Enables easier testing of Permissions modules | com.splendo.kaluga.test-utils-permissions -[test-utils-resources](test-utils-resources/) | Enables easier testing of Resources module | com.splendo.kaluga.test-utils-resources +| Module | Usage | Artifact Name | +|-------------------------------------------------------------|------------------------------------------------------------------------------------|------------------------------------------------| +| [alerts](alerts/) | Used for Showing Alert Dialogs | com.splendo.kaluga:alerts | +| [architecture](architecture/) | MVVM architecture | com.splendo.kaluga:architecture | +| [architecture-compose](architecture-compose/) | Compose extensions for architecture | com.splendo.kaluga:architecture-compose | +| [base](base/) | Core components of Kaluga. Contains threading, flowables and localization features | com.splendo.kaluga:base | +| [beacons](beacons/) | Tracking the availability of Beacons using the Eddystone protocol | com.splendo.kaluga:beacons | +| [bluetooth](bluetooth/) | Scanning for and communicating with BLE devices | com.splendo.kaluga:bluetooth | +| [date-time](date-time/) | Contains multiplatform classes to work with date and time | com.splendo.kaluga:date-time | +| [date-timepicker](date-time-picker/) | Used for showing a Date or Time Picker | com.splendo.kaluga:date-time-picker | +| [hud](hud/) | Used for showing a Loading indicator HUD | com.splendo.kaluga:hud | +| [keyboard](keyboard/) | Used for showing and hiding the keyboard | com.splendo.kaluga:keyboard | +| [keyboard-compose](keyboard-compose/) | Compose extensions for keyboard | com.splendo.kaluga:keyboard-compose | +| [links](links/) | Used for decoding url into an object | com.splendo.kaluga:links | +| [location](location/) | Provides the User' geolocation | com.splendo.kaluga:location | +| [logging](logging/) | Shared console logging | com.splendo.kaluga:logging | +| [base-permissions](base-permissions/) | Managing permissions, used in conjunction with modules below | com.splendo.kaluga:base-permissions | +| [bluetooth-permissions](bluetooth-permissions/) | Managing bluetooth permissions | com.splendo.kaluga:bluetooth-permissions | +| [calendar-permissions](calendar-permissions/) | Managing calendar permissions | com.splendo.kaluga:calendar-permissions | +| [camera-permissions](camera-permissions/) | Managing camera permissions | com.splendo.kaluga:camera-permissions | +| [contacts-permissions](contacts-permissions/) | Managing contacts permissions | com.splendo.kaluga:contacts-permissions | +| [location-permissions](location-permissions/) | Managing location permissions | com.splendo.kaluga:location-permissions | +| [microphone-permissions](microphone-permissions/) | Managing microphone permissions | com.splendo.kaluga:microphone-permissions | +| [notifications-permissions](notifications-permissions/) | Managing notifications permissions | com.splendo.kaluga:notifications-permissions | +| [storage-permissions](storage-permissions/) | Managing storage permissions | com.splendo.kaluga:storage-permissions | +| [resources](resources/) | Provides shared Strings, Images, Colors and Fonts | com.splendo.kaluga:resources | +| [resources-compose](resources-compose/) | Compose extensions for resources | com.splendo.kaluga:resources-compose | +| [resources-databinding](resources-databinding/) | Data Binding extensions for resources | com.splendo.kaluga:resources-databinding | +| [review](review/) | Used for requesting the user to review the app | com.splendo.kaluga:review | +| [scientific](scientific/) | Scientific units and conversions | com.splendo.kaluga:scientific | +| [service](service/) | Used for adding services to Kaluga | com.splendo.kaluga:service | +| [system](system/) | System APIs such as network, audio, battery | com.splendo.kaluga:system | +| [test-utils](test-utils/) | Enables easier testing of Kaluga components | com.splendo.kaluga:test-utils | +| [test-utils-base](test-utils-base/) | Enables easier testing of Kaluga components | com.splendo.kaluga:test-utils-base | +| [test-utils-alerts](test-utils-alerts/) | Enables easier testing of Alerts module | com.splendo.kaluga:test-utils-alerts | +| [test-utils-architecture](test-utils-architecture/) | Enables easier testing of Architecture module | com.splendo.kaluga:test-utils-architecture | +| [test-utils-bluetooth](test-utils-bluetooth/) | Enables easier testing of Bluetooth module | com.splendo.kaluga:test-utils-bluetooth | +| [test-utils-date-time-picker](test-utils-date-time-picker/) | Enables easier testing of Date Time Picker module | com.splendo.kaluga:test-utils-date-time-picker | +| [test-utils-hud](test-utils-hud/) | Enables easier testing of HUD module | com.splendo.kaluga:test-utils-hud | +| [test-utils-keyboard](test-utils-keyboard/) | Enables easier testing of Keyboard module | com.splendo.kaluga:test-utils-keyboard | +| [test-utils-koin](test-utils-koin/) | Enables easier testing with Koin | com.splendo:kaluga.test-utils-koin | +| [test-utils-location](test-utils-location/) | Enables easier testing of Location module | com.splendo.kaluga:test-utils-location | +| [test-utils-permissions](test-utils-permissions/) | Enables easier testing of Permissions modules | com.splendo.kaluga:test-utils-permissions | +| [test-utils-resources](test-utils-resources/) | Enables easier testing of Resources module | com.splendo.kaluga:test-utils-resources | +| [test-utils-service](test-utils-service/) | Enables easier testing of Service module | com.splendo.kaluga:test-utils-service | +| [test-utils-system](test-utils-system/) | Enables easier testing of System module | com.splendo.kaluga:test-utils-system | ### Friends of kaluga Of course not every possible functionality is provided by kaluga. However, this is often because other good multiplatform libraries that work nicely with kaluga already exist. These use similar patterns such as coroutines and `Flow`, and include the following: -Project | Usage ---- | --- -[kotlin-firebase-sdk](https://github.com/GitLiveApp/firebase-kotlin-sdk) | wraps most of the Firebase SDK APIs -[multiplatform-settings](https://github.com/russhwolf/multiplatform-settings) | store key/value data -[SQLDelight](https://github.com/cashapp/sqldelight) | access SQLite (and other SQL database) +| Project | Usage | +|-------------------------------------------------------------------------------|----------------------------------------| +| [kotlin-firebase-sdk](https://github.com/GitLiveApp/firebase-kotlin-sdk) | wraps most of the Firebase SDK APIs | +| [multiplatform-settings](https://github.com/russhwolf/multiplatform-settings) | store key/value data | +| [SQLDelight](https://github.com/cashapp/sqldelight) | access SQLite (and other SQL database) | Kaluga also uses some multiplatform libraries itself, so our thanks to: -Project | Usage ---- | --- -[Napier](https://github.com/AAkira/Napier) | powers the logging module -[stately](https://github.com/touchlab/Stately) | concurrency -[Koin](https://insert-koin.io/) | dependency injection +| Project | Usage | +|------------------------------------------------|---------------------------| +| [Napier](https://github.com/AAkira/Napier) | powers the logging module | +| [Koin](https://insert-koin.io/) | dependency injection | ## Developing Kaluga 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/adding-a-new-module/template/build.gradle.kts b/adding-a-new-module/template/build.gradle.kts index ac6377ed6..c23b7e48e 100644 --- a/adding-a-new-module/template/build.gradle.kts +++ b/adding-a-new-module/template/build.gradle.kts @@ -3,22 +3,16 @@ plugins { id("jacoco") id("convention.publication") id("com.android.library") + id("org.jetbrains.dokka") id("org.jlleitschuh.gradle.ktlint") } -val ext = (gradle as ExtensionAware).extra +/* Multiplatform component */ +publishableComponent() +/* Compose component */ +composeAndroidComponent() -apply(from = "../gradle/publishable_component.gradle") - -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/alerts/README.md b/alerts/README.md index 301cf8e27..b9a2d33c6 100644 --- a/alerts/README.md +++ b/alerts/README.md @@ -1,4 +1,3 @@ - # Alerts A library allows you to show native alerts. @@ -21,7 +20,7 @@ dependencies { ## Usage -Using Alerts is very simple. You can show an alert from shared code like this: +You can show an alert from shared code like this: ```kotlin // Shared code @@ -59,9 +58,26 @@ fun showAlert(builder: AlertPresenter.Builder, title: String) = MainScope().laun println("No pressed") } } - // Show alert.show() + // Show + alert.show() } ``` + +In order to dismiss alert you can use `dismiss()` function: + +```kotlin +MainScope().launch { + // Build alert + val alert = builder.buildAlert(this) { + setTitle("Please wait...") + setPositiveButton("OK") + } + // Show + alert.showAsync() + // Dismiss + alert.dismiss() +} +``` ## Builder @@ -69,11 +85,11 @@ The `AlertPresenter.Builder` class can be used to build Alerts. ### Build alert -- `buildAlert(coroutineScope: CoroutineScope, initialize: BaseAlertPresenter.Builder.() -> Unit): AlertPresenter` — builder to create `AlertPresenter`, thread-safe +- `buildAlert(coroutineScope: CoroutineScope, initialize: Alert.Builder.() -> Unit): BaseAlertPresenter` — builder to create `BaseAlertPresenter`, thread-safe ### Build action sheet -- `buildActionSheet(coroutineScope: CoroutineScope, initialize: AlertPresenter.Builder.() -> Unit): AlertPresenter` — builder to create `AlertPresenter`, thread-safe +- `buildActionSheet(coroutineScope: CoroutineScope, initialize: Alert.Builder.() -> Unit): BaseAlertPresenter` — builder to create `BaseAlertPresenter`, thread-safe ### Set title, style and message @@ -91,7 +107,23 @@ The `AlertPresenter.Builder` class can be used to build Alerts. ### Set actions for action sheet - `addActions(actions: List)` or `addActions(vararg actions: Alert.Action)` — adds a list of actions for the alert - + +You can also show action sheet using Actions with handlers: + +```kotlin +fun showList(builder: AlertPresenter.Builder) = MainScope().launch { + builder.buildActionSheet(this) { + setTitle("Select an option") + addActions( + Alert.Action("Option 1") { /* handle option #1 */ }, + Alert.Action("Option 2") { /* handle option #2 */ }, + Alert.Action("Option 3") { /* handle option #3 */ }, + Alert.Action("Option 4") { /* handle option #4 */ } + ) + }.show() +} +``` + > On iOS you can have only 1 button with type of Action.Style.Cancel / Negative #### Action styles @@ -103,13 +135,18 @@ On iOS actions can be: `Default`, `Cancel` and `Destructive`. The `AlertPresenter.Builder` object should be created from the platform side. ### Android -On Android the builder is a `LifecycleSubscribable` (see Architecture) that needs a `LifecycleSubscribable.LifecycleManager` object to provide the current context in which to display the alert. -For `BaseLifecycleViewModel`, the builder should be made **publicly** visible and bound to a `KalugaViewModelLifecycleObserver`. +On Android the builder is an `ActivityLifecycleSubscribable` (see Architecture) that needs an `ActivityLifecycleSubscribable.LifecycleManager` object to provide the current context in which to display the alert. +For `BaseLifecycleViewModel`, the builder should be provided to `BaseLifecycleViewModel.activeLifecycleSubscribables` (using the constructor or `BaseLifecycleViewModel.addLifecycleSubscribables`) and bound to a `KalugaViewModelLifecycleObserver` or `ViewModelComposable`. ```kotlin -class AlertViewModel: LifecycleViewModel() { +class AlertViewModel: BaseLifecycleViewModel() { val builder = AlertPresenter.Builder() + + init { + addLifecycleSubscribables(builder) + } + fun show() { coroutineScope.launch { builder.build(this) { @@ -133,7 +170,7 @@ class MyActivity: KalugaViewModelActivity() { } ``` -For other usages, make sure to call `LifecycleSubscriber.subscribe` and `LifecycleSubscriber.unsubscribe` to manage the lifecycle manually. +For other usages, make sure to call `ActivityLifecycleSubscribable.subscribe` and `ActivityLifecycleSubscribable.unsubscribe` to manage the lifecycle manually. ```kotlin // Android specific @@ -145,8 +182,6 @@ MainScope().launch { }.show() } ``` - -You can use the `AppCompatActivity.alertPresenterBuilder` convenience method to get a builder that is valid during the lifespan of the Activity it belongs to. ### iOS On iOS this builder should be instantiated with `UIViewController`: @@ -154,40 +189,10 @@ On iOS this builder should be instantiated with `UIViewController`: ```swift // iOS specific let builder = AlertPresenter.Builder(viewController) -``` - -You can also show action sheet using Actions with handlers: - -```kotlin -fun showList(builder: AlertPresenter.Builder) = MainScope().launch { - builder.buildActionSheet(this) { - setTitle("Select an option") - addActions( - Alert.Action("Option 1") { /* handle option #1 */ }, - Alert.Action("Option 2") { /* handle option #2 */ }, - Alert.Action("Option 3") { /* handle option #3 */ }, - Alert.Action("Option 4") { /* handle option #4 */ } - ) - }.show() -} -``` -> Cancel action will be added automatically on iOS platform - -In order to dismiss alert you can use `dismiss()` function: - -```kotlin -MainScope().launch { - // Build alert - val alert = builder.buildAlert(this) { - setTitle("Please wait...") - setPositiveButton("OK") - } - // Show - alert.showAsync() - // Dismiss - alert.dismiss() -} ``` +Since a `UIViewController` is required, for SwiftUI the `View` displaying the Alert should have a `UIViewControllerRepresentable` wrapping the `UIViewController` associated with the `AlertPresenter.Builder` attached. +The [Kaluga SwiftUI scripts](https://github.com/splendo/kaluga-swiftui) provide a `ContainerView` that offers this functionality out of the box (if the `includeAlerts` setting is set to `true`) + ## Testing Use the [`test-utils-alerts` module](../test-utils-alerts) to get a mockable Alert Presenter. diff --git a/alerts/api/androidLib/alerts.api b/alerts/api/androidLib/alerts.api index d865d736f..33c17beff 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; @@ -84,21 +103,17 @@ public final class com/splendo/kaluga/alerts/AlertPresenter : com/splendo/kaluga public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; } -public final class com/splendo/kaluga/alerts/AlertPresenter$Builder : com/splendo/kaluga/alerts/BaseAlertPresenter$Builder, com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { +public final class com/splendo/kaluga/alerts/AlertPresenter$Builder : com/splendo/kaluga/alerts/BaseAlertPresenter$Builder, com/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable { 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 getManager ()Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager; - public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager;)V + 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/ActivityLifecycleSubscribable$LifecycleManager; + public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable$LifecycleManager;)V public fun unsubscribe ()V } -public final class com/splendo/kaluga/alerts/AlertPresenterKt { - public static final fun alertPresenterBuilder (Landroidx/appcompat/app/AppCompatActivity;)Lcom/splendo/kaluga/alerts/AlertPresenter$Builder; -} - public final class com/splendo/kaluga/alerts/AlertsKt { public static final fun buildActionSheet (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; public static final fun buildAlert (Lcom/splendo/kaluga/alerts/BaseAlertPresenter$Builder;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lcom/splendo/kaluga/alerts/BaseAlertPresenter; @@ -116,21 +131,8 @@ public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter : com/splendo public fun showAsync (ZLkotlin/jvm/functions/Function0;)V } -public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { +public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { 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..b581e203f 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 { @@ -111,21 +130,8 @@ public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter : com/splendo public fun showAsync (ZLkotlin/jvm/functions/Function0;)V } -public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { +public abstract class com/splendo/kaluga/alerts/BaseAlertPresenter$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { 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/build.gradle.kts b/alerts/build.gradle.kts index 0fa0baf1f..1ef9d89b7 100644 --- a/alerts/build.gradle.kts +++ b/alerts/build.gradle.kts @@ -3,29 +3,22 @@ plugins { id("jacoco") id("convention.publication") id("com.android.library") + id("org.jetbrains.dokka") 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"]!! - dependencies { - - val ext = (gradle as ExtensionAware).extra - - implementation("androidx.fragment:fragment:${ext["androidx_fragment_version"]}") - debugImplementation("androidx.fragment:fragment-ktx:${ext["androidx_fragment_version"]}") + implementationDependency(Dependencies.AndroidX.Fragment) + testImplementationDependency(Dependencies.AndroidX.FragmentKtx) + androidTestImplementationDependency(Dependencies.AndroidX.Activity.Ktx) } +publishableComponent() + kotlin { sourceSets { getByName("commonMain") { dependencies { - val ext = (gradle as ExtensionAware).extra implementation(project(":architecture", "")) implementation(project(":base", "")) implementation(project(":resources", "")) @@ -33,7 +26,7 @@ kotlin { } getByName("commonTest") { dependencies { - implementation(project(":test-utils-base", "")) + implementation(project(":test-utils-alerts", "")) } } } diff --git a/alerts/src/androidLibAndroidTest/AndroidManifest.xml b/alerts/src/androidLibInstrumentedTest/AndroidManifest.xml similarity index 100% rename from alerts/src/androidLibAndroidTest/AndroidManifest.xml rename to alerts/src/androidLibInstrumentedTest/AndroidManifest.xml diff --git a/alerts/src/androidLibAndroidTest/kotlin/AlertsViewModel.kt b/alerts/src/androidLibInstrumentedTest/kotlin/AlertsViewModel.kt similarity index 86% rename from alerts/src/androidLibAndroidTest/kotlin/AlertsViewModel.kt rename to alerts/src/androidLibInstrumentedTest/kotlin/AlertsViewModel.kt index a34ce1ae4..d8d6301dd 100644 --- a/alerts/src/androidLibAndroidTest/kotlin/AlertsViewModel.kt +++ b/alerts/src/androidLibInstrumentedTest/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. @@ -21,4 +21,8 @@ import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel class AlertsViewModel : BaseLifecycleViewModel() { val alertBuilder = AlertPresenter.Builder() + + init { + addLifecycleSubscribables(alertBuilder) + } } diff --git a/alerts/src/androidLibAndroidTest/kotlin/AndroidAlertPresenterTest.kt b/alerts/src/androidLibInstrumentedTest/kotlin/AndroidAlertPresenterTest.kt similarity index 81% rename from alerts/src/androidLibAndroidTest/kotlin/AndroidAlertPresenterTest.kt rename to alerts/src/androidLibInstrumentedTest/kotlin/AndroidAlertPresenterTest.kt index d216dc65e..92a998710 100644 --- a/alerts/src/androidLibAndroidTest/kotlin/AndroidAlertPresenterTest.kt +++ b/alerts/src/androidLibInstrumentedTest/kotlin/AndroidAlertPresenterTest.kt @@ -1,5 +1,5 @@ /* -Copyright 2019 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. @@ -22,10 +22,9 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until -import com.splendo.kaluga.test.AlertPresenterTests import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -36,6 +35,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,14 +54,13 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { override val builder get() = activity!!.viewModel.alertBuilder companion object { - const val DEFAULT_TIMEOUT = 20_000L + val DEFAULT_TIMEOUT = 30.seconds.inWholeMilliseconds } @Test fun testBuilderReuse() = runBlocking { - - launch(Dispatchers.Main) { - builder.buildAlert(MainScope()) { + val job1 = launch(Dispatchers.Main.immediate) { + builder.buildAlert(this) { setTitle("Test") setPositiveButton("OK") }.show() @@ -70,9 +69,10 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { device.wait(Until.findObject(By.text("Test")), DEFAULT_TIMEOUT) device.wait(Until.findObject(By.text("OK")), DEFAULT_TIMEOUT).click() assertTrue(device.wait(Until.gone(By.text("Test")), DEFAULT_TIMEOUT)) + job1.cancel() - launch(Dispatchers.Main) { - builder.buildAlert(MainScope()) { + val job2 = launch(Dispatchers.Main.immediate) { + builder.buildAlert(this) { setTitle("Hello") setNegativeButton("CANCEL") // sometimes this ends up uppercased in the dialog }.show() @@ -81,16 +81,17 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { device.wait(Until.findObject(By.text("Hello")), DEFAULT_TIMEOUT) device.wait(Until.findObject(By.text("CANCEL")), DEFAULT_TIMEOUT).click() assertTrue(device.wait(Until.gone(By.text("Hello")), DEFAULT_TIMEOUT)) + job2.cancel() } @Test fun testConcurrentBuilders() = runBlocking { val alerts = Array(10) { CompletableDeferred() } - (0..9).forEach { i -> + val jobs = (0..9).map { i -> launch(Dispatchers.Default) { alerts[i].complete( - builder.buildAlert(MainScope()) { + builder.buildAlert(CoroutineScope(coroutineContext + Dispatchers.Main.immediate)) { setTitle("Alert$i") setPositiveButton("OK$i") } @@ -98,19 +99,21 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { } } (0..9).forEach { i -> - launch(Dispatchers.Main) { + val job = launch(Dispatchers.Main.immediate) { alerts[i].await().show() } device.wait(Until.findObject(By.text("Alert$i")), DEFAULT_TIMEOUT) device.wait(Until.findObject(By.text("OK$i")), DEFAULT_TIMEOUT).click() assertTrue(device.wait(Until.gone(By.text("Alert$i")), DEFAULT_TIMEOUT)) + job.cancel() } + jobs.forEach { it.cancel() } } @Test fun testAlertShow() = runBlocking { - launch(Dispatchers.Main) { - builder.buildAlert(MainScope()) { + val job = launch(Dispatchers.Main.immediate) { + builder.buildAlert(this) { setTitle("Hello") setPositiveButton("OK") }.show() @@ -118,13 +121,14 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { device.wait(Until.findObject(By.text("Hello")), DEFAULT_TIMEOUT) device.wait(Until.findObject(By.text("OK")), DEFAULT_TIMEOUT).click() assertTrue(device.wait(Until.gone(By.text("Hello")), DEFAULT_TIMEOUT)) + job.cancel() } @Test fun testAlertFlowWithCoroutines() = runBlocking { - launch(Dispatchers.Main) { + val job = launch(Dispatchers.Main.immediate) { val action = Alert.Action("OK") - val presenter = builder.buildAlert(MainScope()) { + val presenter = builder.buildAlert(this) { setTitle("Hello") addActions(listOf(action)) } @@ -135,12 +139,13 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { device.wait(Until.findObject(By.text("Hello")), DEFAULT_TIMEOUT) device.wait(Until.findObject(By.text("OK")), DEFAULT_TIMEOUT).click() assertTrue(device.wait(Until.gone(By.text("Hello")), DEFAULT_TIMEOUT)) + job.cancel() } @Test @Ignore("test framework for rotation is unstable") fun rotateActivity() = runBlocking { - val job = launch(Dispatchers.Main) { + val job = launch(Dispatchers.Main.immediate) { val presenter = builder.buildAlert(this) { setTitle("Hello") setPositiveButton("OK") @@ -165,8 +170,9 @@ class AndroidAlertPresenterTest : AlertPresenterTests() { } @Test - fun testBuilderFromActivity() { - MainScope().launch { activity?.showAlert() } + fun testBuilderFromActivity() = runBlocking { + val job = launch(Dispatchers.Main.immediate) { activity?.showAlert() } device.wait(Until.findObject(By.text("Activity")), DEFAULT_TIMEOUT) + job.cancel() } } diff --git a/alerts/src/androidLibAndroidTest/kotlin/TestActivity.kt b/alerts/src/androidLibInstrumentedTest/kotlin/TestActivity.kt similarity index 89% rename from alerts/src/androidLibAndroidTest/kotlin/TestActivity.kt rename to alerts/src/androidLibInstrumentedTest/kotlin/TestActivity.kt index 91b5fd345..b0c46e5e4 100644 --- a/alerts/src/androidLibAndroidTest/kotlin/TestActivity.kt +++ b/alerts/src/androidLibInstrumentedTest/kotlin/TestActivity.kt @@ -2,6 +2,7 @@ package com.splendo.kaluga.alerts import androidx.activity.viewModels import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.test.alerts.alertPresenterBuilder class TestActivity : KalugaViewModelActivity() { diff --git a/alerts/src/androidLibMain/kotlin/AlertPresenter.kt b/alerts/src/androidLibMain/kotlin/AlertPresenter.kt index 9215dabb3..972634225 100644 --- a/alerts/src/androidLibMain/kotlin/AlertPresenter.kt +++ b/alerts/src/androidLibMain/kotlin/AlertPresenter.kt @@ -1,5 +1,5 @@ /* -Copyright 2019 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. @@ -25,11 +25,8 @@ import android.text.TextWatcher import android.view.ViewGroup import android.widget.EditText import android.widget.LinearLayout -import androidx.appcompat.app.AppCompatActivity +import com.splendo.kaluga.architecture.lifecycle.ActivityLifecycleSubscribable import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver -import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable -import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache -import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver import com.splendo.kaluga.base.utils.applyIf import com.splendo.kaluga.resources.dpToPixel import kotlinx.coroutines.CoroutineScope @@ -37,17 +34,35 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch +/** + * A [BaseAlertPresenter] for presenting an [Alert]. + * @param alert The [Alert] being presented. + * @param lifecycleManagerObserver The [LifecycleManagerObserver] to observe lifecycle changes + * @param coroutineScope The [CoroutineScope] managing changes to the alert presentation. + */ actual class AlertPresenter( private val alert: Alert, private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver(), coroutineScope: CoroutineScope ) : BaseAlertPresenter(alert), CoroutineScope by coroutineScope { + /** + * A [BaseAlertPresenter.Builder] for creating an [AlertPresenter] + * @param lifecycleManagerObserver The [LifecycleManagerObserver] to observe lifecycle changes + */ actual class Builder( private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver() - ) : BaseAlertPresenter.Builder(), LifecycleSubscribable by lifecycleManagerObserver { - actual override fun create(coroutineScope: CoroutineScope) = - AlertPresenter(createAlert(), lifecycleManagerObserver, coroutineScope) + ) : BaseAlertPresenter.Builder(), ActivityLifecycleSubscribable by lifecycleManagerObserver { + + /** + * 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] + */ + actual override fun create(alert: Alert, coroutineScope: CoroutineScope) = + AlertPresenter(alert, lifecycleManagerObserver, coroutineScope) } private companion object { @@ -197,17 +212,3 @@ actual class AlertPresenter( return linearLayout } } - -/** - * @return The [AlertPresenter.Builder] which can be used to present alerts while this Activity is active - * Will be created if need but only one instance will exist. - * - * Warning: Do not attempt to use this builder outside of the lifespan of the Activity. - * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.LifecycleViewModel], - * which can automatically track which Activity is active for it. - * - */ -fun AppCompatActivity.alertPresenterBuilder(): AlertPresenter.Builder = - getOrPutAndRemoveOnDestroyFromCache { - AlertPresenter.Builder(lifecycleManagerObserver()) - } diff --git a/alerts/src/commonMain/kotlin/Alerts.kt b/alerts/src/commonMain/kotlin/Alerts.kt index 60c1c0e9d..8901b0be2 100644 --- a/alerts/src/commonMain/kotlin/Alerts.kt +++ b/alerts/src/commonMain/kotlin/Alerts.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -18,12 +18,9 @@ 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 com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume typealias AlertActionHandler = () -> Unit typealias AlertTextObserver = (String) -> Unit @@ -48,101 +45,36 @@ data class Alert( * Alert style */ enum class Style { - ALERT, ACTION_LIST, TEXT_INPUT - } - - /** - * 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? + /** + * Alert that shows only a title/message and positive/neutral/negative actions. + */ + ALERT, - /** - * Dismisses the alert, which was presented previously - * - * @param animated Pass `true` to animate the transition - */ - fun dismiss(animated: Boolean = true) -} + /** + * Alert that shows a list of [Action] + */ + ACTION_LIST, -/** - * 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 { + /** + * Alert that shows some Text Inout + */ + TEXT_INPUT + } /** - * 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 = Lock() + private var actions: MutableList = mutableListOf() + private var textInputAction: TextInputAction? = null /** * Sets the [title] displayed in the alert * * @param title The title of the alert + * @return The modified [Alert.Builder] */ fun setTitle(title: String?) = apply { this.title = title } @@ -150,40 +82,44 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { * Sets the [message] displayed in the alert * * @param message The message of the alert + * @return The modified [Alert.Builder] */ fun setMessage(message: String?) = apply { this.message = message } /** - * Sets button with the id `BUTTON_POSITIVE` on Android - * and action with style `UIAlertActionStyleDefault` on iOS + * Sets an [Action] with [Action.Style.POSITIVE]. If one already exists it will be replaced. * * @param title The title of the button * @param handler The block to execute after user taps a button + * @return The modified [Alert.Builder] */ fun setPositiveButton(title: String, handler: AlertActionHandler = {}) = apply { - addAction(Alert.Action(title, Alert.Action.Style.POSITIVE, handler)) + this.actions.removeAll { it.style == Action.Style.POSITIVE || it.style == Action.Style.DEFAULT } + addAction(Action(title, Action.Style.POSITIVE, handler)) } /** - * Sets button with the id `BUTTON_NEGATIVE` on Android - * and action with style `UIAlertActionStyleCancel` on iOS + * Sets an [Action] with [Action.Style.NEGATIVE]. If one already exists it will be replaced. * * @param title The title of the button * @param handler The block to execute after user taps a button + * @return The modified [Alert.Builder] */ fun setNegativeButton(title: String, handler: AlertActionHandler = {}) = apply { - addAction(Alert.Action(title, Alert.Action.Style.NEGATIVE, handler)) + this.actions.removeAll { it.style == Action.Style.NEGATIVE || it.style == Action.Style.CANCEL } + addAction(Action(title, Action.Style.NEGATIVE, handler)) } /** - * Sets button with the id `BUTTON_NEUTRAL` on Android - * and action with style `UIAlertActionStyleDestructive` on iOS + * Sets the [Action] with [Action.Style.NEUTRAL]. If one already exists it will be replaced. * * @param title The title of the button * @param handler The block to execute after user taps a button + * @return The modified [Alert.Builder] */ fun setNeutralButton(title: String, handler: AlertActionHandler = {}) = apply { - addAction(Alert.Action(title, Alert.Action.Style.NEUTRAL, handler)) + this.actions.removeAll { it.style == Action.Style.NEUTRAL || it.style == Action.Style.DESTRUCTIVE } + addAction(Action(title, Action.Style.NEUTRAL, handler)) } /** @@ -192,70 +128,65 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { * @param text The initial text of the input field * @param placeholder The input field hint * @param textObserver The callback for text change events of inout field + * @return The modified [Alert.Builder] */ fun setTextInput( text: String? = null, placeholder: String?, textObserver: AlertTextObserver ) = apply { - setTextInputAction(Alert.TextInputAction(text, placeholder, textObserver)) + setTextInputAction(TextInputAction(text, placeholder, textObserver)) } /** - * Adds a list of [actions] to the alert + * Adds a list of [Action] to the alert * - * @param actions The list of action objects + * @param actions The list of [Action] objects + * @return The modified [Alert.Builder] */ - 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 + * Adds a list of [Action] to the alert * - * @param actions The list of action objects + * @param actions The list of [Action] objects + * @return The modified [Alert.Builder] */ - 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 + * Sets a [Style] of the alert * - * @param style The style of an alert + * @param style The [Style] of an alert + * @return The modified [Alert.Builder] */ - 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 + * Adds an [Action] to the alert * * @param action The action object + * @return The modified [Alert.Builder] */ - 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 + * @return The modified [Alert.Builder] */ - 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 + * Creates an [Alert] based on [title], [message], [actions] and [textInputAction] properties * - * @return The alert object + * @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 +194,122 @@ 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 = {} + ) { + /** + * The style of an action. This determines the look of the button when displayed in the alert. + */ + enum class Style(val value: Int) { + /** + * Default button. Synonymous with [POSITIVE] + */ + DEFAULT(0), + + /** + * Positive button. Synonymous with [DEFAULT] + */ + POSITIVE(DEFAULT.value), + + /** + * Destructive button. Synonymous with [NEUTRAL] + */ + DESTRUCTIVE(1), + + /** + * Neutral button. Synonymous with [DESTRUCTIVE] + */ + NEUTRAL(DESTRUCTIVE.value), + + /** + * Cancel button. Synonymous with [NEGATIVE] + */ + CANCEL(2), + + /** + * Negative button. Synonymous with [CANCEL] + */ + 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 used for presenting an [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 until completion + * + * @param animated Pass `true` to animate the presentation + * @return The [Alert.Action] that was performed by button click or `null` if the alert was cancelled by the user. + * Note that some platforms, such as iOS, may not allow the user to cancel the alert. + */ + suspend fun show(animated: Boolean = true): Alert.Action? + + /** + * Dismisses the currently presented [Alert] + * + * @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] + * + * @param alert The [Alert] to present (and dismiss if needed) + */ +abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { + + /** + * Abstract alert builder class, used to create a [BaseAlertPresenter]. + * + * @see [AlertPresenter.Builder] + */ + abstract class Builder : LifecycleSubscribable { /** * 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) { @@ -281,9 +320,18 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { suspendCancellableCoroutine { continuation -> continuation.invokeOnCancellation { dismissAlert(animated) - continuation.resume(null) + continuation.tryResume(null)?.let { + continuation.completeResume(it) + } } - showAlert(animated, afterHandler = { continuation.resume(it) }) + showAlert( + animated, + afterHandler = { action -> + continuation.tryResume(action)?.let { + continuation.completeResume(it) + } + } + ) } override fun dismiss(animated: Boolean) { @@ -300,67 +348,72 @@ abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions { } /** - * Class for presenting an [Alert]. + * Class for presenting an [Alert]. Implementation of [BaseAlertPresenter] */ expect class AlertPresenter : BaseAlertPresenter { + + /** + * A [BaseAlertPresenter.Builder] for creating an [AlertPresenter] + */ class Builder : BaseAlertPresenter.Builder { /** * 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 } } /** - * Builds an alert of type [Alert.Style.ALERT] using DSL syntax (thread safe) + * Builds a [BaseAlertPresenter] of type [Alert.Style.ALERT] using DSL syntax (thread safe) * * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. - * @param initialize The block to construct an Alert - * @return The built alert interface object + * @param initialize The block to construct an [Alert] + * @return The built [BaseAlertPresenter] */ 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) + * Builds a [BaseAlertPresenter] of type [Alert.Style.ACTION_LIST] using DSL syntax (thread safe) * * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. - * @param initialize The block to construct an Alert - * @return The built alert interface object + * @param initialize The block to construct an [Alert] + * @return The built [BaseAlertPresenter] */ 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) + * Builds a [BaseAlertPresenter] of type [Alert.Style.TEXT_INPUT] using DSL syntax (thread safe) * * @param coroutineScope The [CoroutineScope] managing the alert lifecycle. - * @param initialize The block to construct an Alert - * @return The built alert interface object + * @param initialize The block to construct an [Alert] + * @return The built [BaseAlertPresenter] */ 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/commonTest/kotlin/AlertPresenterTests.kt b/alerts/src/commonTest/kotlin/AlertPresenterTests.kt index bd9815028..f18e6855b 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. @@ -16,11 +16,8 @@ Copyright 2020 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.test +package com.splendo.kaluga.alerts -import com.splendo.kaluga.alerts.AlertPresenter -import com.splendo.kaluga.alerts.buildActionSheet -import com.splendo.kaluga.alerts.buildAlert import com.splendo.kaluga.base.runBlocking import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers 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/alerts/src/iosMain/kotlin/AlertPresenter.kt b/alerts/src/iosMain/kotlin/AlertPresenter.kt index d2363b9c6..a0768420e 100644 --- a/alerts/src/iosMain/kotlin/AlertPresenter.kt +++ b/alerts/src/iosMain/kotlin/AlertPresenter.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -36,6 +36,11 @@ import platform.UIKit.UITextField import platform.UIKit.UIViewController import platform.objc.sel_registerName +/** + * A [BaseAlertPresenter] for presenting an [Alert]. + * @param alert The [Alert] being presented. + * @param parent The [UIViewController] to present the [Alert] + */ actual class AlertPresenter( private val alert: Alert, private val parent: UIViewController @@ -74,8 +79,20 @@ actual class AlertPresenter( } } + /** + * A [BaseAlertPresenter.Builder] for creating an [AlertPresenter] + * @param viewController The [UIViewController] to present any [AlertPresenter] built using this builder. + */ actual class Builder(private val viewController: UIViewController) : BaseAlertPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope) = AlertPresenter(createAlert(), viewController) + + /** + * 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] + */ + actual override fun create(alert: Alert, coroutineScope: CoroutineScope) = AlertPresenter(alert, viewController) } override fun dismissAlert(animated: Boolean) { diff --git a/alerts/src/iosTest/kotlin/IOSAlertPresenterTests.kt b/alerts/src/iosTest/kotlin/IOSAlertPresenterTests.kt index dc1c88a14..18df30e94 100644 --- a/alerts/src/iosTest/kotlin/IOSAlertPresenterTests.kt +++ b/alerts/src/iosTest/kotlin/IOSAlertPresenterTests.kt @@ -1,6 +1,5 @@ -package com.splendo.kaluga.test +package com.splendo.kaluga.alerts -import com.splendo.kaluga.alerts.AlertPresenter import platform.UIKit.UIViewController class IOSAlertPresenterTests : AlertPresenterTests() { diff --git a/alerts/src/jsMain/kotlin/AlertPresenter.kt b/alerts/src/jsMain/kotlin/AlertPresenter.kt index ed075a3b3..804313751 100644 --- a/alerts/src/jsMain/kotlin/AlertPresenter.kt +++ b/alerts/src/jsMain/kotlin/AlertPresenter.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -20,14 +20,31 @@ package com.splendo.kaluga.alerts import kotlinx.coroutines.CoroutineScope +/** + * A [BaseAlertPresenter] for presenting an [Alert]. + * + * This is not yet fully implemented on JavaScript + * + * @param alert The [Alert] being presented. + */ actual class AlertPresenter( alert: Alert ) : BaseAlertPresenter(alert) { + /** + * A [BaseAlertPresenter.Builder] for creating an [AlertPresenter] + */ actual class Builder : BaseAlertPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope): AlertPresenter { - return AlertPresenter(createAlert()) + /** + * 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] + */ + 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..d284b8cbd 100644 --- a/alerts/src/jvmMain/kotlin/AlertPresenter.kt +++ b/alerts/src/jvmMain/kotlin/AlertPresenter.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -20,14 +20,31 @@ package com.splendo.kaluga.alerts import kotlinx.coroutines.CoroutineScope +/** + * A [BaseAlertPresenter] for presenting an [Alert]. + * + * This is not yet fully implemented on JVM + * + * @param alert The [Alert] being presented. + */ actual class AlertPresenter( alert: Alert ) : BaseAlertPresenter(alert) { + /** + * A [BaseAlertPresenter.Builder] for creating an [AlertPresenter] + */ actual class Builder : BaseAlertPresenter.Builder() { - actual override fun create(coroutineScope: CoroutineScope): AlertPresenter { - return AlertPresenter(createAlert()) + /** + * 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] + */ + actual override fun create(alert: Alert, coroutineScope: CoroutineScope): AlertPresenter { + return AlertPresenter(alert) } } diff --git a/architecture-compose/README.md b/architecture-compose/README.md index 6aa131ba8..52f9d4b13 100644 --- a/architecture-compose/README.md +++ b/architecture-compose/README.md @@ -1,8 +1,8 @@ -## Architecture-compose -This Android library contains composable functions to work with Kaluga states and androidx navigation. +## Architecture Compose +This Android library contains composable functions to work with Kaluga architecture and androidx navigation. ## Installing -This library is available on Maven Central. You can import Kaluga Base as follows: +This library is available on Maven Central. You can import Kaluga Architecture Compose as follows: ```kotlin repositories { @@ -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 `LifecycleSubscribable` exposed by `BaseLifecycleViewModel.activeLifecycleSubscribables` 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 `LifecycleSubscribable`: `ComposableLifecycleSubscribable`. +The `ComposableLifecycleSubscribable` provides a modifying function to wrap around a composable view. +`ViewModelComposable` will automatically modify its content with all `ComposableLifecycleSubscribable` exposed by `BaseLifecycleViewModel.activeLifecycleSubscribables`. -#### 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/api/architecture-compose.api b/architecture-compose/api/architecture-compose.api index 1ca05cfeb..be19b3b08 100644 --- a/architecture-compose/api/architecture-compose.api +++ b/architecture-compose/api/architecture-compose.api @@ -1,9 +1,73 @@ +public final class com/splendo/kaluga/architecture/compose/ContextExtensionsKt { + public static final fun getActivity (Landroid/content/Context;)Landroidx/appcompat/app/AppCompatActivity; +} + public final class com/splendo/kaluga/architecture/compose/ObservablesKt { public static final fun mutableState (Lcom/splendo/kaluga/architecture/observable/WithMutableState;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/MutableState; public static final fun state (Lcom/splendo/kaluga/architecture/observable/WithState;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; } -public abstract class com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute { +public abstract interface class com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { + public abstract fun getModifier ()Lkotlin/jvm/functions/Function4; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/BottomSheetComposableNavSpec { + public static final field $stable I +} + +public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetContentNavHostComposableNavigator : com/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostComposableNavigator { + public static final field $stable I + public fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetContentRouteController : com/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostRouteController { + public static final field $stable I + public fun (Lkotlinx/coroutines/flow/StateFlow;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;)V + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun provide (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState;)Landroidx/navigation/NavHostController; + public synthetic fun provide (Ljava/lang/Object;)Landroidx/navigation/NavHostController; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetLaunchedNavigation : com/splendo/kaluga/architecture/compose/navigation/BottomSheetComposableNavSpec { + public static final field $stable I + public fun (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation;)V + public final fun component1 ()Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation; + public final fun copy (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetLaunchedNavigation; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetLaunchedNavigation;Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetLaunchedNavigation; + public fun equals (Ljava/lang/Object;)Z + public final fun getLaunchedNavigation ()Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator : com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable, com/splendo/kaluga/architecture/navigation/Navigator { + public static final field $stable I + public synthetic fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + protected final fun SetupNavigationAction (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Landroidx/compose/runtime/Composer;I)V + protected abstract fun getContentModifier ()Lkotlin/jvm/functions/Function4; + public final fun getModifier ()Lkotlin/jvm/functions/Function4; + protected abstract fun getRouteController ()Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRouteController; + public fun navigate (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)V +} + +public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState { + public static final field $stable I + public fun (Landroidx/navigation/NavHostController;Landroidx/navigation/NavHostController;Landroidx/compose/material/ModalBottomSheetState;)V + public final fun component1 ()Landroidx/navigation/NavHostController; + public final fun component2 ()Landroidx/navigation/NavHostController; + public final fun component3 ()Landroidx/compose/material/ModalBottomSheetState; + public final fun copy (Landroidx/navigation/NavHostController;Landroidx/navigation/NavHostController;Landroidx/compose/material/ModalBottomSheetState;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState;Landroidx/navigation/NavHostController;Landroidx/navigation/NavHostController;Landroidx/compose/material/ModalBottomSheetState;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState; + public fun equals (Ljava/lang/Object;)Z + public final fun getContentNavHostController ()Landroidx/navigation/NavHostController; + public final fun getSheetContentNavHostController ()Landroidx/navigation/NavHostController; + public final fun getSheetState ()Landroidx/compose/material/ModalBottomSheetState; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute : com/splendo/kaluga/architecture/compose/navigation/BottomSheetComposableNavSpec { public static final field $stable I } @@ -37,48 +101,152 @@ public final class com/splendo/kaluga/architecture/compose/navigation/BottomShee public final fun navigate (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute;)V } +public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetRouteKt { + public static final fun getBottomSheetComposable (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetLaunchedNavigation; + public static final fun getBottomSheetContent (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute$Content; + public static final fun getBottomSheetSheetContent (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute$SheetContent; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetSheetContentNavHostComposableNavigator : com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator { + public static final field $stable I + public fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun getRouteController ()Lcom/splendo/kaluga/architecture/compose/navigation/RouteController; +} + public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetSheetContentRouteController : com/splendo/kaluga/architecture/compose/navigation/RouteController { public static final field $stable I - public fun (Landroidx/navigation/NavHostController;Landroidx/compose/material/ModalBottomSheetState;Lkotlinx/coroutines/CoroutineScope;)V - public fun back ()Z + public fun (Lkotlinx/coroutines/flow/StateFlow;Lkotlinx/coroutines/flow/StateFlow;)V + public fun AddResultHandlers (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Ljava/util/List;Landroidx/compose/runtime/Composer;I)V + public fun back (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)Z public fun close ()V public fun navigate (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)V } -public final class com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable, com/splendo/kaluga/architecture/navigation/Navigator { +public final class com/splendo/kaluga/architecture/compose/navigation/BottomSheetSheetContentRouteController$NavHostRouteController : com/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostRouteController { + public fun (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetSheetContentRouteController;)V + public fun provide (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigatorState;)Landroidx/navigation/NavHostController; + public synthetic fun provide (Ljava/lang/Object;)Landroidx/navigation/NavHostController; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec { + public static final field $stable I + public static final field Companion Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Companion; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$CloseActivity : com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation { + public static final field $stable I + public fun ()V + public fun (Lkotlin/Pair;)V + public synthetic fun (Lkotlin/Pair;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlin/Pair; + public final fun copy (Lkotlin/Pair;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$CloseActivity; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$CloseActivity;Lkotlin/Pair;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$CloseActivity; + public fun equals (Ljava/lang/Object;)Z + public final fun getResult ()Lkotlin/Pair; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Companion { + public final fun Browser (Ljava/net/URL;Lcom/splendo/kaluga/architecture/navigation/NavigationSpec$Browser$Type;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; + public static synthetic fun Browser$default (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Companion;Ljava/net/URL;Lcom/splendo/kaluga/architecture/navigation/NavigationSpec$Browser$Type;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; + public final fun Messenger (Lcom/splendo/kaluga/architecture/navigation/NavigationSpec$TextMessenger$TextMessengerSettings;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; + public final fun Phone (Ljava/lang/String;Lcom/splendo/kaluga/architecture/navigation/NavigationSpec$Phone$Type;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; + public final fun SendEmail (Lcom/splendo/kaluga/architecture/navigation/NavigationSpec$Email$EmailSettings;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; + public final fun Settings (Lcom/splendo/kaluga/architecture/navigation/NavigationSpec$Settings$Type;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Dialog : com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation { + public static final field $stable I + public fun (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lkotlin/jvm/functions/Function0; + public final fun copy (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Dialog; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Dialog;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Dialog; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreateDialog ()Lkotlin/jvm/functions/Function0; + public final fun getTag ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher : com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation { public static final field $stable I - public fun (Lkotlin/jvm/functions/Function2;)V - public fun getManager ()Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager; + public fun (Lkotlin/jvm/functions/Function3;)V + public final fun component1 ()Lkotlin/jvm/functions/Function3; + public final fun copy (Lkotlin/jvm/functions/Function3;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$IntentLauncher; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreateIntent ()Lkotlin/jvm/functions/Function3; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation : com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec { + public static final field $stable I + public final fun Launch (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V + protected abstract fun createLauncher (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Lkotlin/jvm/functions/Function0; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Launcher : com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$LaunchedNavigation { + public static final field $stable I + public fun (Lkotlin/reflect/KClass;Landroidx/activity/result/contract/ActivityResultContract;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V + public final fun component1 ()Lkotlin/reflect/KClass; + public final fun component2 ()Landroidx/activity/result/contract/ActivityResultContract; + public final fun component3 ()Lkotlin/jvm/functions/Function2; + public final fun component4 ()Lkotlin/jvm/functions/Function2; + public final fun copy (Lkotlin/reflect/KClass;Landroidx/activity/result/contract/ActivityResultContract;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Launcher; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Launcher;Lkotlin/reflect/KClass;Landroidx/activity/result/contract/ActivityResultContract;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$Launcher; + public fun equals (Ljava/lang/Object;)Z + public final fun getActivityResultContract ()Landroidx/activity/result/contract/ActivityResultContract; + public final fun getGetInput ()Lkotlin/jvm/functions/Function2; + public final fun getOnResult ()Lkotlin/jvm/functions/Function2; + public final fun getViewModelClass ()Lkotlin/reflect/KClass; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpecKt { + public static final fun CloseActivity (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;Ljava/lang/Integer;)Lcom/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec$CloseActivity; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator : com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable, com/splendo/kaluga/architecture/navigation/Navigator { + public static final field $stable I + public synthetic fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + protected final fun SetupNavigationAction (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Landroidx/compose/runtime/Composer;I)V + protected abstract fun getContentModifier ()Lkotlin/jvm/functions/Function4; + public final fun getModifier ()Lkotlin/jvm/functions/Function4; + protected abstract fun getRouteController ()Lcom/splendo/kaluga/architecture/compose/navigation/RouteController; public fun navigate (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)V - public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager;)V - public final fun toActivityNavigator (Lkotlin/jvm/functions/Function1;)Lcom/splendo/kaluga/architecture/navigation/ActivityNavigator; - public fun unsubscribe ()V } -public final class com/splendo/kaluga/architecture/compose/navigation/CombinedNavigatorKt { - public static final fun rememberCombinedNavigator (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)Lcom/splendo/kaluga/architecture/navigation/Navigator; +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableSingletons$ComposableNavigatorKt { + public static final field INSTANCE Lcom/splendo/kaluga/architecture/compose/navigation/ComposableSingletons$ComposableNavigatorKt; + public static field lambda-1 Lkotlin/jvm/functions/Function4; + public fun ()V + public final fun getLambda-1$architecture_compose_release ()Lkotlin/jvm/functions/Function4; } -public final class com/splendo/kaluga/architecture/compose/navigation/ComposableSingletons$RouteNavigatorKt { - public static final field INSTANCE Lcom/splendo/kaluga/architecture/compose/navigation/ComposableSingletons$RouteNavigatorKt; - public static field lambda-1 Lkotlin/jvm/functions/Function3; +public final class com/splendo/kaluga/architecture/compose/navigation/ComposableSingletons$SetupNavHostKt { + public static final field INSTANCE Lcom/splendo/kaluga/architecture/compose/navigation/ComposableSingletons$SetupNavHostKt; + public static field lambda-1 Lkotlin/jvm/functions/Function2; + public static field lambda-2 Lkotlin/jvm/functions/Function2; public fun ()V - public final fun getLambda-1$architecture_compose_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-1$architecture_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-2$architecture_compose_release ()Lkotlin/jvm/functions/Function2; } -public final class com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator : com/splendo/kaluga/architecture/navigation/Navigator { +public final class com/splendo/kaluga/architecture/compose/navigation/LaunchedComposableNavigator : com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator { public static final field $stable I - public fun (Landroidx/navigation/NavHostController;Landroidx/navigation/NavHostController;Landroidx/compose/material/ModalBottomSheetState;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)V - public fun (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRouteController;Lkotlin/jvm/functions/Function1;)V - public fun (Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetSheetContentRouteController;Lkotlin/jvm/functions/Function1;)V - public fun navigate (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)V + public fun (Lkotlin/jvm/functions/Function3;)V } -public final class com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigatorKt { - public static final fun NavigatingModalBottomSheetLayout (Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRouteController;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;I)V - public static final fun NavigatingModalBottomSheetLayout (Lcom/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;I)V - public static final fun getBottomSheetContent (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute$Content; - public static final fun getBottomSheetSheetContent (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)Lcom/splendo/kaluga/architecture/compose/navigation/BottomSheetRoute$SheetContent; +public final class com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator : com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator { + public static final field $stable I + public fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class com/splendo/kaluga/architecture/compose/navigation/NavGraphBuilderKt { @@ -86,27 +254,74 @@ public final class com/splendo/kaluga/architecture/compose/navigation/NavGraphBu public static final fun composable (Landroidx/navigation/NavGraphBuilder;Lkotlin/reflect/KClass;Lcom/splendo/kaluga/architecture/navigation/NavigationBundleSpecType;Lkotlin/jvm/functions/Function3;)V } -public final class com/splendo/kaluga/architecture/compose/navigation/NavHostRouteController : com/splendo/kaluga/architecture/compose/navigation/RouteController { +public final class com/splendo/kaluga/architecture/compose/navigation/NavHostComposableNavigator : com/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostComposableNavigator { public static final field $stable I - public fun (Landroidx/navigation/NavHostController;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;)V - public synthetic fun (Landroidx/navigation/NavHostController;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun back ()Z - public fun close ()V - public fun navigate (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)V + public fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Ljava/util/List;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class com/splendo/kaluga/architecture/compose/navigation/NavHostRouteController : com/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostRouteController { + public static final field $stable I + public fun (Lkotlinx/coroutines/flow/StateFlow;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;)V + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun provide (Landroidx/navigation/NavHostController;)Landroidx/navigation/NavHostController; + public synthetic fun provide (Ljava/lang/Object;)Landroidx/navigation/NavHostController; } public final class com/splendo/kaluga/architecture/compose/navigation/NavigationExtensionsKt { public static final fun HardwareBackButtonNavigation (Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V - public static final fun bind (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable;Landroidx/compose/runtime/Composer;I)Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable; + public static final fun bind (Lcom/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable;Landroidx/compose/runtime/Composer;I)Lcom/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostComposableNavigator : com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator { + public static final field $stable I + public synthetic fun (Lcom/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostRouteController;Ljava/util/List;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lcom/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostRouteController;Ljava/util/List;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + protected fun getContentModifier ()Lkotlin/jvm/functions/Function4; + protected fun getRouteController ()Lcom/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostRouteController; + public synthetic fun getRouteController ()Lcom/splendo/kaluga/architecture/compose/navigation/RouteController; +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/ProvidingNavHostRouteController : com/splendo/kaluga/architecture/compose/navigation/RouteController { + public static final field $stable I + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lkotlinx/coroutines/flow/StateFlow;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun AddResultHandlers (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Ljava/util/List;Landroidx/compose/runtime/Composer;I)V + public fun back (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)Z + public fun close ()V + public fun navigate (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)V + public abstract fun provide (Ljava/lang/Object;)Landroidx/navigation/NavHostController; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/RootModalBottomSheetNavigator : com/splendo/kaluga/architecture/compose/navigation/BottomSheetNavigator { + public static final field $stable I + public fun (Lkotlin/jvm/functions/Function3;Landroidx/compose/material/ModalBottomSheetValue;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Ljava/util/List;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/jvm/functions/Function3;Landroidx/compose/material/ModalBottomSheetValue;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Ljava/util/List;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } -public abstract class com/splendo/kaluga/architecture/compose/navigation/Route { +public final class com/splendo/kaluga/architecture/compose/navigation/RootNavHostComposableNavigator : com/splendo/kaluga/architecture/compose/navigation/ComposableNavigator { public static final field $stable I + public fun (Lkotlin/jvm/functions/Function3;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/jvm/functions/Function3;Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public abstract class com/splendo/kaluga/architecture/compose/navigation/Route : com/splendo/kaluga/architecture/compose/navigation/ComposableNavSpec { + public static final field $stable I + public static final field Companion Lcom/splendo/kaluga/architecture/compose/navigation/Route$Companion; } public final class com/splendo/kaluga/architecture/compose/navigation/Route$Back : com/splendo/kaluga/architecture/compose/navigation/Route { public static final field $stable I - public static final field INSTANCE Lcom/splendo/kaluga/architecture/compose/navigation/Route$Back; + public fun ()V + public fun (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)V + public synthetic fun (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result; + public final fun copy (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$Back; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Back;Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$Back; + public fun equals (Ljava/lang/Object;)Z + public final fun getResult ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class com/splendo/kaluga/architecture/compose/navigation/Route$Close : com/splendo/kaluga/architecture/compose/navigation/Route { @@ -114,6 +329,11 @@ public final class com/splendo/kaluga/architecture/compose/navigation/Route$Clos public static final field INSTANCE Lcom/splendo/kaluga/architecture/compose/navigation/Route$Close; } +public final class com/splendo/kaluga/architecture/compose/navigation/Route$Companion { + public final fun getBack ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$Back; + public final fun getPopToRoot ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopToRoot; +} + public final class com/splendo/kaluga/architecture/compose/navigation/Route$FromRoute : com/splendo/kaluga/architecture/compose/navigation/Route$Navigate { public static final field $stable I public fun (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;Ljava/lang/String;)V @@ -144,10 +364,13 @@ public final class com/splendo/kaluga/architecture/compose/navigation/Route$Next public final class com/splendo/kaluga/architecture/compose/navigation/Route$PopTo : com/splendo/kaluga/architecture/compose/navigation/Route$Navigate { public static final field $stable I - public fun (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)V - public final fun copy (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopTo; - public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopTo;Lcom/splendo/kaluga/architecture/navigation/NavigationAction;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopTo; + public fun (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)V + public synthetic fun (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component2 ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result; + public final fun copy (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopTo; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopTo;Lcom/splendo/kaluga/architecture/navigation/NavigationAction;Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopTo; public fun equals (Ljava/lang/Object;)Z + public final fun getResult ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -164,7 +387,16 @@ public final class com/splendo/kaluga/architecture/compose/navigation/Route$PopT public final class com/splendo/kaluga/architecture/compose/navigation/Route$PopToRoot : com/splendo/kaluga/architecture/compose/navigation/Route { public static final field $stable I - public static final field INSTANCE Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopToRoot; + public fun ()V + public fun (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)V + public synthetic fun (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result; + public final fun copy (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopToRoot; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopToRoot;Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopToRoot; + public fun equals (Ljava/lang/Object;)Z + public final fun getResult ()Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class com/splendo/kaluga/architecture/compose/navigation/Route$Replace : com/splendo/kaluga/architecture/compose/navigation/Route$Navigate { @@ -177,8 +409,35 @@ public final class com/splendo/kaluga/architecture/compose/navigation/Route$Repl public fun toString ()Ljava/lang/String; } +public abstract class com/splendo/kaluga/architecture/compose/navigation/Route$Result { + public static final field $stable I + public static final field Companion Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result$Companion; + public static final field KEY Ljava/lang/String; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/Route$Result$Companion { +} + +public final class com/splendo/kaluga/architecture/compose/navigation/Route$Result$Data : com/splendo/kaluga/architecture/compose/navigation/Route$Result { + public static final field $stable I + public fun (Lcom/splendo/kaluga/architecture/navigation/NavigationBundle;)V + public final fun component1 ()Lcom/splendo/kaluga/architecture/navigation/NavigationBundle; + public final fun copy (Lcom/splendo/kaluga/architecture/navigation/NavigationBundle;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result$Data; + public static synthetic fun copy$default (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result$Data;Lcom/splendo/kaluga/architecture/navigation/NavigationBundle;ILjava/lang/Object;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getBundle ()Lcom/splendo/kaluga/architecture/navigation/NavigationBundle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/splendo/kaluga/architecture/compose/navigation/Route$Result$Empty : com/splendo/kaluga/architecture/compose/navigation/Route$Result { + public static final field $stable I + public static final field INSTANCE Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result$Empty; +} + public abstract interface class com/splendo/kaluga/architecture/compose/navigation/RouteController { - public abstract fun back ()Z + public abstract fun AddResultHandlers (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Ljava/util/List;Landroidx/compose/runtime/Composer;I)V + public abstract fun back (Lcom/splendo/kaluga/architecture/compose/navigation/Route$Result;)Z public abstract fun close ()V public abstract fun navigate (Lcom/splendo/kaluga/architecture/compose/navigation/Route;)V } @@ -186,6 +445,7 @@ public abstract interface class com/splendo/kaluga/architecture/compose/navigati public final class com/splendo/kaluga/architecture/compose/navigation/RouteKt { public static final fun from (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;Ljava/lang/String;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$FromRoute; public static final fun getArgumentKey (Lcom/splendo/kaluga/architecture/navigation/NavigationBundleSpecRow;)Ljava/lang/String; + public static final fun getBack (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$Back; public static final fun getFromRoot (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$FromRoute; public static final fun getNext (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$NextRoute; public static final fun getPopTo (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)Lcom/splendo/kaluga/architecture/compose/navigation/Route$PopTo; @@ -195,26 +455,75 @@ public final class com/splendo/kaluga/architecture/compose/navigation/RouteKt { public static final fun route (Lkotlin/reflect/KClass;Lcom/splendo/kaluga/architecture/navigation/NavigationBundleSpec;)Ljava/lang/String; } -public class com/splendo/kaluga/architecture/compose/navigation/RouteNavigator : com/splendo/kaluga/architecture/navigation/Navigator { - public static final field $stable I - public fun (Landroidx/navigation/NavHostController;Lkotlin/jvm/functions/Function1;)V - public fun (Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/functions/Function1;)V - public fun navigate (Lcom/splendo/kaluga/architecture/navigation/NavigationAction;)V +public final class com/splendo/kaluga/architecture/compose/navigation/SetupNavHostKt { + public static final fun SetupNavHost (Landroidx/navigation/NavHostController;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V + public static final fun SetupNavHost (Lkotlinx/coroutines/flow/StateFlow;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun SetupNavHost (Lkotlinx/coroutines/flow/StateFlow;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } -public final class com/splendo/kaluga/architecture/compose/navigation/RouteNavigatorKt { - public static final fun SetupNavHost (Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V - public static final fun SetupNavHost (Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V - public static final fun SetupNavHost (Lcom/splendo/kaluga/architecture/compose/navigation/RouteController;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V - public static final fun SetupNavHost (Lcom/splendo/kaluga/architecture/compose/navigation/RouteNavigator;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V - public static final fun SetupNavHost (Lcom/splendo/kaluga/architecture/compose/navigation/RouteNavigator;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V - public static final fun SetupNavHost (Lcom/splendo/kaluga/architecture/compose/navigation/RouteNavigator;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V +public final class com/splendo/kaluga/architecture/compose/navigation/result/HandleResultKt { + public static final fun HandleBooleanArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleBooleanArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleBooleanOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleBooleanResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleByteArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleByteArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleByteOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleByteResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleCharArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleCharArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleCharOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleCharResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleCharSequenceOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleCharSequenceResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDateArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDateArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDateOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDateResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDoubleArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDoubleArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDoubleOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleDoubleResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleFloatArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleFloatArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleFloatOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleFloatResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleIntArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleIntArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleIntOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleIntResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleLongArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleLongArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleLongOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleLongResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleNullableResultOrNull (Landroidx/navigation/NavHostController;Lcom/splendo/kaluga/architecture/navigation/NavigationBundleSpecType;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleResult (Landroidx/navigation/NavHostController;Lcom/splendo/kaluga/architecture/navigation/NavigationBundleSpec;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleResult (Landroidx/navigation/NavHostController;Lcom/splendo/kaluga/architecture/navigation/NavigationBundleSpecType;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleResultOfNullableTypeOrNull (Landroidx/navigation/NavHostController;Lkotlinx/serialization/KSerializer;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleResultOfType (Landroidx/navigation/NavHostController;Lkotlinx/serialization/KSerializer;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleResultOfTypeOrNull (Landroidx/navigation/NavHostController;Lkotlinx/serialization/KSerializer;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleResultOrNull (Landroidx/navigation/NavHostController;Lcom/splendo/kaluga/architecture/navigation/NavigationBundleSpecType;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleShortArrayOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleShortArrayResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleShortOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleShortResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleStringListOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleStringListResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleStringOrNullResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun HandleStringResult (Landroidx/navigation/NavHostController;ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V +} + +public final class com/splendo/kaluga/architecture/compose/navigation/result/NavHostResultHandler { + public static final field $stable I + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;)V } -public abstract class com/splendo/kaluga/architecture/compose/viewModel/KalugaViewModelComposeActivity : com/splendo/kaluga/architecture/viewmodel/KalugaViewModelActivity { +public abstract class com/splendo/kaluga/architecture/compose/viewModel/KalugaViewModelComposeActivity : androidx/appcompat/app/AppCompatActivity { public static final field $stable I public fun ()V protected abstract fun Layout (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Landroidx/compose/runtime/Composer;I)V + protected fun RootView (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V + protected abstract fun createViewModel (Landroidx/compose/runtime/Composer;I)Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel; protected fun onCreate (Landroid/os/Bundle;)V } @@ -223,6 +532,7 @@ public final class com/splendo/kaluga/architecture/compose/viewModel/KalugaViewM } public final class com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposableKt { + public static final fun FragmentViewModelComposable (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Landroidx/fragment/app/FragmentManager;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V public static final fun ViewModelComposable (Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V public static final fun store (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel; public static final fun storeAndRemember (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Lcom/splendo/kaluga/architecture/viewmodel/BaseLifecycleViewModel; diff --git a/architecture-compose/build.gradle.kts b/architecture-compose/build.gradle.kts index a40fd2716..49a40415c 100644 --- a/architecture-compose/build.gradle.kts +++ b/architecture-compose/build.gradle.kts @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -21,28 +21,17 @@ plugins { kotlin("plugin.serialization") id("jacoco") id("convention.publication") + id("org.jetbrains.dokka") 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 { 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"]}!!") + 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/navigation/CombinedNavigator.kt b/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt deleted file mode 100644 index 9fc5507f0..000000000 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/CombinedNavigator.kt +++ /dev/null @@ -1,59 +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.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 - -/** 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 deleted file mode 100644 index 5824e9590..000000000 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/ModalBottomSheetNavigator.kt +++ /dev/null @@ -1,196 +0,0 @@ -package com.splendo.kaluga.architecture.compose.navigation - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.material.ModalBottomSheetLayout -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController -import com.splendo.kaluga.architecture.navigation.NavigationAction -import com.splendo.kaluga.architecture.navigation.Navigator -import kotlinx.coroutines.CoroutineScope -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) - } -} - -/** - * 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 -) : RouteController { - - private val sheetContentRouteController = NavHostRouteController(navHostController, this) - - override fun navigate(newRoute: Route) { - if (!sheetState.isVisible) { - coroutineScope.launch { - sheetState.animateTo(ModalBottomSheetValue.Expanded) - } - } - sheetContentRouteController.navigate(newRoute) - } - - override fun back(): Boolean = navHostController.popBackStack() || kotlin.run { - close() - true - } - - override fun close() { - coroutineScope.launch { - 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, - 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 - ) - - constructor( - contentRouteController: RouteController, - sheetContentRouteController: BottomSheetSheetContentRouteController, - navigationMapper: (A) -> BottomSheetRoute - ) : this( - BottomSheetRouteController(contentRouteController, sheetContentRouteController), - navigationMapper - ) - - override fun navigate(action: A) = routeController.navigate(navigationMapper(action)) -} - -typealias BottomSheetRootContentBuilder = (contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) -> Unit -typealias BottomSheetContentBuilder = NavGraphBuilder.(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) -> 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 -fun > ModalBottomSheetNavigator.NavigatingModalBottomSheetLayout( - 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 -) = ModalBottomSheetLayout( - sheetContent = { - if (sheetContentRouteController.sheetState.isVisible) { - HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close) - } - Box(Modifier.defaultMinSize(minHeight = 1.dp)) { - sheetContentRouteController.SetupNavHost { sheetContentNavHostController -> - sheetContent( - contentRouteController.navHostController, - sheetContentNavHostController, - sheetContentRouteController.sheetState - ) - } - } - }, - sheetState = sheetContentRouteController.sheetState, -) { - contentRouteController.SetupNavHost( - rootView = { navHostController -> - contentRoot( - navHostController, - sheetContentRouteController.navHostController, - sheetContentRouteController.sheetState - ) - } - ) { navHostController -> - content( - navHostController, - sheetContentRouteController.navHostController, - sheetContentRouteController.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 deleted file mode 100644 index 9ad98e38a..000000000 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/navigation/RouteNavigator.kt +++ /dev/null @@ -1,177 +0,0 @@ -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.ui.Modifier -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.Navigator - -/** - * 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(): Boolean - - /** - * Closes this 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 parentRouteController The [NavHostController] that is a parent to the [navHostController] if it exists - */ -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) { - 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.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.Close -> close() - } - } - - override fun back(): Boolean = navHostController.popBackStack() || parentRouteController?.back() ?: false - override fun close() { - navHostController.popBackStack(ROOT_VIEW, true) - parentRouteController?.close() - } -} - -typealias RouteRootContentBuilder = (NavHostController) -> Unit -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, - private val navigationMapper: (A) -> Route -) : Navigator { - - constructor( - navHostController: NavHostController, - navigationMapper: (A) -> Route - ) : this(NavHostRouteController(navHostController), navigationMapper) - - 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) - -/** - * 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) - } -} - -/** - * 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, - builder: RouteContentBuilder -) = routeController.SetupNavHost(rootView, 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, - builder: RouteContentBuilder -) { - SetupNavHost(startDestination = ROOT_VIEW) { - composable(ROOT_VIEW, content = { rootView(navHostController) }) - 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( - startDestination: String, - builder: NavGraphBuilder.(RouteController) -> Unit -) { - NavHost( - navController = navHostController, - startDestination = startDestination, - builder = { builder(this@SetupNavHost) } - ) -} 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 deleted file mode 100644 index 95904cfad..000000000 --- a/architecture-compose/src/androidLibMain/kotlin/com/splendo/kaluga/architecture/compose/viewModel/ViewModelComposable.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.splendo.kaluga.architecture.compose.viewModel - -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.LocalLifecycleOwner -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.viewmodel.BaseLifecycleViewModel - -/** - * Composable which manages [viewModel] lifecycle and optionally adds it to local [ViewModelStore]. - * @param viewModel view model to manage - * @param content content based on [viewModel] - */ -@Composable -fun ViewModelComposable( - viewModel: ViewModel, - content: @Composable (ViewModel.() -> Unit)? = null -) { - viewModel.linkLifecycle() - 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 = - provider().also { handleLocalViewModelStore(it) } - -/** - * Stores and remembers a view model in the local [ViewModelStore]. - * Use if the view model was created manually and is not located in Activity/Fragment [ViewModelStore]. - * provider will only be evaluated during the composition. Recomposition will always return the value produced by provider. - */ -@Composable -fun storeAndRemember(provider: @DisallowComposableCalls () -> VM): VM = store { - remember(provider) -} - -/** - * Stores and remembers a view model in the local [ViewModelStore]. - * Use if the view model was created manually and is not located in Activity/Fragment [ViewModelStore]. - * provider will only be evaluated during the composition. Recomposition will always return the value produced by provider. - */ -@Composable -fun storeAndRemember(key: Any?, provider: @DisallowComposableCalls () -> VM): VM = store { - remember(key, provider) -} - -@Composable -private fun handleLocalViewModelStore(viewModel: VM): VM { - // we delegate VM cleanup to the ViewModelStore, which lives in scope of the current @Composable - val viewModelStoreOwner = rememberComposableViewModelStoreOwner(viewModel) - - // ViewModelProvider is the one, who can access ViewModelStore.put() - val viewModelProvider = remember(viewModelStoreOwner) { - ViewModelProvider( - viewModelStoreOwner, - @Suppress("UNCHECKED_CAST") - object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T = - viewModel as T - } - ) - } - // actual injection of the VM into the ViewModelStore - viewModelProvider.get(viewModel::class.java) - - return viewModel -} - -@Composable -private fun rememberComposableViewModelStoreOwner(viewModel: BaseLifecycleViewModel): ViewModelStoreOwner { - val viewModelStoreOwner = remember(viewModel) { - val viewModelStore = ViewModelStore() - ViewModelStoreOwner { viewModelStore } - } - DisposableEffect(Unit) { - onDispose { - viewModelStoreOwner.viewModelStore.clear() - } - } - - return viewModelStoreOwner -} - -@Composable -private fun VM.linkLifecycle(): VM { - val lifecycle = LocalLifecycleOwner.current.lifecycle - DisposableEffect(Unit) { - val observer = VmObserver(this@linkLifecycle) - - lifecycle.addObserver(observer) - - onDispose { - lifecycle.removeObserver(observer) - observer.onDispose() - } - } - return this -} - -private class VmObserver(private val viewModel: VM) : DefaultLifecycleObserver { - private var resumed = false - - override fun onResume(owner: LifecycleOwner) { - viewModel.didResume().also { resumed = true } - } - override fun onPause(owner: LifecycleOwner) { - viewModel.didPause().also { resumed = false } - } - - fun onDispose() { - if (resumed) { - viewModel.didPause() - } - } -} diff --git a/architecture-compose/src/androidLibAndroidTest/AndroidManifest.xml b/architecture-compose/src/androidTest/AndroidManifest.xml similarity index 93% rename from architecture-compose/src/androidLibAndroidTest/AndroidManifest.xml rename to architecture-compose/src/androidTest/AndroidManifest.xml index 0fdb72725..1e3bb4393 100644 --- a/architecture-compose/src/androidLibAndroidTest/AndroidManifest.xml +++ b/architecture-compose/src/androidTest/AndroidManifest.xml @@ -1,5 +1,5 @@ @@ -44,8 +46,9 @@ android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> + android:label="Features list" + android:exported="true" + android:screenOrientation="fullSensor"> @@ -53,22 +56,58 @@ - - + android:label="@string/feature_location" + android:screenOrientation="fullSensor"/> + + + android:label="@string/feature_alerts" + android:screenOrientation="fullSensor"/> + + + + android:label="@string/feature_date_time_picker" + android:screenOrientation="fullSensor"/> + + - + android:label="@string/feature_hud" + android:screenOrientation="fullSensor"/> + + + + + + android:launchMode="singleTask" + android:screenOrientation="fullSensor"> @@ -79,34 +118,61 @@ android:pathPrefix="/kalugaexample"/> - - - - - - - + + + + + + android:label="@string/feature_bluetooth" + android:screenOrientation="fullSensor"/> + android:label="@string/feature_bluetooth" + android:screenOrientation="fullSensor"/> + android:label="@string/feature_beacons" + android:screenOrientation="fullSensor"/> - - - - - + android:label="@string/feature_resources" + android:screenOrientation="fullSensor"/> + + + + + + + + + + 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 3fa0df9a9..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 @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -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/ExampleApplication.kt b/example/android/src/main/java/com/splendo/kaluga/example/ExampleApplication.kt index 49508dd50..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 @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -20,10 +20,17 @@ 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.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() { @@ -31,9 +38,17 @@ class ExampleApplication : Application() { super.onCreate() ApplicationHolder.application = this - startKoin { - androidContext(this@ExampleApplication) - modules(utilitiesModule, viewModelModule) - } + 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/alerts/AlertsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/alerts/AlertsActivity.kt index 1802f310a..93f6aa5b0 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,24 @@ /* + Copyright 2023 Splendo Consulting B.V. The Netherlands -Copyright 2019 Splendo Consulting B.V. The Netherlands + Licensed under the Apache License, Version 2.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.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.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..addbf5c0d --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/compose/AlertsLayout.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.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.accompanist.themeadapter.material.MdcTheme +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +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 + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider { + AlertsLayout() + } + } + } +} + +@Composable +fun AlertsLayout() { + MdcTheme { + val viewModel = koinViewModel() + + 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()) + } + } + } +} 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..9a0d5c405 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/alerts/xml/XMLAlertsActivity.kt @@ -0,0 +1,41 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.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) + } +} 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 new file mode 100644 index 000000000..a1832ff92 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/ArchitectureActivity.kt @@ -0,0 +1,30 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 com.splendo.kaluga.example.architecture.compose.ComposeArchitectureActivity +import com.splendo.kaluga.example.architecture.xml.XMLArchitectureActivity +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity + +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 new file mode 100644 index 000000000..59ece7342 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureDetailsLayout.kt @@ -0,0 +1,62 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 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 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 +import kotlinx.coroutines.flow.StateFlow +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf + +@Composable +fun ArchitectureDetailsLayout(inputDetails: InputDetails, bottomSheetNavigatorState: StateFlow) { + val viewModel = koinViewModel { + parametersOf( + inputDetails, + BottomSheetContentNavHostComposableNavigator>( + bottomSheetNavigatorState, + navigationMapper = { architectureDetailsNavigationRouteMapper(it) } + ) + ) + } + + ViewModelComposable(viewModel) { + val nameText by name.state() + val numberText by number.state() + HardwareBackButtonNavigation(onBackButtonClickHandler = { onBackPressed() }) + Column(Modifier.fillMaxWidth()) { + Text(nameText) + Text(numberText) + inverseButton.Composable(modifier = Modifier.fillMaxWidth()) + finishButton.Composable(modifier = Modifier.fillMaxWidth()) + } + } +} 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 new file mode 100644 index 000000000..78bd2f4d6 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/ArchitectureLayout.kt @@ -0,0 +1,168 @@ +/* + +Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.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.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +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.compose.composable +import com.google.accompanist.themeadapter.material.MdcTheme +import com.splendo.kaluga.architecture.compose.mutableState +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 +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +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 +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() { + + @SuppressLint("MissingSuperCall") // Lint bug + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider( + LocalAppCompatActivity provides this + ) { + ArchitectureLayout() + } + } + } +} + +@Composable +fun ArchitectureLayout() { + MdcTheme { + val viewModel = koinViewModel { + parametersOf( + RootModalBottomSheetNavigator>( + navigationMapper = { architectureNavigationRouteMapper(it) }, + contentRootResultHandlers = listOf( + InputDetails.serializer().NavHostResultHandler { + nameInput.post(it.name) + numberInput.post(it.number.toString()) + } + ), + contentBuilder = { bottomSheetNavigationState -> + composable( + InputDetails.serializer() + ) { inputDetails -> + ArchitectureDetailsLayout(inputDetails, bottomSheetNavigationState) + } + }, + sheetContentBuilder = { bottomSheetNavigationState -> + composable(ArchitectureNavigationAction.BottomSheet.route()) { + BottomSheetLayout( + bottomSheetNavigationState + ) + } + composable(BottomSheetNavigation.SubPage.route()) { + BottomSheetSubPageLayout( + bottomSheetNavigationState + ) + } + } + ) + ) + } + + ViewModelComposable(viewModel) { + val nameInput = nameInput.mutableState() + val isNameValid by isNameValid.state() + val numberInput = numberInput.mutableState() + val isNumberValid by isNumberValid.state() + + val focusManager = LocalFocusManager.current + + 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()) + } + } + } +} 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/architecture/compose/BottomSheetLayout.kt similarity index 50% rename from example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetLayout.kt index 2295d5e90..b597575f4 100644 --- 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/architecture/compose/BottomSheetLayout.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -15,41 +15,35 @@ */ -package com.splendo.kaluga.example.platformspecific.compose.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.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.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.platformspecific.compose.bottomSheet.BottomSheetViewModel +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) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetNavigationRouteMapper, - ) - - val viewModel = store { - remember { - BottomSheetViewModel(navigator) - } +fun BottomSheetLayout(bottomSheetNavigationState: StateFlow) { + val viewModel = koinViewModel { + parametersOf( + ModalBottomSheetNavigator( + bottomSheetNavigationState, + navigationMapper = { bottomSheetNavigationRouteMapper(it) } + ) + ) } ViewModelComposable(viewModel) { @@ -57,17 +51,14 @@ fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentN Column( Modifier .fillMaxWidth() - .padding(Padding.default) + .padding(Constants.Padding.default) ) { Text(text) - Button( + button.Composable( modifier = Modifier .fillMaxWidth() - .padding(Padding.default), - onClick = { onSubPagePressed() } - ) { - Text(buttonText) - } + .padding(Constants.Padding.default) + ) } } } 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/architecture/compose/BottomSheetSubPageLayout.kt similarity index 58% rename from example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetSubPageLayout.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/BottomSheetSubPageLayout.kt index c4ff4c8c6..6eee11da9 100644 --- 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/architecture/compose/BottomSheetSubPageLayout.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -15,7 +15,7 @@ */ -package com.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui +package com.splendo.kaluga.example.architecture.compose import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -23,39 +23,31 @@ 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.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.platformspecific.compose.bottomSheet.BottomSheetSubPageViewModel +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 +import org.koin.core.parameter.parametersOf @Composable fun BottomSheetSubPageLayout( - contentNavHostController: NavHostController, - sheetContentNavHostController: NavHostController, - sheetState: ModalBottomSheetState + bottomSheetNavigationState: StateFlow ) { - val navigator = ModalBottomSheetNavigator( - contentNavHostController, - sheetContentNavHostController, - sheetState, - rememberCoroutineScope(), - ::bottomSheetSubPageNavigationRouteMapper - ) - - val viewModel = store { - remember { - BottomSheetSubPageViewModel(navigator) - } + val viewModel = koinViewModel { + parametersOf( + ModalBottomSheetNavigator( + bottomSheetNavigationState, + navigationMapper = { bottomSheetSubPageNavigationRouteMapper(it) } + ) + ) } ViewModelComposable(viewModel) { @@ -63,11 +55,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/platformspecific/compose/bottomSheet/viewModel/NavigationMappers.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt similarity index 59% rename from example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/viewModel/NavigationMappers.kt rename to example/android/src/main/java/com/splendo/kaluga/example/architecture/compose/NavigationMappers.kt index 8eeb466b7..ae73898ec 100644 --- 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/architecture/compose/NavigationMappers.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -15,30 +15,32 @@ */ -package com.splendo.kaluga.example.platformspecific.compose.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 +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 -import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentSubPageNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.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 -> action.back + is ArchitectureDetailsNavigationAction.Close -> action.back } } @@ -54,6 +56,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/architecture/ArchitectureDetailsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/ArchitectureDetailsActivity.kt similarity index 61% 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/xml/ArchitectureDetailsActivity.kt index 61461a3ac..b0a98ea15 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/xml/ArchitectureDetailsActivity.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -16,13 +16,15 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.architecture +package com.splendo.kaluga.example.architecture.xml import android.os.Bundle -import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType -import com.splendo.kaluga.architecture.navigation.toTypedProperty +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.parseTypeOfOrNull 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.InputDetails import org.koin.androidx.viewmodel.ext.android.viewModel @@ -35,9 +37,18 @@ class ArchitectureDetailsActivity : KalugaViewModelActivity - parametersOf(details) + parseTypeOfOrNull(InputDetails.serializer())?.let { details -> + parametersOf( + details, + ActivityNavigator> { action -> + when (action) { + is ArchitectureDetailsNavigationAction.Close -> NavigationSpec.Close() + is ArchitectureDetailsNavigationAction.FinishWithDetails -> NavigationSpec.Close( + resultCode + ) + } + } + ) } ?: parametersOf("", 0) } @@ -51,6 +62,6 @@ class ArchitectureDetailsActivity : KalugaViewModelActivity() { + + 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(ActivityLifecycleSubscribable.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/ArchitectureInputActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/architecture/xml/XMLArchitectureActivity.kt similarity index 63% 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/xml/XMLArchitectureActivity.kt index 8415c1920..ebca09170 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/xml/XMLArchitectureActivity.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -16,42 +16,46 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.architecture +package com.splendo.kaluga.example.architecture.xml 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.navigation.parseTypeOfOrNull 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 org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -class ArchitectureInputActivity : KalugaViewModelActivity() { +class XMLArchitectureActivity : KalugaViewModelActivity() { - 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.parseTypeOfOrNull(InputDetails.serializer()) + } } - override val viewModel: ArchitectureInputViewModel by viewModel { + override val viewModel: ArchitectureViewModel by viewModel { parametersOf( - ActivityNavigator> { - 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 -> NavigationSpec.Dialog(BottomSheetRootDialogFragment.TAG) { BottomSheetRootDialogFragment() } + } } ) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt index 440d46f49..b1d59b78b 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsActivity.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/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 efedcb307..76ede9720 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 @@ -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. @@ -19,11 +19,21 @@ 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 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() + } +} + class BeaconsAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter() { class BeaconItemViewHolder(val binding: BeaconItemBinding) : RecyclerView.ViewHolder(binding.root) 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/beacons/BeaconsBinding.kt deleted file mode 100644 index 799281e7d..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/beacons/BeaconsBinding.kt +++ /dev/null @@ -1,31 +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. - - */ - -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/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 4502387ac..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 @@ -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. @@ -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/BluetoothAdapter.kt b/example/android/src/main/java/com/splendo/kaluga/example/bluetooth/BluetoothAdapter.kt index d1eca3594..87c59d40f 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 @@ -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. @@ -25,8 +25,7 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListDeviceViewModel -object DevicesBinding { - +object DeviceBinding { @BindingAdapter("devices") @JvmStatic fun bindDevices(view: RecyclerView, devices: List?) { 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 f35df9e2e..37c404dff 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 @@ -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. @@ -25,8 +25,7 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothCharacteristicItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothCharacteristicViewModel -object CharacteristicsBinding { - +object CharacteristicBinding { @BindingAdapter("characteristics") @JvmStatic fun bindCharacteristics(view: RecyclerView, characteristics: List?) { 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 cf97781b6..5c331c8b8 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 @@ -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. @@ -25,8 +25,7 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothDescriptorItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDescriptorViewModel -object DescriptorsBinding { - +object DescriptorBinding { @BindingAdapter("descriptors") @JvmStatic fun bindDescriptors(view: RecyclerView, descriptors: List?) { 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 76f2854d3..e6b99c767 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 @@ -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. @@ -18,22 +18,20 @@ package com.splendo.kaluga.example.bluetooth import android.os.Bundle -import com.splendo.kaluga.architecture.navigation.toNavigationBundle +import com.splendo.kaluga.architecture.navigation.parseTypeOf 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( + parseTypeOf(SerializableIdentifier.serializer()).identifier + ) } override fun onCreate(savedInstanceState: Bundle?) { 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 5c5ce6ba3..956d1477f 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 @@ -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. @@ -25,8 +25,7 @@ import androidx.recyclerview.widget.RecyclerView import com.splendo.kaluga.example.databinding.BluetoothServiceItemBinding import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothServiceViewModel -object ServicesBinding { - +object ServiceBinding { @BindingAdapter("services") @JvmStatic fun bindServices(view: RecyclerView, services: List?) { 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..0613da812 --- /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) + } +} + +object UITypeBinding { + @BindingAdapter("uiTypes") + @JvmStatic + fun bindUITypes(view: RecyclerView, uiTypes: List?) { + val adapter = (view.adapter as? UITypesAdapter) ?: return + adapter.uiTypes = uiTypes.orEmpty() + } +} + +class UITypesAdapter(private val viewModel: ComposeOrXMLSelectionViewModel) : RecyclerView.Adapter() { + + 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) + } + } +} diff --git a/test-utils-keyboard/src/androidLibMain/kotlin/MockFocusHandler.kt b/example/android/src/main/java/com/splendo/kaluga/example/compose/Constants.kt similarity index 59% rename from test-utils-keyboard/src/androidLibMain/kotlin/MockFocusHandler.kt rename to example/android/src/main/java/com/splendo/kaluga/example/compose/Constants.kt index db83ac39d..3d8fafd3d 100644 --- a/test-utils-keyboard/src/androidLibMain/kotlin/MockFocusHandler.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/compose/Constants.kt @@ -15,21 +15,23 @@ */ -package com.splendo.kaluga.test.keyboard +package com.splendo.kaluga.example.compose -import android.app.Activity -import com.splendo.kaluga.keyboard.FocusHandler +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 -actual class MockFocusHandler : BaseMockFocusHandler(), FocusHandler { - override fun requestFocus(activity: Activity?) { - super.giveFocus() - } +object Constants { - actual fun simulateGiveFocus() { - requestFocus(activity = null) + object Padding { + val default = 8.dp + val x2 = 16.dp } - actual fun simulateRemoveFocus() { - super.removeFocus() + @Composable + fun DefaultSpacer() { + Spacer(modifier = Modifier.size(Padding.default)) } } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/datetime/TimerActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/datetime/TimerActivity.kt new file mode 100644 index 000000000..69cfb4eb4 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetime/TimerActivity.kt @@ -0,0 +1,38 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.datetime + +import android.os.Bundle +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityTimerBinding +import com.splendo.kaluga.example.shared.viewmodel.datetime.TimerViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +class TimerActivity : KalugaViewModelActivity() { + + override val viewModel: TimerViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityTimerBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + binding.viewModel = viewModel + binding.lifecycleOwner = this + } +} 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..3a184ff7c --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/datetimepicker/compose/DateTimePickerLayout.kt @@ -0,0 +1,82 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.accompanist.themeadapter.material.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.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 + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider( + LocalAppCompatActivity provides this + ) { + DateTimePickerLayout() + } + } + } +} + +@Composable +fun DateTimePickerLayout() { + MdcTheme { + val viewModel = koinViewModel() + + 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) + } + } + } +} 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..7eacb2047 --- /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 + } +} 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 7a20aa8d9..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/di/Modules.kt +++ /dev/null @@ -1,216 +0,0 @@ -/* - -Copyright 2019 Splendo Consulting B.V. The Netherlands - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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 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(), timeoutMs = 60_000) } -} - -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/FeaturesListFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/featurelist/FeaturesListFragment.kt similarity index 63% 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 292c65eba..d179b36fa 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,44 +1,46 @@ /* + Copyright 2022 Splendo Consulting B.V. The Netherlands -Copyright 2019 Splendo Consulting B.V. The Netherlands + Licensed under the Apache License, Version 2.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 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.alerts.AlertsActivity -import com.splendo.kaluga.example.architecture.ArchitectureInputActivity +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.datetime.TimerActivity 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.platformspecific.PlatformSpecificActivity +import com.splendo.kaluga.example.permissions.PermissionsListActivity import com.splendo.kaluga.example.resources.ResourcesActivity +import com.splendo.kaluga.example.scientific.ScientificActivity 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 @@ -46,38 +48,53 @@ 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.DateTime -> NavigationSpec.Activity() FeatureListNavigationAction.DateTimePicker -> NavigationSpec.Activity() FeatureListNavigationAction.LoadingIndicator -> NavigationSpec.Activity() - FeatureListNavigationAction.Architecture -> NavigationSpec.Activity() - FeatureListNavigationAction.Keyboard -> 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.Scientific -> NavigationSpec.Activity() + FeatureListNavigationAction.PlatformSpecific -> throw java.lang.RuntimeException("Not supported") } } ) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val adapter = FeaturesAdapter(viewModel).apply { + 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 + } +} - view.findViewById(R.id.features_list).adapter = this - } - viewModel.feature.observeInitialized { adapter.features = it } +object FeatureBinding { + @BindingAdapter("features") + @JvmStatic + fun bindFeatures(view: RecyclerView, features: List?) { + val adapter = (view.adapter as? FeaturesAdapter) ?: return + adapter.features = features.orEmpty() } } 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 60% 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 ea9955a37..ba9ac57c3 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 2019 Splendo Consulting B.V. The Netherlands + Licensed under the Apache License, Version 2.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 @@ -24,16 +23,17 @@ 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.shared.viewmodel.info.DialogSpecRow +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 -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 +45,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 ) ) } @@ -68,15 +66,27 @@ class InfoFragment : KalugaViewModelFragment(R.layout.fragment_in ) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - - super.onViewCreated(view, savedInstanceState) + 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 + } +} - val adapter = InfoAdapter(viewModel).apply { - view.findViewById(R.id.info_buttons) - .adapter = this - } - viewModel.buttons.observeInitialized { adapter.buttons = it } +object InfoButtonBinding { + @BindingAdapter("infoButtons") + @JvmStatic + fun bindInfoButtons(view: RecyclerView, infoButtons: List?) { + val adapter = (view.adapter as? InfoAdapter) ?: return + adapter.buttons = infoButtons.orEmpty() } } @@ -108,7 +118,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/test-utils-keyboard/src/jvmMain/kotlin/MockFocusHandler.kt b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardActivity.kt similarity index 60% rename from test-utils-keyboard/src/jvmMain/kotlin/MockFocusHandler.kt rename to example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardActivity.kt index cc2dc3dba..f3a19a0ed 100644 --- a/test-utils-keyboard/src/jvmMain/kotlin/MockFocusHandler.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardActivity.kt @@ -15,16 +15,10 @@ */ -package com.splendo.kaluga.test.keyboard +package com.splendo.kaluga.example.keyboard -import com.splendo.kaluga.keyboard.FocusHandler +import com.splendo.kaluga.example.compose.ComposeOrXMLActivity +import com.splendo.kaluga.example.keyboard.compose.ComposeKeyboardActivity +import com.splendo.kaluga.example.keyboard.xml.XMLKeyboardActivity -actual class MockFocusHandler : BaseMockFocusHandler(), FocusHandler { - actual fun simulateGiveFocus() { - super.giveFocus() - } - - actual fun simulateRemoveFocus() { - super.removeFocus() - } -} +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 03f5616ee..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/keyboard/KeyboardManagerActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - -Copyright 2019 Splendo Consulting B.V. The Netherlands - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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..945f62b81 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/compose/KeyboardLayout.kt @@ -0,0 +1,109 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.accompanist.themeadapter.material.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.compose.ComposeFocusHandler +import com.splendo.kaluga.keyboard.compose.ComposeKeyboardManager +import com.splendo.kaluga.resources.compose.Composable +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 + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider { + KeyboardLayout() + } + } + } +} + +@Composable +fun KeyboardLayout() { + MdcTheme { + val focusHandler = LocalFocusManager.current + val viewModel = + koinViewModel>(named(composeKeyboardViewModel)) { + parametersOf( + ComposeKeyboardManager.Builder(), + 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()) + } + } + } +} 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..c077f9ef9 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/keyboard/xml/XMLKeyboardActivity.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.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.ViewKeyboardManager +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import org.koin.core.qualifier.named + +class XMLKeyboardActivity : KalugaViewModelActivity>() { + + companion object { + const val viewModelName = "ViewKeyboardViewModel" + } + + override val viewModel: KeyboardViewModel by viewModel(named(viewModelName)) { + parametersOf( + ViewKeyboardManager.Builder(), + 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) + } +} 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 bb88136ad..05abdd6ef 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 @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -22,23 +22,21 @@ 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) { +class LinksActivity : KalugaViewModelActivity() { 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 00ce85ea7..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 2019 Splendo Consulting B.V. The Netherlands + Licensed under the Apache License, Version 2.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.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..a835f4e63 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/loading/compose/LoadingLayout.kt @@ -0,0 +1,71 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.accompanist.themeadapter.material.MdcTheme +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.hud.HudViewModel +import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel + +class ComposeLoadingActivity : AppCompatActivity() { + @SuppressLint("MissingSuperCall") // Lint bug + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CompositionLocalProvider { + LoadingLayout() + } + } + } +} + +@Composable +fun LoadingLayout() { + MdcTheme { + val viewModel = koinViewModel() + + 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()) + } + } + } +} 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..22994bb1c --- /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) + } +} 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 61ba1731d..5e8868a37 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 @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -20,38 +20,42 @@ 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) + private val locationPermission = LocationPermission(background = false, precise = true) } override val viewModel: LocationViewModel by viewModel { - parametersOf(permission) + parametersOf(locationPermission) } 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/location/LocationBackgroundService.kt b/example/android/src/main/java/com/splendo/kaluga/example/location/LocationBackgroundService.kt index 503a36f06..0cf911b79 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 @@ -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. @@ -17,39 +17,61 @@ package com.splendo.kaluga.example.location +import android.Manifest import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.os.Build +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.lifecycle.lifecycleScope import com.splendo.kaluga.example.R import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel +import com.splendo.kaluga.example.shared.viewmodel.permissions.NotificationPermissionViewModel import com.splendo.kaluga.permissions.location.LocationPermission -import org.koin.core.component.KoinComponent -import org.koin.core.component.get +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNot -class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinComponent { +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 locationPermission = LocationPermission(background = true, precise = true) } private val notificationService by lazy { applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } - private val viewModel = LocationViewModel(permission, get()) + private val viewModel = LocationViewModel(locationPermission) + private val notificationsViewModel = NotificationPermissionViewModel() override fun onCreate() { super.onCreate() - viewModel.location.observeInitialized { message -> - NotificationManagerCompat.from(applicationContext).notify(notificationId, getNotification(message)) + lifecycleScope.launchWhenCreated { + combine(viewModel.location.stateFlow, notificationsViewModel.hasPermission.stateFlow) { message, hasNotificationsPermission -> + if (hasNotificationsPermission) message else null + }.collect { message -> + if (message != null && ActivityCompat.checkSelfPermission( + this@LocationBackgroundService, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + ) { + NotificationManagerCompat.from(applicationContext).notify(notificationId, getNotification(message)) + } + } + } + + lifecycleScope.launchWhenCreated { + notificationsViewModel.hasPermission.stateFlow.filterNot { it }.collect { + notificationsViewModel.requestPermission() + } } startForeground(notificationId, getNotification("")) @@ -58,6 +80,7 @@ class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinCom override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) viewModel.didResume() + notificationsViewModel.didResume() return START_NOT_STICKY } @@ -65,9 +88,12 @@ class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinCom super.onDestroy() viewModel.didPause() viewModel.onCleared() + notificationsViewModel.didPause() + notificationsViewModel.onCleared() 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/permissions/PermissionActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt new file mode 100644 index 000000000..19143a8e0 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionActivity.kt @@ -0,0 +1,54 @@ +/* + 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.LayoutInflater +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.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 PermissionActivity : KalugaViewModelActivity() { + + 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) + + val binding = ActivityPermissionBinding.inflate(LayoutInflater.from(this), null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + + 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/PermissionsDemoActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt deleted file mode 100644 index ca92bc4cd..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/permissions/PermissionsDemoActivity.kt +++ /dev/null @@ -1,84 +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.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/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 75% 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 35737b556..5a07532ea 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 @@ -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. @@ -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,10 +47,20 @@ 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) + } +} + +object PermissionBinding { + @BindingAdapter("permissions") + @JvmStatic + fun bindPermissions(view: RecyclerView, permissions: List?) { + val adapter = (view.adapter as? PermissionsAdapter) ?: return + adapter.permissions = permissions.orEmpty() } } 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 7959af867..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/PlatformSpecificActivity.kt +++ /dev/null @@ -1,88 +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.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.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.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/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 d4831c087..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentLayout.kt +++ /dev/null @@ -1,124 +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.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.platformspecific.compose.bottomSheet.BottomSheetNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.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 99cbc0f99..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/ui/BottomSheetParentSubPageLayout.kt +++ /dev/null @@ -1,53 +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.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.platformspecific.compose.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/contacts/ContactsActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ContactsActivity.kt deleted file mode 100644 index 6cae19f6e..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ContactsActivity.kt +++ /dev/null @@ -1,32 +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.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 5ddc05a5e..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactDetailsLayout.kt +++ /dev/null @@ -1,97 +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.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.platformspecific.compose.contacts.model.ContactDetails -import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactDetailsNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.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 ecef2a853..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsLayout.kt +++ /dev/null @@ -1,53 +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.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.platformspecific.compose.contacts.model.ContactDetails -import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.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 350fb5559..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/ui/ContactsListLayout.kt +++ /dev/null @@ -1,100 +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.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.platformspecific.compose.contacts.model.ContactDetails -import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactsListNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.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 7c4d7bf6a..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/contacts/viewModel/NavigationMappers.kt +++ /dev/null @@ -1,52 +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.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.platformspecific.compose.contacts.viewModel.ContactDetailsNavigation -import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.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/resources/ButtonActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt deleted file mode 100644 index 5f0d0f95d..000000000 --- a/example/android/src/main/java/com/splendo/kaluga/example/resources/ButtonActivity.kt +++ /dev/null @@ -1,79 +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.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/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..6eed87299 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ButtonsLayout.kt @@ -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. + + */ + +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.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel +import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel + +@Composable +fun ButtonsLayout() { + val viewModel = koinViewModel() + + 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..b7235ea58 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ColorsLayout.kt @@ -0,0 +1,158 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.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.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.runtime.Composable +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.onFocusChanged +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +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.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.KalugaBackgroundStyle +import org.koin.androidx.compose.koinViewModel + +@Composable +fun ColorsLayout() { + val viewModel = koinViewModel() + + 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/ImagesLayout.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ImagesLayout.kt new file mode 100644 index 000000000..a39bc87ed --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ImagesLayout.kt @@ -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. + + */ + +package com.splendo.kaluga.example.resources.compose + +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.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +import com.splendo.kaluga.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.resources.ImagesViewModel +import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel + +@Composable +fun ImagesLayout() { + val viewModel = koinViewModel() + + ViewModelComposable(viewModel) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(Constants.Padding.default) + .verticalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(Constants.Padding.default) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + viewModel.images.forEach { + it.Composable(contentDescription = null) + } + } + Column( + verticalArrangement = Arrangement.spacedBy(Constants.Padding.default), + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + viewModel.tintedImages.forEach { + it.Composable(contentDescription = null) + } + } + } + } +} 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..091fcd546 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/LabelsLayout.kt @@ -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. + + */ + +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.example.compose.Constants +import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel +import com.splendo.kaluga.resources.compose.Composable +import org.koin.androidx.compose.koinViewModel + +@Composable +fun LabelsLayout() { + val viewModel = koinViewModel() + + 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..f639b6515 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/compose/ResourcesLayout.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.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 com.google.accompanist.themeadapter.material.MdcTheme +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 +import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity +import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable +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 + +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 viewModel = koinViewModel { + parametersOf( + RootNavHostComposableNavigator( + navigationMapper = { action -> + when (action) { + is ResourcesListNavigationAction.Button -> action.next + is ResourcesListNavigationAction.Color -> action.next + is ResourcesListNavigationAction.Image -> action.next + is ResourcesListNavigationAction.Label -> action.next + } + } + ) { + composable(ResourcesListNavigationAction.Button.route()) { ButtonsLayout() } + composable(ResourcesListNavigationAction.Color.route()) { ColorsLayout() } + composable(ResourcesListNavigationAction.Image.route()) { ImagesLayout() } + composable(ResourcesListNavigationAction.Label.route()) { LabelsLayout() } + } + ) + } + 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) + } + } + } + } + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ButtonActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ButtonActivity.kt new file mode 100644 index 000000000..6bbd0a1e4 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ButtonActivity.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.resources.xml + +import android.os.Bundle +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityButtonBinding +import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel +import com.splendo.kaluga.example.view.ButtonAdapter +import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration +import com.splendo.kaluga.resources.dpToPixel +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 = ActivityButtonBinding.inflate(layoutInflater, null, false) + binding.buttonsList.adapter = ButtonAdapter() + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + binding.buttonsList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) + } +} 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 61% 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 c9e6138e8..cdbcc7635 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 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. + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 @@ -30,12 +28,8 @@ 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.stylable.KalugaBackgroundStyle 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() { @@ -76,13 +70,22 @@ class ColorActivity : KalugaViewModelActivity() { } } +object BackgroundStyleBinding { + @BindingAdapter("backgroundStyles") + @JvmStatic + fun bindBackgroundStyles(view: RecyclerView, backgroundStyles: List?) { + val adapter = (view.adapter as? BackgroundAdapter) ?: return + adapter.backgrounds = backgroundStyles.orEmpty() + } +} + class BackgroundAdapter : RecyclerView.Adapter() { class BackgroundViewHolder(val binding: ViewResourceListBackgroundBinding) : RecyclerView.ViewHolder(binding.root) { val view = binding.root } - var backgrounds: List = emptyList() + var backgrounds: List = emptyList() set(newValue) { field = newValue notifyDataSetChanged() @@ -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/xml/ImagesActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ImagesActivity.kt new file mode 100644 index 000000000..5032436a6 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/ImagesActivity.kt @@ -0,0 +1,116 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityResourcesImagesBinding +import com.splendo.kaluga.example.databinding.ViewResourceListImageBinding +import com.splendo.kaluga.example.databinding.ViewResourceListTintedImageBinding +import com.splendo.kaluga.example.shared.viewmodel.resources.ImagesViewModel +import com.splendo.kaluga.resources.KalugaImage +import com.splendo.kaluga.resources.TintedImage +import org.koin.androidx.viewmodel.ext.android.viewModel + +class ImagesActivity : KalugaViewModelActivity() { + + override val viewModel: ImagesViewModel by viewModel() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityResourcesImagesBinding.inflate(layoutInflater, null, false) + binding.viewModel = viewModel + binding.lifecycleOwner = this + + binding.imagesList.adapter = ImageAdapter() + binding.tintedImagesList.adapter = TintedImageAdapter() + + setContentView(binding.root) + } +} + +object ImagesBinding { + @BindingAdapter("images") + @JvmStatic + fun bindImages(view: RecyclerView, images: List?) { + val adapter = (view.adapter as? ImageAdapter) ?: return + adapter.images = images.orEmpty() + } +} + +object TintedImagesBinding { + + @BindingAdapter("tintedImages") + @JvmStatic + fun bindImages(view: RecyclerView, tintedImages: List?) { + val adapter = (view.adapter as? TintedImageAdapter) ?: return + adapter.tintedImages = tintedImages.orEmpty() + } +} + +class ImageAdapter : RecyclerView.Adapter() { + + class ImageViewHolder(val binding: ViewResourceListImageBinding) : RecyclerView.ViewHolder(binding.root) { + val view = binding.root + } + + var images: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { + val binding = ViewResourceListImageBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ImageViewHolder(binding) + } + + override fun getItemCount(): Int = images.size + + override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { + holder.binding.image = images.getOrNull(position) + } +} + +class TintedImageAdapter : RecyclerView.Adapter() { + + class ImageViewHolder(val binding: ViewResourceListTintedImageBinding) : RecyclerView.ViewHolder(binding.root) { + val view = binding.root + } + + var tintedImages: List = emptyList() + set(newValue) { + field = newValue + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { + val binding = ViewResourceListTintedImageBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ImageViewHolder(binding) + } + + override fun getItemCount(): Int = tintedImages.size + + override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { + holder.binding.image = tintedImages.getOrNull(position) + } +} 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 43784f1b3..e592a1fc7 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 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. + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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,15 +38,21 @@ 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) + binding.labelsList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) + } +} - val adapter = LabelAdapter().apply { - - binding.resourcesList.adapter = this - } - binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) - viewModel.labels.observeInitialized { adapter.labels = it } +object LabelsBinding { + @BindingAdapter("labels") + @JvmStatic + fun bindLabels(view: RecyclerView, labels: List?) { + val adapter = (view.adapter as? LabelAdapter) ?: return + adapter.labels = labels.orEmpty() } } 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..4e1dbb1fc --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/resources/xml/XMLResourcesActivity.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.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.Image -> 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) + } +} + +object ResourcesBinding { + @BindingAdapter("resources") + @JvmStatic + fun bindResources(view: RecyclerView, resources: List?) { + val adapter = (view.adapter as? ResourcesAdapter) ?: return + adapter.resources = resources.orEmpty() + } +} + +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/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificActivity.kt new file mode 100644 index 000000000..ed0633501 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificActivity.kt @@ -0,0 +1,96 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +import android.os.Bundle +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +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.toTypedProperty +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityScientificBinding +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificViewModel +import com.splendo.kaluga.example.view.ButtonAdapter +import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration +import com.splendo.kaluga.resources.dpToPixel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +object ScientificConverterButtonBinding { + @BindingAdapter("scientificConverterButtons") + @JvmStatic + fun bindButtons(view: RecyclerView, buttons: List?) { + val adapter = (view.adapter as? ButtonAdapter) ?: return + adapter.buttons = buttons?.map { it.button }.orEmpty() + } +} + +class ScientificActivity : KalugaViewModelActivity() { + + companion object { + const val leftUnitRequestKey: String = "LeftUnit" + const val rightUnitRequestKey: String = "RightUnit" + } + + class LeftUnitDialogSelectionFragment : ScientificUnitSelectionDialogFragment() { + override val requestKey: String = leftUnitRequestKey + } + + class RightUnitSelectionDialogFragment : ScientificUnitSelectionDialogFragment() { + override val requestKey: String = rightUnitRequestKey + } + + override val viewModel: ScientificViewModel by viewModel { + parametersOf( + ActivityNavigator> { action -> + when (action) { + is ScientificNavigationAction.SelectUnit -> { + NavigationSpec.Dialog(ScientificUnitSelectionDialogFragment.TAG) { + when (action) { + is ScientificNavigationAction.SelectUnit.Left -> LeftUnitDialogSelectionFragment() + is ScientificNavigationAction.SelectUnit.Right -> RightUnitSelectionDialogFragment() + } + } + } + is ScientificNavigationAction.Converter -> NavigationSpec.Activity() + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + supportFragmentManager.setFragmentResultListener(leftUnitRequestKey, this) { _, result -> + viewModel.didSelectLeftUnit(result.toTypedProperty(NavigationBundleSpecType.IntegerType)) + } + supportFragmentManager.setFragmentResultListener(rightUnitRequestKey, this) { _, result -> + viewModel.didSelectRightUnit(result.toTypedProperty(NavigationBundleSpecType.IntegerType)) + } + + val binding = ActivityScientificBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + binding.viewModel = viewModel + binding.convertersList.adapter = ButtonAdapter() + binding.convertersList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt())) + binding.lifecycleOwner = this + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificConverterActivity.kt b/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificConverterActivity.kt new file mode 100644 index 000000000..968f0c501 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificConverterActivity.kt @@ -0,0 +1,83 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +import android.os.Bundle +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.parseTypeOf +import com.splendo.kaluga.architecture.navigation.toTypedProperty +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.example.databinding.ActivityScientificConverterBinding +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificConverterNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificConverterViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +class ScientificConverterActivity : KalugaViewModelActivity() { + + companion object { + const val leftUnitRequestKey: String = "ConverterLeftUnit" + const val rightUnitRequestKey: String = "ConverterRightUnit" + } + class LeftUnitDialogSelectionFragment : ScientificUnitSelectionDialogFragment() { + override val requestKey: String = leftUnitRequestKey + } + + class RightUnitSelectionDialogFragment : ScientificUnitSelectionDialogFragment() { + override val requestKey: String = rightUnitRequestKey + } + + override val viewModel: ScientificConverterViewModel by viewModel { + parametersOf( + parseTypeOf(ScientificConverterViewModel.Arguments.serializer()), + ActivityNavigator> { action -> + when (action) { + is ScientificConverterNavigationAction.Close -> NavigationSpec.Close() + is ScientificConverterNavigationAction.SelectUnit -> NavigationSpec.Dialog(ScientificUnitSelectionDialogFragment.TAG) { + when (action) { + is ScientificConverterNavigationAction.SelectUnit.Left -> LeftUnitDialogSelectionFragment() + is ScientificConverterNavigationAction.SelectUnit.Right -> RightUnitSelectionDialogFragment() + } + } + } + } + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + supportFragmentManager.setFragmentResultListener(leftUnitRequestKey, this) { _, result -> + viewModel.didSelectLeftUnit(result.toTypedProperty(NavigationBundleSpecType.IntegerType)) + } + supportFragmentManager.setFragmentResultListener(rightUnitRequestKey, this) { _, result -> + viewModel.didSelectRightUnit(result.toTypedProperty(NavigationBundleSpecType.IntegerType)) + } + + val binding = ActivityScientificConverterBinding.inflate(layoutInflater, null, false) + setContentView(binding.root) + binding.viewModel = viewModel + binding.lifecycleOwner = this + } + + override fun onBackPressed() { + viewModel.onClosePressed() + } +} diff --git a/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificUnitSelectionDialogFragment.kt b/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificUnitSelectionDialogFragment.kt new file mode 100644 index 000000000..3da9e61fd --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/scientific/ScientificUnitSelectionDialogFragment.kt @@ -0,0 +1,84 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +import android.app.ActionBar.LayoutParams +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.splendo.kaluga.architecture.navigation.ActivityNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.parseTypeOfOrNull +import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelDialogFragment +import com.splendo.kaluga.example.databinding.DialogScientificUnitSelectionBinding +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificUnitSelectionAction +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificUnitSelectionViewModel +import com.splendo.kaluga.example.view.ButtonAdapter +import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration +import com.splendo.kaluga.resources.dpToPixel +import com.splendo.kaluga.scientific.PhysicalQuantity +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf + +abstract class ScientificUnitSelectionDialogFragment : KalugaViewModelDialogFragment() { + + companion object { + val TAG = ScientificUnitSelectionDialogFragment::class.simpleName.orEmpty() + } + + abstract val requestKey: String + + override val viewModel: ScientificUnitSelectionViewModel by viewModel { + parametersOf( + parseTypeOfOrNull(PhysicalQuantity.serializer()), + ActivityNavigator> { action -> + when (action) { + is ScientificUnitSelectionAction.Cancelled -> NavigationSpec.DismissDialog(TAG) + is ScientificUnitSelectionAction.DidSelect -> { + NavigationSpec.DismissDialog(TAG, fragmentRequestKey = requestKey) + } + } + } + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding = DialogScientificUnitSelectionBinding.inflate(inflater, container, false) + binding.viewModel = viewModel + binding.currentUnits.adapter = ButtonAdapter() + binding.lifecycleOwner = this + binding.currentUnits.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(requireContext()).toInt())) + return binding.root + } + + override fun onResume() { + super.onResume() + + dialog?.window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + } + + override fun onCancel(dialog: DialogInterface) { + viewModel.onCancel() + } +} 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/NetworkActivity.kt similarity index 62% rename from example/android/src/main/java/com/splendo/kaluga/example/system/fragments/NetworkFragment.kt rename to example/android/src/main/java/com/splendo/kaluga/example/system/NetworkActivity.kt index afb7a6d50..127e4494b 100644 --- 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/NetworkActivity.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -15,30 +15,24 @@ */ -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) } } 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 d0ed42a86..1ec146fa4 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 @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -20,29 +20,25 @@ 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.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 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) { +class SystemActivity : KalugaViewModelActivity() { 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() } } ) @@ -51,13 +47,20 @@ 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 - } + val binding = ActivitySystemBinding.inflate(LayoutInflater.from(this), null, false) + binding.systemFeaturesList.adapter = SystemFeatureAdapter(viewModel) + binding.viewModel = viewModel + binding.lifecycleOwner = this + setContentView(binding.root) + } +} - viewModel.modules.observeInitialized { - adapter.modules = it - } +object SystemFeaturesBinding { + @BindingAdapter("systemFeatures") + @JvmStatic + fun bindSystemFeatures(view: RecyclerView, resources: List?) { + val adapter = (view.adapter as? SystemFeatureAdapter) ?: return + adapter.systemFeatures = resources.orEmpty() } } @@ -65,21 +68,23 @@ 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() + var systemFeatures: 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) + 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 -> + systemFeatures.getOrNull(position)?.let { feature -> holder.button.text = feature.name holder.button.setOnClickListener { viewModel.onButtonTapped(feature) } } ?: run { @@ -88,5 +93,5 @@ class SystemFeatureAdapter( } } - override fun getItemCount(): Int = modules.size + override fun getItemCount(): Int = systemFeatures.size } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/view/ButtonAdapter.kt b/example/android/src/main/java/com/splendo/kaluga/example/view/ButtonAdapter.kt new file mode 100644 index 000000000..c877aa7f6 --- /dev/null +++ b/example/android/src/main/java/com/splendo/kaluga/example/view/ButtonAdapter.kt @@ -0,0 +1,65 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.splendo.kaluga.example.databinding.ViewListButtonBinding +import com.splendo.kaluga.resources.view.KalugaButton +import com.splendo.kaluga.resources.view.bindButton + +object ButtonsBinding { + @BindingAdapter("buttons") + @JvmStatic + fun bindButtons(view: RecyclerView, buttons: List?) { + val adapter = (view.adapter as? ButtonAdapter) ?: return + adapter.buttons = buttons.orEmpty() + } +} + +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/example/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt b/example/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt index 4412a7e84..b7398e774 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt +++ b/example/android/src/main/java/com/splendo/kaluga/example/view/ItemDecoration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 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/example/android/src/main/res/drawable/cancel.xml b/example/android/src/main/res/drawable/cancel.xml new file mode 100644 index 000000000..5ca0cb0c9 --- /dev/null +++ b/example/android/src/main/res/drawable/cancel.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/example/android/src/main/res/drawable/check.xml b/example/android/src/main/res/drawable/check.xml new file mode 100644 index 000000000..f4ff4129f --- /dev/null +++ b/example/android/src/main/res/drawable/check.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/example/android/src/main/res/drawable/star.xml b/example/android/src/main/res/drawable/star.xml new file mode 100644 index 000000000..a8ca9ef06 --- /dev/null +++ b/example/android/src/main/res/drawable/star.xml @@ -0,0 +1,27 @@ + + + + + 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 926bd42b8..eda34f47b 100644 --- a/example/android/src/main/res/layout/activity_architecture_details.xml +++ b/example/android/src/main/res/layout/activity_architecture_details.xml @@ -31,13 +31,21 @@ android:text="@{viewModel.number.stateFlow}" /> + app:kalugaButton="@{viewModel.inverseButton}"/> + + \ 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..cb65056fe 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}"> + + + 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_link.xml b/example/android/src/main/res/layout/activity_link.xml index d5fecd2fd..99c88fc0b 100644 --- a/example/android/src/main/res/layout/activity_link.xml +++ b/example/android/src/main/res/layout/activity_link.xml @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_scientific_converter.xml b/example/android/src/main/res/layout/activity_scientific_converter.xml new file mode 100644 index 000000000..bd9f4c440 --- /dev/null +++ b/example/android/src/main/res/layout/activity_scientific_converter.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_system.xml b/example/android/src/main/res/layout/activity_system.xml index bf9c61779..6fcd59fdf 100644 --- a/example/android/src/main/res/layout/activity_system.xml +++ b/example/android/src/main/res/layout/activity_system.xml @@ -1,42 +1,25 @@ - - - - + + + + + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_gravity="center_horizontal" + android:layout_margin="@dimen/networkLayoutsMargin"> - - + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:systemFeatures="@{viewModel.systemFeatures.stateFlow}"/> - \ No newline at end of file + + \ No newline at end of file diff --git a/example/android/src/main/res/layout/activity_timer.xml b/example/android/src/main/res/layout/activity_timer.xml new file mode 100644 index 000000000..ea6cc5ddd --- /dev/null +++ b/example/android/src/main/res/layout/activity_timer.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + @@ -85,9 +87,11 @@ + + @@ -99,6 +103,7 @@ + @@ -121,53 +126,47 @@ - - + + - + - + - + - - + @@ -177,6 +176,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -284,14 +354,14 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -787,9 +908,9 @@ - + - + @@ -798,21 +919,15 @@ - + - - @@ -831,6 +946,8 @@ + + @@ -878,14 +995,14 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + @@ -1079,14 +1084,14 @@ - + - + @@ -1710,9 +1714,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/example/ios/Demo/Beacons/BeaconsViewCell.swift b/example/ios/Demo/Beacons/BeaconsViewCell.swift index ec3468e95..f24195259 100644 --- a/example/ios/Demo/Beacons/BeaconsViewCell.swift +++ b/example/ios/Demo/Beacons/BeaconsViewCell.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 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. @@ -15,7 +15,7 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared class BeaconsViewCell: UICollectionViewCell { @@ -35,15 +35,18 @@ class BeaconsViewCell: UICollectionViewCell { func startMonitoring() { viewModel?.namespace_.observe { [weak self] namespace in self?.namespaceLabel.text = namespace as String? - }.addTo(disposeBag: disposeBag) + } + .addTo(disposeBag: disposeBag) viewModel?.instance.observe { [weak self] instance in self?.instanceLabel.text = instance as String? - }.addTo(disposeBag: disposeBag) + } + .addTo(disposeBag: disposeBag) viewModel?.txPower.observe { [weak self] txPower in self?.txPowerLabel.text = txPower as String? - }.addTo(disposeBag: disposeBag) + } + .addTo(disposeBag: disposeBag) } func stopMonitoring() { diff --git a/example/ios/Demo/Beacons/BeaconsViewController.swift b/example/ios/Demo/Beacons/BeaconsViewController.swift index d50d2f1b1..11c3aebd7 100644 --- a/example/ios/Demo/Beacons/BeaconsViewController.swift +++ b/example/ios/Demo/Beacons/BeaconsViewController.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 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. @@ -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,9 @@ class BeaconsViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + title = "feature_beacons".localized() + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in guard let viewModel = self?.viewModel else { return [] } return [ @@ -77,9 +75,9 @@ class BeaconsViewController: UICollectionViewController { } 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 + return collectionView.dequeueTypedReusableCell(withReuseIdentifier: BeaconsViewCell.identifier, for: indexPath) { (beaconCell: BeaconsViewCell) in + beaconCell.configure(with: beacons[indexPath.row]) + } } override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { diff --git a/example/ios/Demo/Bluetooth/BluetoothCharacteristicView.swift b/example/ios/Demo/Bluetooth/BluetoothCharacteristicView.swift new file mode 100644 index 000000000..58efcf4ac --- /dev/null +++ b/example/ios/Demo/Bluetooth/BluetoothCharacteristicView.swift @@ -0,0 +1,128 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 BluetoothCharacteristicView: UICollectionViewCell { + + enum Companion { + static let identifier = "BluetoothCharacteristicView" + } + + weak var parent: BluetoothServiceView? + var characteristic: BluetoothCharacteristicViewModel? + private let disposeBag = DisposeBag() + + private var isInvalidating = 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 = "bluetooth_descriptors".localized() + + 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 + ) + } + + 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) + } + + 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 { + return collectionView.dequeueTypedReusableCell( + withReuseIdentifier: BluetoothDescriptorView.Companion.identifier, + for: indexPath + ) { (descriptorCell: BluetoothDescriptorView) in + descriptorCell.descriptor = descriptors[indexPath.row] + } + } + + 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 + } +} diff --git a/example/ios/Demo/Bluetooth/BluetoothDescriptorView.swift b/example/ios/Demo/Bluetooth/BluetoothDescriptorView.swift new file mode 100644 index 000000000..495e149bb --- /dev/null +++ b/example/ios/Demo/Bluetooth/BluetoothDescriptorView.swift @@ -0,0 +1,62 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 BluetoothDescriptorView: UICollectionViewCell { + + enum Companion { + static let identifier = "BluetoothDescriptorView" + } + + var descriptor: BluetoothDescriptorViewModel? + private let disposeBag = DisposeBag() + + @IBOutlet var descriptorIdentifier: UILabel! + @IBOutlet var descriptorValue: UILabel! + + 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) + } + + 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/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift b/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift index dade37ebd..33b3b93f5 100644 --- a/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift +++ b/example/ios/Demo/Bluetooth/BluetoothDeviceDetailsViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,24 +16,19 @@ */ 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 { - let vc = Const.storyboard.instantiateViewController(withIdentifier: Const.storyboardId) as! BluetoothDeviceDetailsViewController + static func create(identifier: UUID) -> BluetoothDeviceDetailsViewController { + let viewController = MainStoryboard.instantiateBluetoothDeviceDetailsViewController() if #available(iOS 13.0, *) { - vc.isModalInPresentation = true + viewController.isModalInPresentation = true } - vc.viewModel = KNArchitectureFramework().createBluetoothDeviceDetailsViewModel(identifier: deviceUuid, bluetooth: bluetooth) - return vc + viewController.viewModel = BluetoothDeviceDetailViewModel(identifier: identifier) + return viewController } - + var viewModel: BluetoothDeviceDetailViewModel! private var lifecycleManager: LifecycleManager! @@ -46,29 +41,31 @@ class BluetoothDeviceDetailsViewController : UIViewController, UICollectionViewD @IBOutlet var servicesList: UICollectionView! private var services: [BluetoothServiceViewModel] = [] - - private var isInvalidating: Bool = false - + + private var isInvalidating = false + deinit { lifecycleManager.unbind() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - servicesHeader.text = NSLocalizedString("bluetooth_services_header", comment: "") + + title = "feature_bluetooth".localized() + + servicesHeader.text = "bluetooth_services_header".localized() 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,273 +89,39 @@ 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 + return collectionView.dequeueTypedReusableCell( + withReuseIdentifier: BluetoothServiceView.Companion.identifier, + for: indexPath + ) { (serviceCell: BluetoothServiceView) in + serviceCell.parent = self + serviceCell.service = services[indexPath.row] + } } - + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { + if !isInvalidating { (cell as? BluetoothServiceView)?.startMonitoring() } } - + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - if (!isInvalidating) { + if !isInvalidating { (cell as? BluetoothServiceView)?.stopMonitoring() } } - - fileprivate func updateListSize() { + + 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/example/ios/Demo/Bluetooth/BluetoothServiceView.swift b/example/ios/Demo/Bluetooth/BluetoothServiceView.swift new file mode 100644 index 000000000..230bda401 --- /dev/null +++ b/example/ios/Demo/Bluetooth/BluetoothServiceView.swift @@ -0,0 +1,126 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 BluetoothServiceView: UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource { + + enum Companion { + static let identifier = "BluetoothServiceView" + } + + weak var parent: BluetoothDeviceDetailsViewController? + var service: BluetoothServiceViewModel? + private let disposeBag = DisposeBag() + + private var isInvalidating = 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 = "bluetooth_service".localized() + characteristicsHeader.text = "bluetooth_characteristics".localized() + + 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 + ) + } + + 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) + } + + func stopMonitoring() { + service?.didPause() + disposeBag.dispose() + } + + 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 { + return collectionView.dequeueTypedReusableCell( + withReuseIdentifier: BluetoothCharacteristicView.Companion.identifier, + for: indexPath + ) { (characteristicCell: BluetoothCharacteristicView) in + characteristicCell.parent = self + characteristicCell.characteristic = characteristics[indexPath.row] + } + } + + 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 + } +} diff --git a/example/ios/Demo/Bluetooth/BluetoothViewController.swift b/example/ios/Demo/Bluetooth/BluetoothViewController.swift index f4195727b..10b032aa9 100644 --- a/example/ios/Demo/Bluetooth/BluetoothViewController.swift +++ b/example/ios/Demo/Bluetooth/BluetoothViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -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) +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! @@ -33,20 +36,22 @@ 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 + + title = "feature_bluetooth".localized() + + 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,55 +65,74 @@ 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) + if isScanning { + self.navigationItem.setRightBarButton( + UIBarButtonItem( + title: "bluetooth_stop_scanning".localized(), + 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) + self.navigationItem.setRightBarButton( + UIBarButtonItem( + title: "bluetooth_start_scanning".localized(), + 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 + return collectionView.dequeueTypedReusableCell( + withReuseIdentifier: BluetoothCell.Companion.identifier, + for: indexPath + ) { (bluetoothCell: BluetoothCell) in + bluetoothCell.device = devices[indexPath.row] + } } - + 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() @@ -119,7 +143,7 @@ class BluetoothViewController : UICollectionViewController { class BluetoothCell: UICollectionViewCell { - fileprivate struct Companion { + fileprivate enum Companion { static let identifier = "BluetoothCell" } @@ -142,20 +166,20 @@ class BluetoothCell: UICollectionViewCell { @IBOutlet var moreButtonContainer: UIView! @IBOutlet var moreButton: UIButton! - fileprivate var device: BluetoothListDeviceViewModel? = nil - + fileprivate var device: BluetoothListDeviceViewModel? + @IBAction func onConnectPressed() { device?.onConnectPressed() } - + @IBAction func onDisonnectPressed() { device?.onDisconnectPressed() } - + @IBAction func onMorePressed() { device?.onMorePressed() } - + func startMonitoring() { disposeBag.dispose() guard let device = device else { @@ -165,29 +189,25 @@ class BluetoothCell: UICollectionViewCell { 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 { + let disposables = [ + device.name.observe { [weak self] name in + self?.deviceName.text = name as? String + }, + device.rssi.observe { [weak self] rssiValue in + self?.rssi.text = rssiValue as? String + }, + device.txPower.observe { [weak self] txPowerValue in + self?.txPower.text = txPowerValue as? String + }, + device.status.observe { [weak self] status in + self?.connectionStatus.text = status as? String + }, + device.isConnectButtonVisible.observe { [weak self] isVisible in + self?.buttonContainer.alpha = (isVisible as? Bool ?? false) ? 1.0 : 0.0 + }, + device.connectButtonState.observe { [weak self] connectButtonState in + let state = connectButtonState as? BluetoothListDeviceViewModel.ConnectButtonState ?? BluetoothListDeviceViewModel.ConnectButtonState.disconnect + switch state { case BluetoothListDeviceViewModel.ConnectButtonState.connect: self?.connectButton.isHidden = false self?.disconnectButton.isHidden = true @@ -195,8 +215,13 @@ class BluetoothCell: UICollectionViewCell { self?.connectButton.isHidden = true self?.disconnectButton.isHidden = false default: () + } } - }.addTo(disposeBag: disposeBag) + ] + + for disposable in disposables { + disposable.addTo(disposeBag: disposeBag) + } device.isFoldedOut.observe { [weak self] isFoldedOut in self?.foldOutMenu.isHidden = !((isFoldedOut as? Bool) ?? false) @@ -222,7 +247,7 @@ class BluetoothCell: UICollectionViewCell { self?.manufacturerData.text = manufacturerData as? String } } - + func stopMonitoring() { disposeBag.dispose() device?.didPause() diff --git a/example/ios/Demo/DateTime/TimerView.swift b/example/ios/Demo/DateTime/TimerView.swift new file mode 100644 index 000000000..3696b3977 --- /dev/null +++ b/example/ios/Demo/DateTime/TimerView.swift @@ -0,0 +1,52 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +struct TimerView: View { + + private let lifecycleViewModel: LifecycleViewModel + @ObservedObject var elapsed: StringObservable + @ObservedObject var button: ButtonObservable + + init() { + let viewModel = TimerViewModel() + + elapsed = StringObservable(viewModel.elapsed) + button = ButtonObservable(viewModel.button) + + lifecycleViewModel = LifecycleViewModel(viewModel) + } + + var body: some View { + lifecycleViewModel.lifecycleView { _ in + VStack(alignment: .center, spacing: 10.0) { + Text(elapsed.value).font(.system(size: 32)) + button.value.toButton(buttonFrame: .frame(maxWidth: .infinity)) + Spacer() + } + .padding(10.0) + .navigationTitle(Text("feature_date_time".localized())) + } + } +} + +struct TimerView_Previews: PreviewProvider { + static var previews: some View { + TimerView() + } +} diff --git a/example/ios/Demo/DateTimePicker/DateTimePickerView.swift b/example/ios/Demo/DateTimePicker/DateTimePickerView.swift new file mode 100644 index 000000000..22fd3e374 --- /dev/null +++ b/example/ios/Demo/DateTimePicker/DateTimePickerView.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 SwiftUI +import KalugaExampleShared + +struct DateTimePickerView: View { + + @ObservedObject var dateLabel: StringObservable + let lifecycleViewModel: LifecycleViewModel + + init() { + let containerView = ContainerView(.datePicker) + let viewModel = DateTimePickerViewModel(dateTimePickerPresenterBuilder: containerView.datePickerBuilder) + dateLabel = StringObservable(viewModel.dateLabel) + lifecycleViewModel = LifecycleViewModel(viewModel, containerView: containerView) + } + + var body: some View { + lifecycleViewModel.lifecycleView { viewModel in + VStack { + HStack(spacing: 10.0) { + viewModel.selectDateButton.toButton( + buttonFrame: .frame(maxWidth: .infinity) + ) + viewModel.selectTimeButton.toButton( + buttonFrame: .frame(maxWidth: .infinity) + ) + } + Text(viewModel.currentTimeTitle) + Text(dateLabel.value) + Spacer() + } + } + .padding(10.0) + .navigationTitle(Text("feature_date_time_picker".localized())) + } +} + +struct DateTimePickerView_Previews: PreviewProvider { + static var previews: some View { + DateTimePickerView() + } +} diff --git a/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift b/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift index b95eb0b30..a6119470a 100644 --- a/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift +++ b/example/ios/Demo/DateTimePicker/DateTimePickerViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,43 +16,40 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class DateTimePickerViewController : UIViewController { - +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! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self, onLifecycleChanges: { [weak self] in + + title = "feature_date_time_picker".localized() + + 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() + } + + currentTimeLabel.text = viewModel.currentTimeTitle + ButtonStyleKt.bindButton(dateButton, button: viewModel.selectDateButton) + ButtonStyleKt.bindButton(timeButton, button: viewModel.selectTimeButton) } } diff --git a/example/ios/Demo/ExampleViewController.swift b/example/ios/Demo/ExampleViewController.swift index 02dbb9538..c5f4cf386 100644 --- a/example/ios/Demo/ExampleViewController.swift +++ b/example/ios/Demo/ExampleViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,42 +16,66 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class ExampleViewController : UIViewController { - - struct Const { - static let storyboard = UIStoryboard(name: "Main", bundle: nil) - static let featuresList = "FeaturesList" - static let infoView = "InfoViewController" - } +class ExampleViewController: UIViewController { @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 }) - + lazy var featuresListController = MainStoryboard.instantiateFeaturesListViewController() + lazy var infoViewController = MainStoryboard.instantiateInfoViewController() + + 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(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 + + title = "app_name".localized() + + 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 9b3fcde4d..5743c48e0 100644 --- a/example/ios/Demo/FeaturesList/FeaturesListViewController.swift +++ b/example/ios/Demo/FeaturesList/FeaturesListViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,60 +16,125 @@ */ import UIKit -import KotlinNativeFramework +import SwiftUI +import KalugaExampleShared +import PartialSheet -class FeaturesListViewController : UITableViewController { - - private lazy var viewModel: FeatureListViewModel = KNArchitectureFramework().createFeatureListViewModel(parent: self) +class FeaturesListViewController: UITableViewController { + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + action.spec + } + private lazy var viewModel = FeatureListViewModel(navigator: navigator) private var lifecycleManager: LifecycleManager! private var features = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? + 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 + if let feature = features[index] as? Feature { + viewModel.onFeaturePressed(feature: 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 + return tableView.dequeueTypedReusableCell(withIdentifier: FeaturesListCell.Const.identifier, for: indexPath) { (cell: FeaturesListCell) in + cell.label.text = features[indexPath.row] + } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } - } -class FeaturesListCell : UITableViewCell { +class FeaturesListCell: UITableViewCell { - struct Const { + enum Const { static let identifier = "FeaturesListCell" } @IBOutlet weak var label: UILabel! - +} + +private extension FeatureListNavigationAction { + var spec: NavigationSpec { + switch self { + case is FeatureListNavigationAction.Alerts: return NavigationSpec.Push(animated: true) { + SwiftUIOrUIKitSelectionViewController.create( + uiKitViewController: { MainStoryboard.instantiateAlertsViewController() }, + swiftUIView: { AlertsView() } + ) + } + case is FeatureListNavigationAction.Architecture: return NavigationSpec.Push(animated: true) { + SwiftUIOrUIKitSelectionViewController.create( + uiKitViewController: { MainStoryboard.instantiateArchitectureViewController() }, + swiftUIView: { ArchitectureView().attachPartialSheetToRoot() } + ) + } + case is FeatureListNavigationAction.Beacons: return NavigationSpec.Segue(identifier: "showBeacons") + case is FeatureListNavigationAction.Bluetooth: return NavigationSpec.Segue(identifier: "showBluetooth") + case is FeatureListNavigationAction.DateTime: return NavigationSpec.Push(animated: true) { + UIHostingController(rootView: TimerView()) + } + case is FeatureListNavigationAction.DateTimePicker: return NavigationSpec.Push(animated: true) { + SwiftUIOrUIKitSelectionViewController.create( + uiKitViewController: { MainStoryboard.instantiateDateTimePickerViewController() }, + swiftUIView: { DateTimePickerView() } + ) + } + case is FeatureListNavigationAction.Keyboard: return NavigationSpec.Push(animated: true) { + SwiftUIOrUIKitSelectionViewController.create( + uiKitViewController: { MainStoryboard.instantiateKeyboardManagerViewController() }, + swiftUIView: { KeyboardManagerView() } + ) + } + case is FeatureListNavigationAction.Links: return NavigationSpec.Segue(identifier: "showLinks") + case is FeatureListNavigationAction.LoadingIndicator: return NavigationSpec.Push(animated: true) { + SwiftUIOrUIKitSelectionViewController.create( + uiKitViewController: { MainStoryboard.instantiateLoadingViewController() }, + swiftUIView: { LoadingView() } + ) + } + 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.Push(animated: true) { + SwiftUIOrUIKitSelectionViewController.create( + uiKitViewController: { MainStoryboard.instantiateResourcesListViewController() }, + swiftUIView: { ResourcesListView() } + ) + } + case is FeatureListNavigationAction.Scientific: return NavigationSpec.Push(animated: true) { + UIHostingController(rootView: ScientificView()) + } + case is FeatureListNavigationAction.System: return NavigationSpec.Segue(identifier: "showSystem") + default: fatalError("Unknown action") + } + } } diff --git a/example/ios/Demo/Helpers/MainStoryboard.swift b/example/ios/Demo/Helpers/MainStoryboard.swift new file mode 100644 index 000000000..e2b4829ee --- /dev/null +++ b/example/ios/Demo/Helpers/MainStoryboard.swift @@ -0,0 +1,86 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +enum MainStoryboard { + private static let storyboard = UIStoryboard(name: "Main", bundle: nil) + + static func instantiateAlertsViewController() -> AlertsViewController { + return instantiate(identifier: "AlertsViewController") + } + + static func instantiateArchitectureViewController() -> ArchitectureViewController { + return instantiate(identifier: "Architecture") + } + + static func instantiateArchitectureDetailsViewController() -> ArchitectureDetailsViewController { + return instantiate(identifier: "ArchitectureDetails") + } + + static func instantiateBluetoothDeviceDetailsViewController() -> BluetoothDeviceDetailsViewController { + return instantiate(identifier: "BluetoothDeviceDetails") + } + + static func instantiateBottomSheetViewController() -> BottomSheetViewController { + return instantiate(identifier: "BottomSheet") + } + + static func instantiateBottomSheetSubPageViewController() -> BottomSheetSubPageViewController { + instantiate(identifier: "BottomSheetSubPage") + } + + static func instantiateDateTimePickerViewController() -> DateTimePickerViewController { + return instantiate(identifier: "DateTimePicker") + } + + static func instantiateFeaturesListViewController() -> FeaturesListViewController { + return instantiate(identifier: "FeaturesList") + } + + static func instantiateInfoViewController() -> InfoViewController { + return instantiate(identifier: "InfoViewController") + } + + static func instantiateKeyboardManagerViewController() -> KeyboardManagerViewController { + return instantiate(identifier: "KeyboardManagerViewController") + } + + static func instantiateLinksViewController() -> LinksViewController { + return instantiate(identifier: "LinksViewController") + } + + static func instantiateLoadingViewController() -> LoadingViewController { + return instantiate(identifier: "LoadingView") + } + + static func instantiatePermissionViewController() -> PermissionViewController { + return instantiate(identifier: "Permission") + } + + static func instantiateResourcesListViewController() -> ResourcesListViewController { + return instantiate(identifier: "ResourcesList") + } + + static func instantiateSwiftUIOrUIKitSelectionViewController() -> SwiftUIOrUIKitSelectionViewController { + return instantiate(identifier: "SwiftOrUIKit") + } + + private static func instantiate(identifier: String) -> VC { + // swiftlint:disable:next force_cast + storyboard.instantiateViewController(withIdentifier: identifier) as! VC + } +} diff --git a/example/ios/Demo/Helpers/String+Localized.swift b/example/ios/Demo/Helpers/String+Localized.swift new file mode 100644 index 000000000..2d4129b22 --- /dev/null +++ b/example/ios/Demo/Helpers/String+Localized.swift @@ -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 Foundation + +extension String { + func localized() -> String { + return NSLocalizedString(self, comment: "") + } +} diff --git a/example/ios/Demo/Helpers/UICollectionView+Typed.swift b/example/ios/Demo/Helpers/UICollectionView+Typed.swift new file mode 100644 index 000000000..218be9f7a --- /dev/null +++ b/example/ios/Demo/Helpers/UICollectionView+Typed.swift @@ -0,0 +1,32 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +extension UICollectionView { + + func dequeueTypedReusableCell( + withReuseIdentifier identifier: String, + for indexPath: IndexPath, + asType: (TypedCell) -> Void + ) -> UICollectionViewCell { + let cell = dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) + if let typedCell = cell as? TypedCell { + asType(typedCell) + } + return cell + } +} diff --git a/example/ios/Demo/Helpers/UIControl+Closure.swift b/example/ios/Demo/Helpers/UIControl+Closure.swift index ff193ceb3..ae2512e08 100644 --- a/example/ios/Demo/Helpers/UIControl+Closure.swift +++ b/example/ios/Demo/Helpers/UIControl+Closure.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -18,9 +18,9 @@ import UIKit @objc class UIControlClosure: NSObject { - let closure: ()->() + let closure: () -> Void - init (_ closure: @escaping ()->()) { + init (_ closure: @escaping () -> Void) { self.closure = closure } @@ -30,9 +30,9 @@ import UIKit } extension UIControl { - func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping ()->()) { + func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping () -> Void) { let sleeve = UIControlClosure(closure) addTarget(sleeve, action: #selector(UIControlClosure.invoke), for: controlEvents) - objc_setAssociatedObject(self, "[\(arc4random())]", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + objc_setAssociatedObject(self, "[\(UInt32.random(in: UInt32.min..( + withIdentifier identifier: String, + for indexPath: IndexPath, + asType: (TypedCell) -> Void + ) -> UITableViewCell { + let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) + if let typedCell = cell as? TypedCell { + asType(typedCell) + } + return cell + } +} diff --git a/example/ios/Demo/Info/InfoViewController.swift b/example/ios/Demo/Info/InfoViewController.swift index 2ea9da2bd..be86798dc 100644 --- a/example/ios/Demo/Info/InfoViewController.swift +++ b/example/ios/Demo/Info/InfoViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,64 +16,95 @@ */ import UIKit -import KotlinNativeFramework +import MessageUI +import KalugaExampleShared -class InfoViewController : UITableViewController { - - private lazy var viewModel: InfoViewModel = KNArchitectureFramework().createInfoViewModel(parent: self) +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( + reviewManagerBuilder: ReviewManager.Builder(), + navigator: navigator) private var buttons = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil + private var onSelected: ((Int) -> Void)? 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 + return tableView.dequeueTypedReusableCell(withIdentifier: InfoButtonCell.Const.identifier, for: indexPath) { (cell: InfoButtonCell) in + cell.label.text = buttons[indexPath.row] + } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } - } -class InfoButtonCell : UITableViewCell { +class InfoButtonCell: UITableViewCell { - struct Const { + enum Const { static let identifier = "InfoButtonCell" } @IBOutlet weak var label: UILabel! - } diff --git a/example/ios/Demo/KeyboardManager/KeyboardManagerView.swift b/example/ios/Demo/KeyboardManager/KeyboardManagerView.swift new file mode 100644 index 000000000..0774cdc8c --- /dev/null +++ b/example/ios/Demo/KeyboardManager/KeyboardManagerView.swift @@ -0,0 +1,68 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +struct KeyboardManagerView: View { + + enum Field: String, Hashable { + case textField + } + + let lifecycleViewModel: LifecycleViewModel>>> + @ObservedObject var keyboardManagerBuilder: SwiftUIKeyboardManagerBuilder + @State var text: String = "" + @FocusState private var focusedField: Field? + + init() { + let keyboardManagerBuilder = SwiftUIKeyboardManagerBuilder() + self.keyboardManagerBuilder = keyboardManagerBuilder + let viewModel = KeyboardViewModel(keyboardManagerBuilder: keyboardManagerBuilder, editFieldFocusHandler: swiftUIFocusHandler(value: Field.textField)) + lifecycleViewModel = LifecycleViewModel(viewModel) + } + + var body: some View { + lifecycleViewModel.lifecycleView { viewModel in + VStack(spacing: 10.0) { + TextField("", text: _text.projectedValue) + .focused($focusedField, equals: Field.textField) + .textFieldStyle(.roundedBorder) + viewModel.showButton.toButton( + buttonFrame: .frame(maxWidth: .infinity) + ) + viewModel.hideButton.toButton( + buttonFrame: .frame(maxWidth: .infinity) + ) + Spacer() + } + .padding(10.0) + .navigationTitle( + Text("feature_keyboard".localized()) + ) + .bindKeyboardManager( + keyboardManagerBuilder: keyboardManagerBuilder, + focusState: _focusedField.projectedValue + ) + } + } +} + +struct KeyboardManagerView_Previews: PreviewProvider { + static var previews: some View { + KeyboardManagerView() + } +} diff --git a/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift b/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift index 4b95b01a0..c9e4d4d2f 100644 --- a/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift +++ b/example/ios/Demo/KeyboardManager/KeyboardManagerViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,35 +16,34 @@ */ import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class KeyboardManagerViewController : UIViewController { +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) + UIKitFocusHandler(view: self.editField) }() - lazy var viewModel = KNArchitectureFramework().createKeyboardViewModel(focusHandler: self.editFieldFocusHandler) + lazy var viewModel = KeyboardViewModel( + keyboardManagerBuilder: UIKitKeyboardManager.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 [] }) - } - - @IBAction - func showButtonPressed() { - viewModel.onShowPressed() - } - - @IBAction - func hideButtonPressed() { - viewModel.onHidePressed() + + title = "feature_keyboard".localized() + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [] } + ButtonStyleKt.bindButton(showButton, button: viewModel.showButton) + ButtonStyleKt.bindButton(hideButton, button: viewModel.hideButton) } } diff --git a/example/ios/Demo/Links/LinksViewController.swift b/example/ios/Demo/Links/LinksViewController.swift index 8b252da5d..c807a909d 100644 --- a/example/ios/Demo/Links/LinksViewController.swift +++ b/example/ios/Demo/Links/LinksViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -17,17 +17,19 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class LinksViewController : UIViewController { +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( + linkManagerBuilder: DefaultLinksManager.Builder(), + alertPresenterBuilder: AlertPresenter.Builder(viewController: self), + navigator: navigator + ) private var lifecycleManager: LifecycleManager! deinit { @@ -36,8 +38,10 @@ class LinksViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() + + title = "feature_links".localized() - 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/ActivityViewController.swift b/example/ios/Demo/LoadingIndicator/ActivityViewController.swift index 02e2d7f22..045eea92b 100644 --- a/example/ios/Demo/LoadingIndicator/ActivityViewController.swift +++ b/example/ios/Demo/LoadingIndicator/ActivityViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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/example/ios/Demo/LoadingIndicator/LoadingView.swift b/example/ios/Demo/LoadingIndicator/LoadingView.swift new file mode 100644 index 000000000..9fc31189b --- /dev/null +++ b/example/ios/Demo/LoadingIndicator/LoadingView.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 SwiftUI +import KalugaExampleShared + +struct LoadingView: View { + + let lifecycleViewModel: LifecycleViewModel + + init() { + let containerView = ContainerView(.hud) + let viewModel = HudViewModel(builder: containerView.hudBuilder) + lifecycleViewModel = LifecycleViewModel(viewModel, containerView: containerView) + } + + var body: some View { + lifecycleViewModel.lifecycleView { viewModel in + VStack(spacing: 10.0) { + viewModel.showSystemButton.toButton( + buttonFrame: .frame(maxWidth: .infinity) + ) + viewModel.showCustomButton.toButton( + buttonFrame: .frame(maxWidth: .infinity) + ) + Spacer() + } + } + .padding(10.0) + .navigationTitle(Text("feature_hud".localized())) + } +} + +struct LoadingView_Previews: PreviewProvider { + static var previews: some View { + LoadingView() + } +} diff --git a/example/ios/Demo/LoadingIndicator/LoadingViewController.swift b/example/ios/Demo/LoadingIndicator/LoadingViewController.swift index 56e2f94a7..067f44952 100644 --- a/example/ios/Demo/LoadingIndicator/LoadingViewController.swift +++ b/example/ios/Demo/LoadingIndicator/LoadingViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,29 +16,27 @@ */ import UIKit -import KotlinNativeFramework +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! - + 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: () - } + + title = "feature_hud".localized() + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [] } + ButtonStyleKt.bindButton(systemButton, button: viewModel.showSystemButton) + ButtonStyleKt.bindButton(customButton, button: viewModel.showCustomButton) } } diff --git a/example/ios/Demo/Location/LocationViewController.swift b/example/ios/Demo/Location/LocationViewController.swift index 75b7fdb96..f38a10c2a 100644 --- a/example/ios/Demo/Location/LocationViewController.swift +++ b/example/ios/Demo/Location/LocationViewController.swift @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -18,21 +18,21 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands import UIKit import CoreLocation -import KotlinNativeFramework +import KalugaExampleShared class LocationViewController: UIViewController { - struct Const { + enum Const { static let permission = LocationPermission(background: false, precise: true) } - //MARK: Properties + // MARK: Properties @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,17 @@ class LocationViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - lifecycleManager = viewModel.addLifecycleManager(parent: self, onLifecycle: { [weak self] in + title = "feature_location".localized() + + 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 6c597bfa5..ec2866450 100644 --- a/example/ios/Demo/Permissions/PermissionListViewController.swift +++ b/example/ios/Demo/Permissions/PermissionListViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -16,62 +16,72 @@ */ 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) +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(navigator: navigator) private var lifecycleManager: LifecycleManager! private var permissions = [PermissionView]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + title = "feature_permissions".localized() + + 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 + return tableView.dequeueTypedReusableCell(withIdentifier: PermissionsListCell.Const.identifier, for: indexPath) { (cell: PermissionsListCell) in + cell.label.text = permissions[indexPath.row].title + } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } - } -class PermissionsListCell : UITableViewCell { +class PermissionsListCell: UITableViewCell { - struct Const { + enum Const { static let identifier = "PermissionsListCell" } @IBOutlet weak var label: UILabel! - } diff --git a/example/ios/Demo/Permissions/PermissionViewController.swift b/example/ios/Demo/Permissions/PermissionViewController.swift index ebe37e7d6..46b63ff36 100644 --- a/example/ios/Demo/Permissions/PermissionViewController.swift +++ b/example/ios/Demo/Permissions/PermissionViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -17,71 +17,62 @@ 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) - return vc + let viewController = MainStoryboard.instantiatePermissionViewController() + viewController.viewModel = PermissionViewModel(permission: permission) + return viewController } @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 - + + title = viewModel.title + + requestPermissionButton.setTitle("permission_request".localized(), for: .normal) + + 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)) + + let alert = UIAlertController(title: "permission_request".localized(), message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK".localized(), 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/ButtonView.swift b/example/ios/Demo/Resources/ButtonView.swift new file mode 100644 index 000000000..e7b3dc7b8 --- /dev/null +++ b/example/ios/Demo/Resources/ButtonView.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 SwiftUI +import KalugaExampleShared + +struct ButtonView: View, Equatable { + static func == (lhs: ButtonView, rhs: ButtonView) -> Bool { + return true + } + + let lifecycleViewModel: LifecycleViewModel + @ObservedObject var buttons: ListObservable + + init() { + let containerView = ContainerView(.alert) + let viewModel = ButtonViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider(), alertPresenterBuilder: containerView.alertBuilder) + buttons = ListObservable(viewModel.buttons) + lifecycleViewModel = LifecycleViewModel(viewModel, containerView: containerView) + } + + var body: some View { + lifecycleViewModel.lifecycleView { _ in + ScrollView { + VStack(spacing: 10.0) { + ForEach(buttons.value, id: \.self) { button in + button.toButton(buttonFrame: .frame(maxWidth: .infinity)) + } + } + }.navigationTitle("feature_resources_button".localized()) + } + } +} + +struct ButtonView_Previews: PreviewProvider { + static var previews: some View { + ButtonView() + } +} diff --git a/example/ios/Demo/Resources/ButtonViewController.swift b/example/ios/Demo/Resources/ButtonViewController.swift index d37ffc11f..cda3681f5 100644 --- a/example/ios/Demo/Resources/ButtonViewController.swift +++ b/example/ios/Demo/Resources/ButtonViewController.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 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. @@ -15,11 +15,14 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class ButtonViewController : UITableViewController { +class ButtonViewController: UITableViewController { - private lazy var viewModel: ButtonViewModel = KNArchitectureFramework().createButtonViewModel(parent: self) + private lazy var viewModel = ButtonViewModel( + styledStringBuilderProvider: StyledStringBuilder.Provider(), + alertPresenterBuilder: AlertPresenter.Builder(viewController: self) + ) private var lifecycleManager: LifecycleManager! private var buttons = [KalugaButton]() @@ -30,8 +33,11 @@ class ButtonViewController : UITableViewController { override func viewDidLoad() { super.viewDidLoad() + + title = "feature_resources_button".localized() + 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 @@ -51,18 +57,17 @@ class ButtonViewController : UITableViewController { } 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 + return tableView.dequeueTypedReusableCell(withIdentifier: ButtonListCell.Const.identifier, for: indexPath) { (cell: ButtonListCell) in + ButtonStyleKt.bindButton(cell.button, button: buttons[indexPath.row]) + } } } -class ButtonListCell : UITableViewCell { +class ButtonListCell: UITableViewCell { - struct Const { + enum Const { static let identifier = "ButtonListCell" } @IBOutlet weak var button: UIButton! - } diff --git a/example/ios/Demo/Resources/ColorView.swift b/example/ios/Demo/Resources/ColorView.swift new file mode 100644 index 000000000..17df650b9 --- /dev/null +++ b/example/ios/Demo/Resources/ColorView.swift @@ -0,0 +1,141 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +struct ColorView: View, Equatable { + + static func == (lhs: ColorView, rhs: ColorView) -> Bool { + return true + } + + let lifecycleViewModel: LifecycleViewModel + private let disposeBag = DisposeBag() + @ObservedObject var backdropColorBackground: BackgroundStyleObservable + @ObservedObject var blendedColorBackground: BackgroundStyleObservable + @ObservedObject var sourceColorBackground: BackgroundStyleObservable + + @State var backdropText: String = "" + @State var sourceText: String = "" + + @ObservedObject var blendModeButton: ButtonObservable + @ObservedObject var lightenedBackdrops: ListObservable + @ObservedObject var darkenedBackdrops: ListObservable + @ObservedObject var lightenedBlended: ListObservable + @ObservedObject var darkenedBlended: ListObservable + @ObservedObject var lightenedSource: ListObservable + @ObservedObject var darkenedSource: ListObservable + + init() { + let containerView = ContainerView(.alert) + let viewModel = ColorViewModel(alertPresenterBuilder: containerView.alertBuilder) + backdropColorBackground = BackgroundStyleObservable(viewModel.backdropColorBackground) + blendedColorBackground = BackgroundStyleObservable(viewModel.blendedColorBackground) + sourceColorBackground = BackgroundStyleObservable(viewModel.sourceColorBackground) + lifecycleViewModel = LifecycleViewModel(viewModel, containerView: containerView) + + blendModeButton = ButtonObservable(viewModel.blendModeButton) + + lightenedBackdrops = ListObservable(viewModel.lightenBackdrops) + darkenedBackdrops = ListObservable(viewModel.darkenBackdrops) + lightenedBlended = ListObservable(viewModel.lightenBlended) + darkenedBlended = ListObservable(viewModel.darkenBlended) + lightenedSource = ListObservable(viewModel.lightenSource) + darkenedSource = ListObservable(viewModel.darkenSource) + } + + var body: some View { + lifecycleViewModel.lifecycleView { viewModel in + ScrollView { + HStack(alignment: .top, spacing: 10.0) { + VStack(alignment: .center, spacing: 5.0) { + Spacer() + .frame(width: 100, height: 100, alignment: .center) + .background(backdropColorBackground.value) + TextField("", text: $backdropText) + .onSubmit { + viewModel.submitBackdropText(backdropText: backdropText) + } + .textFieldStyle(.roundedBorder) + } + .frame(maxWidth: .infinity, alignment: .center) + Spacer() + .frame(width: 100, height: 100, alignment: .center) + .background(blendedColorBackground.value) + VStack(alignment: .center, spacing: 5.0) { + Spacer() + .frame(width: 100, height: 100, alignment: .center) + .background(sourceColorBackground.value) + TextField("", text: $sourceText).onSubmit { + viewModel.submitSourceText(sourceText: sourceText) + } + .textFieldStyle(.roundedBorder) + } + .frame(maxWidth: .infinity, alignment: .center) + } + blendModeButton.value.toButton() + viewModel.flipButton.toButton() + lightenedBackdrops.asHorizontalScrollView() + darkenedBackdrops.asHorizontalScrollView() + lightenedBlended.asHorizontalScrollView() + darkenedBlended.asHorizontalScrollView() + lightenedSource.asHorizontalScrollView() + darkenedSource.asHorizontalScrollView() + } + .navigationTitle("feature_resources_color".localized()) + .onAppear { + disposeBag.add( + disposable: viewModel.backdropText.observe { next in + if let backdrop = next as? String { + backdropText = backdrop + } + } + ) + disposeBag.add( + disposable: viewModel.sourceText.observe { next in + if let source = next as? String { + sourceText = source + } + } + ) + } + .onDisappear { + disposeBag.dispose() + } + } + } +} + +extension ListObservable { + func asHorizontalScrollView() -> some View { + ScrollView(.horizontal) { + HStack(spacing: 2.0) { + ForEach(value, id: \.self) { backgroundStyle in + Spacer() + .frame(width: 50.0, height: 50.0) + .background(backgroundStyle) + } + }.padding(.horizontal, 10.0) + } + } +} + +struct ColorView_Previews: PreviewProvider { + static var previews: some View { + ColorView() + } +} diff --git a/example/ios/Demo/Resources/ColorViewController.swift b/example/ios/Demo/Resources/ColorViewController.swift index 650c97d11..2bc7e16a7 100644 --- a/example/ios/Demo/Resources/ColorViewController.swift +++ b/example/ios/Demo/Resources/ColorViewController.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 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. @@ -15,9 +15,9 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class ColorViewController : UIViewController { +class ColorViewController: UIViewController { @IBOutlet var backdropColorBackground: UIView! @IBOutlet var sourceColorBackground: UIView! @@ -36,14 +36,14 @@ class ColorViewController : UIViewController { @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 var backdropLightenedColors: [KalugaBackgroundStyle] = [] + private var backdropDarkenedColors: [KalugaBackgroundStyle] = [] + private var sourceLightenedColors: [KalugaBackgroundStyle] = [] + private var sourceDarkenedColors: [KalugaBackgroundStyle] = [] + private var blendedLightenedColors: [KalugaBackgroundStyle] = [] + private var blendedDarkenedColors: [KalugaBackgroundStyle] = [] - private lazy var viewModel: ColorViewModel = KNArchitectureFramework().createColorViewModel(parent: self) + private lazy var viewModel = ColorViewModel(alertPresenterBuilder: AlertPresenter.Builder(viewController: self)) private var lifecycleManager: LifecycleManager! deinit { @@ -52,30 +52,33 @@ class ColorViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() + + title = "feature_resources_color".localized() + 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 + 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 { + guard let backdropColorBackground = self?.backdropColorBackground, let backdropBackgroundStyle = next else { return } BackgroundStyleKt.applyBackgroundStyle(backdropColorBackground, style: backdropBackgroundStyle) }, viewModel.sourceColorBackground.observe { next in - guard let sourceColorBackground = self?.sourceColorBackground, let sourceBackgroundStyle = next as? BackgroundStyle else { + guard let sourceColorBackground = self?.sourceColorBackground, let sourceBackgroundStyle = next else { return } BackgroundStyleKt.applyBackgroundStyle(sourceColorBackground, style: sourceBackgroundStyle) }, viewModel.blendedColorBackground.observe { next in - guard let blendedColorBackground = self?.blendedColorBackground, let blendedBackgroundStyle = next as? BackgroundStyle else { + guard let blendedColorBackground = self?.blendedColorBackground, let blendedBackgroundStyle = next else { return } BackgroundStyleKt.applyBackgroundStyle(blendedColorBackground, style: blendedBackgroundStyle) @@ -93,29 +96,29 @@ class ColorViewController : UIViewController { ButtonStyleKt.bindButton(blendModeButton, button: buttonStyle) }, viewModel.lightenBackdrops.observe { next in - self?.backdropLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.backdropLightenedColors = next?.compactMap { $0 as? KalugaBackgroundStyle } ?? [] self?.backdropLightenedColorsCollectionView?.reloadData() }, viewModel.darkenBackdrops.observe { next in - self?.backdropDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.backdropDarkenedColors = next?.compactMap { $0 as? KalugaBackgroundStyle } ?? [] self?.backdropDarkenedColorsCollectionView?.reloadData() }, viewModel.lightenSource.observe { next in - self?.sourceLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.sourceLightenedColors = next?.compactMap { $0 as? KalugaBackgroundStyle } ?? [] self?.sourceLightenedColorsCollectionView?.reloadData() }, viewModel.darkenSource.observe { next in - self?.sourceDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.sourceDarkenedColors = next?.compactMap { $0 as? KalugaBackgroundStyle } ?? [] self?.sourceDarkenedColorsCollectionView?.reloadData() }, viewModel.lightenBlended.observe { next in - self?.blendedLightenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.blendedLightenedColors = next?.compactMap { $0 as? KalugaBackgroundStyle } ?? [] self?.blendedLightenedColorsCollectionView?.reloadData() }, viewModel.darkenBlended.observe { next in - self?.blendedDarkenedColors = next?.compactMap({ $0 as? BackgroundStyle }) ?? [] + self?.blendedDarkenedColors = next?.compactMap { $0 as? KalugaBackgroundStyle } ?? [] self?.blendedDarkenedColorsCollectionView?.reloadData() - }, + } ] } @@ -123,7 +126,7 @@ class ColorViewController : UIViewController { } } -extension ColorViewController : UITextFieldDelegate { +extension ColorViewController: UITextFieldDelegate { func textFieldDidEndEditing(_ textField: UITextField) { if textField == backdropInputField { viewModel.submitBackdropText(backdropText: textField.text ?? "") @@ -134,7 +137,7 @@ extension ColorViewController : UITextFieldDelegate { } } -extension ColorViewController : UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { +extension ColorViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if collectionView == backdropLightenedColorsCollectionView { @@ -163,32 +166,35 @@ extension ColorViewController : UICollectionViewDataSource, UICollectionViewDele } 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 collectionView.dequeueTypedReusableCell( + withReuseIdentifier: ColorCollectionCellView.Const.identifier, + for: indexPath + ) { (cell: ColorCollectionCellView) in + 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 { +class ColorCollectionCellView: UICollectionViewCell { - struct Const { + enum Const { static let identifier = "ColorCollectionCellView" } diff --git a/example/ios/Demo/Resources/ImagesView.swift b/example/ios/Demo/Resources/ImagesView.swift new file mode 100644 index 000000000..fb249fcce --- /dev/null +++ b/example/ios/Demo/Resources/ImagesView.swift @@ -0,0 +1,55 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +struct ImagesView: View { + + let lifecycleViewModel: LifecycleViewModel + + init() { + let viewModel = ImagesViewModel() + lifecycleViewModel = LifecycleViewModel(viewModel) + } + + var body: some View { + lifecycleViewModel.lifecycleView { viewModel in + ScrollView { + HStack { + VStack(spacing: 10.0) { + ForEach(viewModel.images, id: \.self) { image in + Image(uiImage: image) + } + } + .frame(maxWidth: .infinity) + VStack(spacing: 10.0) { + ForEach(viewModel.tintedImages, id: \.self) { tintedImage in + tintedImage.swiftUI + } + } + .frame(maxWidth: .infinity) + } + }.navigationTitle("feature_resources_image".localized()) + } + } +} + +struct ImagesView_Previews: PreviewProvider { + static var previews: some View { + ImagesView() + } +} diff --git a/example/ios/Demo/Resources/ImagesViewController.swift b/example/ios/Demo/Resources/ImagesViewController.swift new file mode 100644 index 000000000..f62356596 --- /dev/null +++ b/example/ios/Demo/Resources/ImagesViewController.swift @@ -0,0 +1,71 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 ImagesViewController: UITableViewController { + + private var viewModel = ImagesViewModel() + private var lifecycleManager: LifecycleManager! + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "feature_resources_image".localized() + + tableView.allowsSelection = false + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [] } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return max(viewModel.images.count, viewModel.tintedImages.count) + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return tableView.dequeueTypedReusableCell(withIdentifier: ImageListCell.Const.identifier, for: indexPath) { (cell: ImageListCell) in + let index = indexPath.row + if index < viewModel.images.count { + cell.plainImage.image = viewModel.images[index] + } else { + cell.plainImage.image = nil + } + if index < viewModel.tintedImages.count { + cell.tintedImage.image = viewModel.tintedImages[index].uiImage + } else { + cell.tintedImage.image = nil + } + } + } +} + +class ImageListCell: UITableViewCell { + + enum Const { + static let identifier = "ImageListCell" + } + + @IBOutlet weak var plainImage: UIImageView! + @IBOutlet weak var tintedImage: UIImageView! +} diff --git a/example/ios/Demo/Resources/LabelView.swift b/example/ios/Demo/Resources/LabelView.swift new file mode 100644 index 000000000..3d12f3c9e --- /dev/null +++ b/example/ios/Demo/Resources/LabelView.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 SwiftUI +import KalugaExampleShared + +struct LabelView: View { + + let lifecycleViewModel: LifecycleViewModel + @ObservedObject var labels: ListObservable + + init() { + let viewModel = LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider()) + labels = ListObservable(viewModel.labels) + lifecycleViewModel = LifecycleViewModel(viewModel) + } + + var body: some View { + lifecycleViewModel.lifecycleView { _ in + ScrollView { + VStack(spacing: 10.0) { + ForEach(labels.value, id: \.self) { label in + label.toText().frame(maxWidth: .infinity) + } + } + }.navigationTitle("feature_resources_button".localized()) + } + } +} + +struct LabelView_Previews: PreviewProvider { + static var previews: some View { + LabelView() + } +} diff --git a/example/ios/Demo/Resources/LabelViewController.swift b/example/ios/Demo/Resources/LabelViewController.swift index a7ea95f68..a26f0dac9 100644 --- a/example/ios/Demo/Resources/LabelViewController.swift +++ b/example/ios/Demo/Resources/LabelViewController.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 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. @@ -15,11 +15,11 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class LabelViewController : UITableViewController { - - private lazy var viewModel: LabelViewModel = KNArchitectureFramework().createLabelViewModel() +class LabelViewController: UITableViewController { + + private var viewModel = LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider()) private var lifecycleManager: LifecycleManager! private var labels = [KalugaLabel]() @@ -30,13 +30,15 @@ class LabelViewController : UITableViewController { override func viewDidLoad() { super.viewDidLoad() + + title = "feature_resources_label".localized() 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() } ] @@ -52,18 +54,17 @@ class LabelViewController : UITableViewController { } 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 + return tableView.dequeueTypedReusableCell(withIdentifier: LabelListCell.Const.identifier, for: indexPath) { (cell: LabelListCell) in + TextStyleKt.bindLabel(cell.label, label: labels[indexPath.row]) + } } } -class LabelListCell : UITableViewCell { +class LabelListCell: UITableViewCell { - struct Const { + enum Const { static let identifier = "LabelListCell" } @IBOutlet weak var label: UILabel! - } diff --git a/example/ios/Demo/Resources/ResourcesListView.swift b/example/ios/Demo/Resources/ResourcesListView.swift new file mode 100644 index 000000000..326329052 --- /dev/null +++ b/example/ios/Demo/Resources/ResourcesListView.swift @@ -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. +// + +import SwiftUI +import KalugaExampleShared + +struct ResourcesListView: View { + + @ObservedObject var navigationState = ObjectRoutingState() + @ObservedObject var resources: ListObservable + let lifecycleViewModel: LifecycleViewModel + let router = Router() + + init() { + + let viewModel = ResourcesListViewModel(navigator: router.defaultNavigator) + resources = ListObservable(viewModel.resources) + lifecycleViewModel = LifecycleViewModel(viewModel) + } + + var body: some View { + router.nextRouter = navigationState + return generateBody().navigationTitle("feature_resources".localized()) + } + + func generateBody() -> some View { + lifecycleViewModel.lifecycleView { viewModel in + ScrollView { + VStack(spacing: 10.0) { + ForEach(resources.value, id: \.self) { resource in + Button(resource.title) { + viewModel.onResourceSelected(resource: resource) + } + } + } + } + .navigation(state: navigationState, type: .push) { + switch navigationState.object { + case .buttons: ButtonView().equatable() // For lifecycle subviews it is recommended to use equatable + case .labels: LabelView() + case .images: ImagesView() + case .colors: ColorView().equatable() + default: EmptyView() + } + } + } + } +} + +extension ResourcesListView { + enum Route: Equatable { + case buttons + case labels + case images + case colors + } + + class Router { + + var nextRouter: ObjectRoutingState? + lazy var defaultNavigator: DefaultNavigator = DefaultNavigator { [weak self] action in + self?.nextRouter?.show(action.route) + } + } +} + +extension ResourcesListNavigationAction { + var route: ResourcesListView.Route { + switch self { + case is ResourcesListNavigationAction.Button: return .buttons + case is ResourcesListNavigationAction.Color: return .colors + case is ResourcesListNavigationAction.Image: return .images + case is ResourcesListNavigationAction.Label: return .labels + default: fatalError("Unknown action \(self)") + } + } +} + +struct ResourcesListView_Previews: PreviewProvider { + static var previews: some View { + ResourcesListView() + } +} diff --git a/example/ios/Demo/Resources/ResourcesListViewController.swift b/example/ios/Demo/Resources/ResourcesListViewController.swift index 8dfc14672..2efa06779 100644 --- a/example/ios/Demo/Resources/ResourcesListViewController.swift +++ b/example/ios/Demo/Resources/ResourcesListViewController.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 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. @@ -15,60 +15,83 @@ // import UIKit -import KotlinNativeFramework +import KalugaExampleShared -class ResourcesListViewController : UITableViewController { +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(navigator: navigator) private var lifecycleManager: LifecycleManager! private var resources = [String]() - private var onSelected: ((KotlinInt) -> KotlinUnit)? = nil - + private var onSelected: ((Int) -> Void)? + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = KNArchitectureFramework().bind(viewModel: viewModel, to: self) { [weak self] in + + title = "feature_resources".localized() + + 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 + if let resource = resources[index] as? Resource { + viewModel.onResourceSelected(resource: 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 + return tableView.dequeueTypedReusableCell(withIdentifier: ResourcesListCell.Const.identifier, for: indexPath) { (cell: ResourcesListCell) in + cell.label.text = resources[indexPath.row] + } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onSelected?(KotlinInt.init(int: Int32(indexPath.row))) + _ = onSelected?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } - } -class ResourcesListCell : UITableViewCell { +class ResourcesListCell: UITableViewCell { - struct Const { + enum Const { static let identifier = "ResourcesListCell" } @IBOutlet weak var label: UILabel! - +} + +private extension ResourcesListNavigationAction { + var segueKey: String { + switch self { + case is ResourcesListNavigationAction.Button: return "showButton" + case is ResourcesListNavigationAction.Color: return "showColor" + case is ResourcesListNavigationAction.Image: return "showImage" + case is ResourcesListNavigationAction.Label: return "showLabel" + default: return "" + } + } } diff --git a/example/ios/Demo/Scientific/ScientificConverterView.swift b/example/ios/Demo/Scientific/ScientificConverterView.swift new file mode 100644 index 000000000..e2e2fe20f --- /dev/null +++ b/example/ios/Demo/Scientific/ScientificConverterView.swift @@ -0,0 +1,132 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +struct ScientificConverterView: View, Equatable { + + static func == (lhs: ScientificConverterView, rhs: ScientificConverterView) -> Bool { + lhs.arguments == rhs.arguments + } + + let arguments: ScientificConverterViewModel.Arguments + private let lifecycleViewModel: LifecycleViewModel + private let router = Router() + @ObservedObject private var leftValue: StringSubject + @ObservedObject private var currentLeftUnitButton: ButtonObservable + @ObservedObject private var rightValue: StringSubject + @ObservedObject private var currentRightUnitButton: ButtonObservable + @ObservedObject private var resultValue: StringObservable + @ObservedObject private var nextRoute = ObjectRoutingState() + @EnvironmentObject var previousRoute: RoutingState + + init(arguments: ScientificConverterViewModel.Arguments) { + self.arguments = arguments + + let viewModel = ScientificConverterViewModelKt.ScientificConverterViewModel( + arguments: arguments, + navigator: router.navigator + ) + + leftValue = StringSubject(viewModel.leftValue) + currentLeftUnitButton = ButtonObservable(viewModel.currentLeftUnitButton) + rightValue = StringSubject(viewModel.rightValue) + currentRightUnitButton = ButtonObservable(viewModel.currentRightUnitButton) + resultValue = StringObservable(viewModel.resultValue) + + lifecycleViewModel = LifecycleViewModel(viewModel) + } + + var body: some View { + router.previousRoutingState = previousRoute + router.nextRoutingState = nextRoute + return generateBody().navigationTitle("feature_scientific".localized()) + } + + func generateBody() -> some View { + lifecycleViewModel.lifecycleView { viewModel in + ScrollView { + VStack(spacing: 10) { + HStack(alignment: .center, spacing: 10.0) { + HStack { + TextField("", text: $leftValue.value) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: .infinity) + currentLeftUnitButton.value.toButton() + }.frame(maxWidth: .infinity) + if viewModel.isRightUnitSelectable { + Text(viewModel.calculateOperatorSymbol) + HStack { + TextField("", text: $rightValue.value) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: .infinity) + currentRightUnitButton.value.toButton() + }.frame(maxWidth: .infinity) + } + } + viewModel.calculateButton.toButton(buttonFrame: .frame(maxWidth: .infinity)) + HStack { + Text(resultValue.value) + } + } + } + .padding(10.0) + .navigation(state: nextRoute, type: .sheet) { + if let routeObject = nextRoute.object { + switch routeObject { + case let .leftUnit(quantity): + ScientificUnitSelectionView(quantity: quantity) { index in + viewModel.didSelectLeftUnit(unitIndex: index) + } + .equatable() + .environmentObject(nextRoute as RoutingState) + case let .rightUnit(quantity): + ScientificUnitSelectionView(quantity: quantity) { index in + viewModel.didSelectRightUnit(unitIndex: index) + } + .equatable() + .environmentObject(nextRoute as RoutingState) + } + } + } + } + } +} + +extension ScientificConverterView { + + enum Route: Equatable { + case leftUnit(quantity: ScientificPhysicalQuantity) + case rightUnit(quantity: ScientificPhysicalQuantity) + } + + class Router { + var previousRoutingState: RoutingState? + var nextRoutingState: ObjectRoutingState? + lazy var navigator = ScientificConverterNavigatorKt.ScientificConverterNavigator( + onSelectLeftUnit: { [weak self] quantity in + self?.nextRoutingState?.show(.leftUnit(quantity: quantity)) + }, + onSelectRightUnit: { [weak self] quantity in + self?.nextRoutingState?.show(.rightUnit(quantity: quantity)) + }, + onClose: { [weak self] in + self?.previousRoutingState?.close() + } + ) + } +} diff --git a/example/ios/Demo/Scientific/ScientificUnitSelectionView.swift b/example/ios/Demo/Scientific/ScientificUnitSelectionView.swift new file mode 100644 index 000000000..6404bcdcc --- /dev/null +++ b/example/ios/Demo/Scientific/ScientificUnitSelectionView.swift @@ -0,0 +1,87 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +struct ScientificUnitSelectionView: View, Equatable { + + static func == (lhs: ScientificUnitSelectionView, rhs: ScientificUnitSelectionView) -> Bool { + lhs.quantity == rhs.quantity + } + + private let lifecycleViewModel: LifecycleViewModel + + let quantity: ScientificPhysicalQuantity + let router: Router + @ObservedObject var filter: StringSubject + @ObservedObject var currentUnits: ListObservable + @EnvironmentObject var previousRoute: RoutingState + + init(quantity: ScientificPhysicalQuantity, onUnitIndex: @escaping (Int32) -> Void) { + self.quantity = quantity + router = Router(onUnitIndex: onUnitIndex) + let viewModel = ScientificUnitSelectionViewModel(quantity: quantity, navigator: router.navigator) + + filter = StringSubject(viewModel.filter) + currentUnits = ListObservable(viewModel.currentUnits) + + lifecycleViewModel = LifecycleViewModel(viewModel) + } + + var body: some View { + router.previousRoute = previousRoute + return generateBody() + } + + func generateBody() -> some View { + lifecycleViewModel.lifecycleView { _ in + VStack { + HStack { + Text("Select Unit").padding(10.0) + } + ScrollView { + VStack { + TextField("", text: $filter.value) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: .infinity) + ForEach(currentUnits.value, id: \.self) { button in + button.toButton(buttonFrame: .frame(maxWidth: .infinity)) + } + } + }.padding(10.0) + } + } + } +} + +extension ScientificUnitSelectionView { + class Router { + + private let onUnitIndex: ((Int32) -> Void)? + var previousRoute: RoutingState? + lazy var navigator = ScientificUnitSelectionNavigatorKt.ScientificUnitSelectionNavigator { [weak self] index in + self?.onUnitIndex?(index.int32Value) + self?.previousRoute?.close() + } onCancelled: { [weak self] in + self?.previousRoute?.close() + } + + init(onUnitIndex: @escaping (Int32) -> Void) { + self.onUnitIndex = onUnitIndex + } + } +} diff --git a/example/ios/Demo/Scientific/ScientificView.swift b/example/ios/Demo/Scientific/ScientificView.swift new file mode 100644 index 000000000..ef41e5f67 --- /dev/null +++ b/example/ios/Demo/Scientific/ScientificView.swift @@ -0,0 +1,150 @@ +// +// Copyright 2023 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +struct ScientificView: View { + + private let lifecycleViewModel: LifecycleViewModel + private let router = Router() + + @ObservedObject private var quantityDetailsButton: ButtonObservable + @ObservedObject private var leftValue: StringSubject + @ObservedObject private var currentLeftUnitButton: ButtonObservable + @ObservedObject private var rightValue: StringObservable + @ObservedObject private var currentRightUnitButton: ButtonObservable + @ObservedObject private var converters: ListObservable + @ObservedObject private var route = ObjectRoutingState() + @ObservedObject private var converterRoute = IdentifiableObjectRoutingState() + + init() { + let containerView = ContainerView(.alert) + let viewModel = ScientificViewModel(alertPresenterBuilder: containerView.alertBuilder, navigator: router.navigator) + + quantityDetailsButton = ButtonObservable(viewModel.quantityDetailsButton) + leftValue = StringSubject(viewModel.leftValue) + currentLeftUnitButton = ButtonObservable(viewModel.currentLeftUnitButton) + rightValue = StringObservable(viewModel.rightValue) + currentRightUnitButton = ButtonObservable(viewModel.currentRightUnitButton) + converters = ListObservable(viewModel.converters) + + lifecycleViewModel = LifecycleViewModel(viewModel, containerView: containerView) + } + + var body: some View { + router.routingState = route + router.converterRoutingState = converterRoute + return generateBody().navigationTitle("feature_scientific".localized()) + } + + func generateBody() -> some View { + lifecycleViewModel.lifecycleView { viewModel in + ScrollView { + VStack(spacing: 10) { + quantityDetailsButton.value.toButton(buttonFrame: .frame(maxWidth: .infinity)) + Text("Convert:") + HStack(alignment: .center, spacing: 10.0) { + HStack { + TextField("", text: $leftValue.value) + .textFieldStyle(.roundedBorder) + .frame(maxWidth: .infinity) + currentLeftUnitButton.value.toButton() + }.frame(maxWidth: .infinity) + HStack { + Text(rightValue.value) + .frame(maxWidth: .infinity) + currentRightUnitButton.value.toButton() + }.frame(maxWidth: .infinity) + } + viewModel.calculateButton.toButton(buttonFrame: .frame(maxWidth: .infinity)) + Text("Converters:") + ForEach(converters.value) { converter in + converter.button.toButton(buttonFrame: .frame(maxWidth: .infinity)).navigation( + state: converterRoute, + id: converter.id, + type: .push, + didSelect: converter.button.action + ) { + if let route = converterRoute.object { + switch route { + case let .converter(arguments): ScientificConverterView(arguments: arguments) + .equatable() + .environmentObject(converterRoute as RoutingState) + } + } + } + } + } + } + .padding(.horizontal, 10.0) + .navigation(state: route, type: .sheet) { + if let routeObject = route.object { + switch routeObject { + case let .leftUnit(quantity): + ScientificUnitSelectionView(quantity: quantity) { index in + viewModel.didSelectLeftUnit(unitIndex: index) + } + .equatable() + .environmentObject(route as RoutingState) + case let .rightUnit(quantity): + ScientificUnitSelectionView(quantity: quantity) { index in + viewModel.didSelectRightUnit(unitIndex: index) + } + .equatable() + .environmentObject(route as RoutingState) + } + } + } + } + } +} + +extension ScientificView { + + enum Route: Equatable { + case leftUnit(quantity: ScientificPhysicalQuantity) + case rightUnit(quantity: ScientificPhysicalQuantity) + } + + enum ConverterRoute: Identifiable, Equatable { + var id: String { + switch self { + case let .converter(arguments): return arguments.id + } + } + + case converter(arguments: ScientificConverterViewModel.Arguments) + } + + class Router { + var routingState: ObjectRoutingState? + var converterRoutingState: IdentifiableObjectRoutingState? + lazy var navigator = ScientificNavigatorKt.ScientificNavigator( + onSelectLeftUnit: { [weak self] quantity in + self?.routingState?.show(.leftUnit(quantity: quantity)) + }, + onSelectRightUnit: { [weak self] quantity in + self?.routingState?.show(.rightUnit(quantity: quantity)) + }, + onConverter: { [weak self] arguments in + self?.converterRoutingState?.show(.converter(arguments: arguments)) + } + ) + } +} + +extension ScientificViewModel.Button: Identifiable {} diff --git a/example/ios/Demo/System/Network/NetworkViewController.swift b/example/ios/Demo/System/Network/NetworkViewController.swift index 693477508..0ee8a9d03 100644 --- a/example/ios/Demo/System/Network/NetworkViewController.swift +++ b/example/ios/Demo/System/Network/NetworkViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -17,15 +17,14 @@ import Foundation import UIKit -import KotlinNativeFramework +import KalugaExampleShared + +class NetworkViewController: UIViewController { -class NetworkViewController : UIViewController { - - private let knArchitectureFramework = KNArchitectureFramework() @IBOutlet weak var networkStateText: UILabel! private var lifecycleManager: LifecycleManager! - private lazy var viewModel: NetworkViewModel = NetworkViewModel(networkStateRepoBuilder: NetworkStateRepoBuilder()) + private lazy var viewModel = NetworkViewModel(networkStateRepoBuilder: NetworkStateRepoBuilder()) deinit { lifecycleManager.unbind() @@ -33,16 +32,17 @@ class NetworkViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() + + title = "network_feature".localized() - 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 3f1a57ee4..39d349771 100644 --- a/example/ios/Demo/System/SystemViewController.swift +++ b/example/ios/Demo/System/SystemViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -17,67 +17,71 @@ 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 var modules = [String]() - private var onModuleTapped: ((KotlinInt) -> KotlinUnit)? = nil +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(navigator: navigator) + + private var systemFeatures = [String]() + private var onSystemFeatureTapped: ((Int) -> Void)? private var lifecycleManager: LifecycleManager! - + deinit { lifecycleManager.unbind() } - + override func viewDidLoad() { super.viewDidLoad() - - lifecycleManager = knArchitectureFramework.bind(viewModel: viewModel, to: self) { [weak self] in + + title = "feature_system".localized() + + 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.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() } ] } } - + override func numberOfSections(in tableView: UITableView) -> Int { return 1 } - + 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] - return cell + return tableView.dequeueTypedReusableCell(withIdentifier: SystemListCell.Const.identifier, for: indexPath) { (cell: SystemListCell) in + cell.label.text = systemFeatures[indexPath.row] + } } - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let _ = onModuleTapped?(KotlinInt.init(int: Int32(indexPath.row))) + _ = onSystemFeatureTapped?(indexPath.row) tableView.deselectRow(at: indexPath, animated: true) } - } -class SystemListCell : UITableViewCell { +class SystemListCell: UITableViewCell { - struct Const { + enum Const { static let identifier = "SystemListCell" } @IBOutlet weak var label: UILabel! - } diff --git a/example/ios/Demo/UITypeSelection/SwiftUIOrUIKitSelectionViewController.swift b/example/ios/Demo/UITypeSelection/SwiftUIOrUIKitSelectionViewController.swift new file mode 100644 index 000000000..87b557def --- /dev/null +++ b/example/ios/Demo/UITypeSelection/SwiftUIOrUIKitSelectionViewController.swift @@ -0,0 +1,98 @@ +// +// Copyright 2022 Splendo Consulting B.V. The Netherlands +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 SwiftUI +import KalugaExampleShared + +class SwiftUIOrUIKitSelectionViewController: UITableViewController { + + static func create(uiKitViewController: @escaping () -> UIViewController, swiftUIView: @escaping () -> some View) -> SwiftUIOrUIKitSelectionViewController { + let viewController = MainStoryboard.instantiateSwiftUIOrUIKitSelectionViewController() + + let navigator = ViewControllerNavigator(parentVC: viewController) { action in + switch action { + case is SwiftUIOrUIKitNavigationAction.SwiftUI: return NavigationSpec.Push(animated: true) { + UIHostingController(rootView: swiftUIView()) + } + case is SwiftUIOrUIKitNavigationAction.UIKit: return NavigationSpec.Push(animated: true, push: uiKitViewController) + default: fatalError("Unknown navigation action \(action)") + } + } + viewController.viewModel = SwiftUIOrUIKitSelectionViewModel(navigator: navigator) + return viewController + } + + var viewModel: SwiftUIOrUIKitSelectionViewModel! + private var lifecycleManager: LifecycleManager! + + private var uiTypes = [String]() + private var onSelected: ((Int) -> Void)? + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "ios_ui_type_selector".localized() + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + return [ + viewModel.uiTypes.observeInitialized { next in + let uiTypes = next ?? [] + self?.uiTypes = uiTypes.map { ($0 as? UIType)?.title ?? "" } + self?.onSelected = { (index: Int) in + if let uiType = uiTypes[index] as? UIType { + viewModel.onUITypePressed(uiType: uiType) + } + } + self?.tableView.reloadData() + } + ] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return uiTypes.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return tableView.dequeueTypedReusableCell(withIdentifier: UITypesListCell.Const.identifier, for: indexPath) { (cell: UITypesListCell) in + cell.label.text = uiTypes[indexPath.row] + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + _ = onSelected?(indexPath.row) + tableView.deselectRow(at: indexPath, animated: true) + } +} + +class UITypesListCell: UITableViewCell { + + enum Const { + static let identifier = "UITypesListCell" + } + + @IBOutlet weak var label: UILabel! +} diff --git a/example/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift b/example/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift index b130f74db..0c6b41e04 100644 --- a/example/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift +++ b/example/ios/Demo/View/FittingWidthAutomaticHeightCollectionViewFlowLayout.swift @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -20,20 +20,20 @@ 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 + 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() + fatalError("CollectionView should not be empty when being layed out") } guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else { return nil @@ -44,8 +44,10 @@ final class FittingWidthAutomaticHeightCollectionViewFlowLayout: UICollectionVie return layoutAttributes } - override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, - withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool { + override func shouldInvalidateLayout( + forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, + withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes + ) -> Bool { return true } } diff --git a/example/ios/DemoTests/DemoTests.swift b/example/ios/DemoTests/DemoTests.swift index ba18e086b..9d36d12b2 100644 --- a/example/ios/DemoTests/DemoTests.swift +++ b/example/ios/DemoTests/DemoTests.swift @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -18,7 +18,7 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands import XCTest @testable import Demo -import KotlinNativeFramework +import KalugaExampleShared class DemoTests: XCTestCase { @@ -31,9 +31,7 @@ class DemoTests: XCTestCase { } 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. + assert(HelloSharedKt.helloCommon() == "Hello from the shared module common source") } func testPerformanceExample() { @@ -42,5 +40,4 @@ class DemoTests: XCTestCase { // Put the code you want to measure the time of here. } } - } diff --git a/example/ios/DemoUITests/DemoUITests.swift b/example/ios/DemoUITests/DemoUITests.swift index af63caa53..b354e7719 100644 --- a/example/ios/DemoUITests/DemoUITests.swift +++ b/example/ios/DemoUITests/DemoUITests.swift @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -29,7 +29,8 @@ class DemoUITests: XCTestCase { // 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. + // 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() { @@ -40,5 +41,4 @@ class DemoUITests: XCTestCase { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } - } diff --git a/example/ios/Kaluga-SwiftUI b/example/ios/Kaluga-SwiftUI new file mode 160000 index 000000000..46b0561e2 --- /dev/null +++ b/example/ios/Kaluga-SwiftUI @@ -0,0 +1 @@ +Subproject commit 46b0561e2195d43bcad4efb5c715096447692b84 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 51cb090c9..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/KotlinNativeFramework.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - -Copyright 2019 Splendo Consulting B.V. The Netherlands - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 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 fa0fb3792..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/architecture/KNArchitectureFramework.kt +++ /dev/null @@ -1,368 +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. - - */ - -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 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 -> - 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 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 { - 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]) } - } -} - -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/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt b/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt deleted file mode 100644 index e00f0884c..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/beacons/KNBeaconsFramework.kt +++ /dev/null @@ -1,38 +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. - - */ - -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 kotlinx.coroutines.MainScope -import permissions.KNPermissionsFramework - -class KNBeaconsFramework { - private val mainScope = MainScope() - val service = Beacons( - BluetoothBuilder().create( - { BaseScanner.Settings(KNPermissionsFramework().getPermissions()) }, - ConnectionSettings(), - singleThreadDispatcher("Bluetooth") - ), - timeoutMs = 60_000 - ) -} 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 7cfc52e20..000000000 --- a/example/ios/KotlinNativeFramework/src/commonMain/kotlin/bluetooth/KNBluetoothFramework.kt +++ /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. - - */ - -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 - -@kotlin.native.concurrent.ThreadLocal -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 91ca28c8b..000000000 Binary files a/example/ios/Supporting Files/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/example/ios/Supporting Files/settings.gradle.kts b/example/ios/Supporting Files/settings.gradle.kts deleted file mode 100644 index 24a4da1ff..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") - -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/ios/install.sh b/example/ios/install.sh new file mode 100755 index 000000000..66918f9b4 --- /dev/null +++ b/example/ios/install.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +sourcery +sourcery --config ./kaluga.sourcery.yml diff --git a/example/ios/kaluga.sourcery.yml b/example/ios/kaluga.sourcery.yml new file mode 100644 index 000000000..3c8affada --- /dev/null +++ b/example/ios/kaluga.sourcery.yml @@ -0,0 +1,20 @@ +project: + file: ./Demo.xcodeproj + target: + name: Demo +templates: + - Kaluga-SwiftUI/stencils/ +output: + path: ./Generated/KalugaSwiftUI + link: + project: ./Demo.xcodeproj + targets: [Demo] + group: Generated/KalugaSwiftUI +args: + sharedFrameworkName: KalugaExampleShared + includeResources: true + includeAlerts: true + includeHud: true + includeDatePicker: true + includeKeyboard: true + includePartialSheet: true diff --git a/example/settings.gradle.kts b/example/settings.gradle.kts new file mode 100644 index 000000000..422543c51 --- /dev/null +++ b/example/settings.gradle.kts @@ -0,0 +1,102 @@ +import java.io.File +import java.io.FileInputStream +import java.util.Properties + +pluginManagement { + repositories { + google() + gradlePluginPortal() + mavenCentral() + } + + 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") + } + } + } +} + +val props = Properties() +val file = File("$rootDir/local.properties") +if (file.exists()) { + props.load(FileInputStream(file)) +} + +val exampleEmbeddingMethod = if (System.getenv().containsKey("EXAMPLE_EMBEDDING_METHOD")) { + System.getenv()["EXAMPLE_EMBEDDING_METHOD"].also { + 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 { + logger.lifecycle("local.properties read (kaluga.exampleEmbeddingMethod=$exampleEmbeddingMethodLocalProperties, using $it)") + } +} + +val isCompositeBuild = when (exampleEmbeddingMethod) { + "composite" -> true + else -> false +} + +dependencyResolutionManagement { + repositories { + if (!isCompositeBuild) { + val exampleMavenRepo = if (System.getenv().containsKey("EXAMPLE_MAVEN_REPO")) { + System.getenv()["EXAMPLE_MAVEN_REPO"].also { + logger.lifecycle("System env EXAMPLE_MAVEN_REPO set to ${System.getenv()["EXAMPLE_MAVEN_REPO"]}, using $it") + }!! + } else { + val exampleMavenRepoLocalProperties: String? = + props["kaluga.exampleMavenRepo"] as? String + exampleMavenRepoLocalProperties?.also { + logger.lifecycle("local.properties read (kaluga.exampleMavenRepo=$exampleMavenRepoLocalProperties, using $it)") + } + ?: "local".also { + logger.lifecycle("local.properties not found, using default value ($it)") + } + } + logger.lifecycle("Using repo: $exampleMavenRepo for resolving dependencies") + + when (exampleMavenRepo) { + null, "", "local" -> mavenLocal() + "none" -> {/* noop */ + } + else -> + maven(exampleMavenRepo) + } + } + mavenCentral() + google() + } +} + +includeBuild("../kaluga-library-components") +includeBuild("../convention-plugins") + +rootProject.name = "Kaluga Example" +include(":android") +include(":shared") + +if (isCompositeBuild) { + includeBuild("../") +} diff --git a/example/shared/build.gradle.kts b/example/shared/build.gradle.kts index c2855329f..23f706b3f 100644 --- a/example/shared/build.gradle.kts +++ b/example/shared/build.gradle.kts @@ -1,36 +1,59 @@ plugins { kotlin("multiplatform") kotlin("plugin.serialization") - id("jacoco") id("com.android.library") + id("jacoco") id("org.jlleitschuh.gradle.ktlint") } -apply(from = "../../gradle/component.gradle") +val libraryVersion = Library.version +val modules = listOf( + "alerts" to true, + "architecture" to true, + "base" to false, + "bluetooth" to false, + "beacons" to false, + "date-time" to false, + "date-time-picker" to true, + "hud" to true, + "keyboard" to true, + "links" to true, + "location" to false, + "logging" to false, + "resources" to true, + "review" to true, + "scientific" to false, + "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") + } + apiDependency(Dependencies.Koin.Core) } } } } + +android { + dependencies { + apiDependency(Dependencies.Koin.Android) + } +} 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 new file mode 100644 index 000000000..dc632d0ff --- /dev/null +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt @@ -0,0 +1,234 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 + +import com.splendo.kaluga.alerts.AlertPresenter +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.base.ApplicationHolder +import com.splendo.kaluga.bluetooth.BluetoothBuilder +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.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.datetime.TimerViewModel +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.link.BrowserNavigationActions +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.NotificationPermissionViewModel +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.ImagesViewModel +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.scientific.ScientificViewModel +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificConverterNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificConverterViewModel +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificNavigationAction +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificUnitSelectionAction +import com.splendo.kaluga.example.shared.viewmodel.scientific.ScientificUnitSelectionViewModel +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.links.DefaultLinksManager +import com.splendo.kaluga.location.LocationStateRepoBuilder +import com.splendo.kaluga.location.DefaultLocationManager +import com.splendo.kaluga.location.GoogleLocationProvider +import com.splendo.kaluga.permissions.base.Permission +import com.splendo.kaluga.permissions.location.LocationPermission +import com.splendo.kaluga.resources.StyledStringBuilder +import com.splendo.kaluga.review.ReviewManager +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder +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 + +internal val androidModule = module { + viewModel { (navigator: Navigator) -> + ExampleViewModel(navigator) + } + + viewModel { (navigator: Navigator) -> + FeatureListViewModel(navigator) + } + + viewModel { (navigator: Navigator>) -> + InfoViewModel( + ReviewManager.Builder(), + navigator + ) + } + + viewModel { (navigator: Navigator) -> + ComposeOrXMLSelectionViewModel(navigator) + } + + viewModel { (navigator: Navigator) -> + PermissionsListViewModel(navigator) + } + + viewModel { (permission: Permission) -> PermissionViewModel(permission) } + + viewModel { (permission: LocationPermission) -> LocationViewModel(permission) } + + viewModel { NotificationPermissionViewModel() } + + viewModel { (navigator: Navigator>) -> + ArchitectureViewModel(navigator) + } + + viewModel { (initialDetail: InputDetails, navigator: Navigator>) -> + ArchitectureDetailsViewModel( + initialDetail, + navigator + ) + } + + viewModel { (navigator: Navigator) -> + BottomSheetViewModel(navigator) + } + + viewModel { (navigator: Navigator) -> + BottomSheetSubPageViewModel(navigator) + } + + viewModel { + AlertViewModel(AlertPresenter.Builder()) + } + + viewModel { + TimerViewModel() + } + + viewModel { + DateTimePickerViewModel(DateTimePickerPresenter.Builder()) + } + + viewModel { + HudViewModel(HUD.Builder()) + } + + viewModel { (navigator: Navigator>) -> + LinksViewModel( + DefaultLinksManager.Builder(), + AlertPresenter.Builder(), + navigator + ) + } + + viewModel { (navigator: Navigator) -> + SystemViewModel( + navigator + ) + } + + viewModel { + NetworkViewModel(NetworkStateRepoBuilder()) + } + + viewModel { (navigator: Navigator) -> + BluetoothListViewModel(navigator) + } + + viewModel { (identifier: com.splendo.kaluga.bluetooth.device.Identifier) -> + BluetoothDeviceDetailViewModel(identifier) + } + + viewModel { + BeaconsListViewModel() + } + + viewModel { (navigator: Navigator) -> + ResourcesListViewModel(navigator) + } + + viewModel { + ColorViewModel(AlertPresenter.Builder()) + } + + viewModel { + ImagesViewModel() + } + + viewModel { + LabelViewModel(StyledStringBuilder.Provider()) + } + + viewModel { + ButtonViewModel(StyledStringBuilder.Provider(), AlertPresenter.Builder()) + } + + viewModel { (navigator: Navigator>) -> + ScientificViewModel(AlertPresenter.Builder(), navigator) + } + + viewModel { (quantity: PhysicalQuantity, navigator: Navigator>) -> + ScientificUnitSelectionViewModel(quantity, navigator) + } + + viewModel { (arguments: ScientificConverterViewModel.Arguments, navigator: Navigator>) -> + ScientificConverterViewModel(arguments, navigator) + } +} + +fun initKoin(customModules: List = emptyList()) = initKoin( + androidModule, + { + LocationStateRepoBuilder( + locationManagerBuilder = DefaultLocationManager.Builder( + googleLocationProviderSettings = GoogleLocationProvider.Settings() + ), + permissionsBuilder = it, + ) + }, + { BluetoothBuilder(permissionsBuilder = it) }, + customModules +) + +internal actual val appDeclaration: KoinAppDeclaration = { + androidContext(ApplicationHolder.applicationContext) +} 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 f1bfdc05a..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentSubPageViewModel.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. - - */ - -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 c17b2beba..000000000 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/bottomSheet/BottomSheetParentViewModel.kt +++ /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. - - */ - -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/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 fb89a3c98..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 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.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.platformspecific.compose.contacts.model.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 a5efcb96c..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 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.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.platformspecific.compose.contacts.model.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/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrXMLSelectionViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrXMLSelectionViewModel.kt new file mode 100644 index 000000000..dc9f30a63 --- /dev/null +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/compose/ComposeOrXMLSelectionViewModel.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 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 XML : UIType("android_ui_xml".localized()) +} + +class ComposeOrXMLSelectionViewModel( + navigator: Navigator +) : NavigatingViewModel(navigator) { + + val uiTypes = observableOf( + listOf( + UIType.Compose, + UIType.XML + ) + ) + + fun onUITypePressed(feature: UIType) { + navigator.navigate( + when (feature) { + is UIType.Compose -> ComposeOrXMLNavigationAction.Compose + is UIType.XML -> ComposeOrXMLNavigationAction.XML + } + ) + } +} 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 9a5bd2304..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 @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -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/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 new file mode 100644 index 000000000..5c0a64d55 --- /dev/null +++ b/example/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/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationPermissionViewModel.kt b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationPermissionViewModel.kt new file mode 100644 index 000000000..2148de324 --- /dev/null +++ b/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/NotificationPermissionViewModel.kt @@ -0,0 +1,70 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.toInitializedObservable +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.base.singleThreadDispatcher +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.PermissionState +import com.splendo.kaluga.permissions.base.Permissions +import com.splendo.kaluga.permissions.base.PermissionsBuilder +import com.splendo.kaluga.permissions.notifications.NotificationsPermission +import com.splendo.kaluga.permissions.notifications.registerNotificationsPermissionIfNotRegistered +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +private val permissionsDispatcher = singleThreadDispatcher("NotificationPermissionsDispatcher") + +class NotificationPermissionViewModel : BaseLifecycleViewModel(), KoinComponent { + + companion object { + private val permission = NotificationsPermission(notificationOptions) + } + + private val permissionsBuilder: PermissionsBuilder by inject() + private val permissions = Permissions(permissionsBuilder, coroutineScope.coroutineContext + permissionsDispatcher) + + val hasPermission by lazy { + permissions[permission] + .map { permissionState -> permissionState is PermissionState.Allowed } + .toInitializedObservable(false, coroutineScope) + } + + init { + permissionsBuilder.registerNotificationsPermissionIfNotRegistered( + settings = BasePermissionManager.Settings( + logger = RestrictedLogger(RestrictedLogLevel.None) + ) + ) + } + + fun requestPermission() { + coroutineScope.launch { + permissions.request(permission) + } + } + + public override fun onCleared() { + super.onCleared() + } +} diff --git a/resources/src/commonMain/kotlin/stylable/TextAlignment.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/HelloShared.kt similarity index 75% rename from resources/src/commonMain/kotlin/stylable/TextAlignment.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/HelloShared.kt index 8f687ffd8..f1c3e63ea 100644 --- a/resources/src/commonMain/kotlin/stylable/TextAlignment.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/HelloShared.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 Splendo Consulting B.V. The Netherlands + Copyright 2023 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,12 +15,8 @@ */ -package com.splendo.kaluga.resources.stylable +package com.splendo.kaluga.example.shared -enum class TextAlignment { - LEFT, - RIGHT, - END, - START, - CENTER +fun helloCommon(): String { + return "Hello from the shared module common source" } 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 new file mode 100644 index 000000000..2e6863657 --- /dev/null +++ b/example/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.DefaultBeacons +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 { DefaultBeacons(get(), beaconLifetime = 1.minutes, logger = get()) } +} + +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/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/model/ContactDetails.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt similarity index 83% rename from example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/model/ContactDetails.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt index 0b2e33700..80767a6c4 100644 --- a/example/shared/src/androidLibMain/kotlin/com/splendo/kaluga/example/shared/platformspecific/compose/contacts/model/ContactDetails.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/contacts/ContactDetails.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -15,7 +15,7 @@ */ -package com.splendo.kaluga.example.shared.platformspecific.compose.contacts.model +package com.splendo.kaluga.example.shared.model.contacts import kotlinx.serialization.Serializable diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/QuantityDetails.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/QuantityDetails.kt new file mode 100644 index 000000000..e665be6f4 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/QuantityDetails.kt @@ -0,0 +1,170 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific + +import com.splendo.kaluga.base.utils.Decimal +import com.splendo.kaluga.example.shared.model.scientific.converters.QuantityConverter +import com.splendo.kaluga.example.shared.model.scientific.converters.converters +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.ScientificValue +import com.splendo.kaluga.scientific.convert +import com.splendo.kaluga.scientific.unit.* + +data class QuantityDetails( + val quantity: Quantity, + val units: Set>, + val converters: List> +) { + @Suppress("UNCHECKED_CAST") + fun convert(value: Decimal, unit: ScientificUnit<*>, to: ScientificUnit<*>): ScientificValue? = if (unit.quantity == quantity && to.quantity == quantity) { + DefaultScientificValue(value, unit as ScientificUnit).convert(to as ScientificUnit) + } else { + null + } +} + +val allPhysicalQuantities: Set = setOf( + PhysicalQuantity.Acceleration, + PhysicalQuantity.Action, + PhysicalQuantity.AmountOfSubstance, + PhysicalQuantity.Angle, + PhysicalQuantity.AngularAcceleration, + PhysicalQuantity.AngularVelocity, + PhysicalQuantity.Area, + PhysicalQuantity.AreaDensity, + PhysicalQuantity.CatalysticActivity, + PhysicalQuantity.Density, + PhysicalQuantity.Dimensionless, + PhysicalQuantity.DynamicViscosity, + PhysicalQuantity.ElectricCapacitance, + PhysicalQuantity.ElectricCharge, + PhysicalQuantity.ElectricConductance, + PhysicalQuantity.ElectricCurrent, + PhysicalQuantity.ElectricInductance, + PhysicalQuantity.ElectricInductance, + PhysicalQuantity.ElectricResistance, + PhysicalQuantity.Energy, + PhysicalQuantity.Force, + PhysicalQuantity.Frequency, + PhysicalQuantity.HeatCapacity, + PhysicalQuantity.Illuminance, + PhysicalQuantity.IonizingRadiationAbsorbedDose, + PhysicalQuantity.IonizingRadiationEquivalentDose, + PhysicalQuantity.LinearMassDensity, + PhysicalQuantity.Jolt, + PhysicalQuantity.Length, + PhysicalQuantity.Luminance, + PhysicalQuantity.LuminousEnergy, + PhysicalQuantity.LuminousExposure, + PhysicalQuantity.LuminousFlux, + PhysicalQuantity.LuminousIntensity, + PhysicalQuantity.MassFlowRate, + PhysicalQuantity.MagneticFlux, + PhysicalQuantity.MagneticInduction, + PhysicalQuantity.Molality, + PhysicalQuantity.Molarity, + PhysicalQuantity.MolarEnergy, + PhysicalQuantity.MolarMass, + PhysicalQuantity.MolarVolume, + PhysicalQuantity.Momentum, + PhysicalQuantity.Power, + PhysicalQuantity.Pressure, + PhysicalQuantity.Radioactivity, + PhysicalQuantity.SolidAngle, + PhysicalQuantity.SpecificEnergy, + PhysicalQuantity.SpecificHeatCapacity, + PhysicalQuantity.SpecificVolume, + PhysicalQuantity.Speed, + PhysicalQuantity.SurfaceTension, + PhysicalQuantity.Temperature, + PhysicalQuantity.ThermalResistance, + PhysicalQuantity.Time, + PhysicalQuantity.Voltage, + PhysicalQuantity.Volume, + PhysicalQuantity.VolumetricFlow, + PhysicalQuantity.VolumetricFlux, + PhysicalQuantity.Weight, + PhysicalQuantity.Yank +) + +internal val PhysicalQuantity.quantityDetails: QuantityDetails<*>? get() = when (this) { + is PhysicalQuantity.Dimensionless -> null + is PhysicalQuantity.Acceleration -> QuantityDetails(this, AccelerationUnits, converters) + is PhysicalQuantity.Action -> QuantityDetails(this, ActionUnits, converters) + is PhysicalQuantity.AmountOfSubstance -> QuantityDetails(this, AmountOfSubstanceUnits, converters) + is PhysicalQuantity.Angle -> QuantityDetails(this, AngleUnits, converters) + is PhysicalQuantity.AngularAcceleration -> QuantityDetails(this, AngularAccelerationUnits, converters) + is PhysicalQuantity.AngularVelocity -> QuantityDetails(this, AngularVelocityUnits, converters) + is PhysicalQuantity.Area -> QuantityDetails(this, AreaUnits, converters) + is PhysicalQuantity.AreaDensity -> QuantityDetails(this, AreaDensityUnits, converters) + is PhysicalQuantity.CatalysticActivity -> QuantityDetails(this, CatalysticActivityUnits, converters) + is PhysicalQuantity.Density -> QuantityDetails(this, DensityUnits, converters) + is PhysicalQuantity.DynamicViscosity -> QuantityDetails(this, DynamicViscosityUnits, converters) + is PhysicalQuantity.ElectricCapacitance -> QuantityDetails(this, ElectricCapacitanceUnits, converters) + is PhysicalQuantity.ElectricCharge -> QuantityDetails(this, ElectricChargeUnits, converters) + is PhysicalQuantity.ElectricConductance -> QuantityDetails(this, ElectricConductanceUnits, converters) + is PhysicalQuantity.ElectricCurrent -> QuantityDetails(this, ElectricCurrentUnits, converters) + is PhysicalQuantity.ElectricInductance -> QuantityDetails(this, ElectricInductanceUnits, converters) + is PhysicalQuantity.ElectricResistance -> QuantityDetails(this, ElectricResistanceUnits, converters) + is PhysicalQuantity.Energy -> QuantityDetails(this, EnergyUnits, converters) + is PhysicalQuantity.Force -> QuantityDetails(this, ForceUnits, converters) + is PhysicalQuantity.Frequency -> QuantityDetails(this, FrequencyUnits, converters) + is PhysicalQuantity.HeatCapacity -> QuantityDetails(this, HeatCapacityUnits, converters) + is PhysicalQuantity.Illuminance -> QuantityDetails(this, IlluminanceUnits, converters) + is PhysicalQuantity.IonizingRadiationAbsorbedDose -> QuantityDetails(this, IonizingRadiationAbsorbedDoseUnits, converters) + is PhysicalQuantity.IonizingRadiationEquivalentDose -> QuantityDetails(this, IonizingRadiationEquivalentDoseUnits, converters) + is PhysicalQuantity.Jolt -> QuantityDetails(this, JoltUnits, converters) + is PhysicalQuantity.KinematicViscosity -> QuantityDetails(this, KinematicViscosityUnits, converters) + is PhysicalQuantity.Length -> QuantityDetails(this, LengthUnits, converters) + is PhysicalQuantity.LinearMassDensity -> QuantityDetails(this, LinearMassDensityUnits, converters) + is PhysicalQuantity.Luminance -> QuantityDetails(this, LuminanceUnits, converters) + is PhysicalQuantity.LuminousEnergy -> QuantityDetails(this, LuminousEnergyUnits, converters) + is PhysicalQuantity.LuminousExposure -> QuantityDetails(this, LuminousExposureUnits, converters) + is PhysicalQuantity.LuminousFlux -> QuantityDetails(this, LuminousFluxUnits, converters) + is PhysicalQuantity.LuminousIntensity -> QuantityDetails(this, LuminousIntensityUnits, converters) + is PhysicalQuantity.MagneticFlux -> QuantityDetails(this, MagneticFluxUnits, converters) + is PhysicalQuantity.MagneticInduction -> QuantityDetails(this, MagneticInductionUnits, converters) + is PhysicalQuantity.MassFlowRate -> QuantityDetails(this, MassFlowRateUnits, converters) + is PhysicalQuantity.Molality -> QuantityDetails(this, MolalityUnits, converters) + is PhysicalQuantity.MolarEnergy -> QuantityDetails(this, MolarEnergyUnits, converters) + is PhysicalQuantity.Molarity -> QuantityDetails(this, MolarityUnits, converters) + is PhysicalQuantity.MolarMass -> QuantityDetails(this, MolarMassUnits, converters) + is PhysicalQuantity.MolarVolume -> QuantityDetails(this, MolarVolumeUnits, converters) + is PhysicalQuantity.Momentum -> QuantityDetails(this, MomentumUnits, converters) + is PhysicalQuantity.Power -> QuantityDetails(this, PowerUnits, converters) + is PhysicalQuantity.Pressure -> QuantityDetails(this, PressureUnits, converters) + is PhysicalQuantity.Radioactivity -> QuantityDetails(this, RadioactivityUnits, converters) + is PhysicalQuantity.SolidAngle -> QuantityDetails(this, SolidAngleUnits, converters) + is PhysicalQuantity.SpecificEnergy -> QuantityDetails(this, SpecificEnergyUnits, converters) + is PhysicalQuantity.SpecificHeatCapacity -> QuantityDetails(this, SpecificHeatCapacityUnits, converters) + is PhysicalQuantity.SpecificVolume -> QuantityDetails(this, SpecificVolumeUnits, converters) + is PhysicalQuantity.Speed -> QuantityDetails(this, SpeedUnits, converters) + is PhysicalQuantity.SurfaceTension -> QuantityDetails(this, SurfaceTensionUnits, converters) + is PhysicalQuantity.Temperature -> QuantityDetails(this, TemperatureUnits, converters) + is PhysicalQuantity.ThermalResistance -> QuantityDetails(this, ThermalResistanceUnits, converters) + is PhysicalQuantity.Time -> QuantityDetails(this, TimeUnits, converters) + is PhysicalQuantity.Voltage -> QuantityDetails(this, VoltageUnits, converters) + is PhysicalQuantity.Volume -> QuantityDetails(this, VolumeUnits, converters) + is PhysicalQuantity.VolumetricFlow -> QuantityDetails(this, VolumetricFlowUnits, converters) + is PhysicalQuantity.VolumetricFlux -> QuantityDetails(this, VolumetricFluxUnits, converters) + is PhysicalQuantity.Weight -> QuantityDetails(this, WeightUnits, converters) + is PhysicalQuantity.Yank -> QuantityDetails(this, YankUnits, converters) +} + +val PhysicalQuantity.name: String get() = this::class.simpleName.orEmpty().split("(?=\\p{Upper})".toRegex()).joinToString(" ") diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AccelerationConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AccelerationConverters.kt new file mode 100644 index 000000000..f24039996 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AccelerationConverters.kt @@ -0,0 +1,80 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.acceleration.div +import com.splendo.kaluga.scientific.converter.acceleration.times +import com.splendo.kaluga.scientific.unit.Acceleration +import com.splendo.kaluga.scientific.unit.Grain +import com.splendo.kaluga.scientific.unit.Gram +import com.splendo.kaluga.scientific.unit.ImperialAcceleration +import com.splendo.kaluga.scientific.unit.ImperialTon +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.Jolt +import com.splendo.kaluga.scientific.unit.MetricAcceleration +import com.splendo.kaluga.scientific.unit.MetricWeight +import com.splendo.kaluga.scientific.unit.Ounce +import com.splendo.kaluga.scientific.unit.Pound +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.UsTon +import com.splendo.kaluga.scientific.unit.Weight + +val PhysicalQuantity.Acceleration.converters get() = listOf>( + QuantityConverterWithOperator("Force from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (accelerationValue, accelerationUnit), (weightValue, weightUnit) -> + when { + accelerationUnit is MetricAcceleration && weightUnit is Gram -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is MetricAcceleration && weightUnit is MetricWeight -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is Pound -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is Ounce -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is Grain -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is UsTon -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is ImperialTon -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is ImperialWeight -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is UKImperialWeight -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is ImperialAcceleration && weightUnit is USCustomaryWeight -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + accelerationUnit is Acceleration && weightUnit is Weight -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(weightValue, weightUnit) + else -> throw RuntimeException("Unexpected units: $accelerationUnit, $weightUnit") + } + }, + QuantityConverterWithOperator("Jolt from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (accelerationValue, accelerationUnit), (timeValue, timeUnit) -> + when { + accelerationUnit is MetricAcceleration && timeUnit is Time -> DefaultScientificValue(accelerationValue, accelerationUnit) / DefaultScientificValue(timeValue, timeUnit) + accelerationUnit is ImperialAcceleration && timeUnit is Time -> DefaultScientificValue(accelerationValue, accelerationUnit) / DefaultScientificValue(timeValue, timeUnit) + accelerationUnit is Acceleration && timeUnit is Time -> DefaultScientificValue(accelerationValue, accelerationUnit) / DefaultScientificValue(timeValue, timeUnit) + else -> throw RuntimeException("Unexpected units: $accelerationUnit, $timeUnit") + } + }, + QuantityConverterWithOperator("Speed from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (accelerationValue, accelerationUnit), (timeValue, timeUnit) -> + when { + accelerationUnit is MetricAcceleration && timeUnit is Time -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(timeValue, timeUnit) + accelerationUnit is ImperialAcceleration && timeUnit is Time -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(timeValue, timeUnit) + accelerationUnit is Acceleration && timeUnit is Time -> DefaultScientificValue(accelerationValue, accelerationUnit) * DefaultScientificValue(timeValue, timeUnit) + else -> throw RuntimeException("Unexpected units: $accelerationUnit, $timeUnit") + } + }, + QuantityConverterWithOperator("Time from Jolt", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Jolt) { (accelerationValue, accelerationUnit), (joltValue, joltUnit) -> + when { + accelerationUnit is Acceleration && joltUnit is Jolt -> DefaultScientificValue(accelerationValue, accelerationUnit) / DefaultScientificValue(joltValue, joltUnit) + else -> throw RuntimeException("Unexpected units: $accelerationUnit, $joltUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ActionConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ActionConverters.kt new file mode 100644 index 000000000..0ff989802 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ActionConverters.kt @@ -0,0 +1,46 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.action.div +import com.splendo.kaluga.scientific.unit.Action +import com.splendo.kaluga.scientific.unit.Energy +import com.splendo.kaluga.scientific.unit.ImperialAction +import com.splendo.kaluga.scientific.unit.MetricAction +import com.splendo.kaluga.scientific.unit.MetricAndImperialAction +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.Action.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (actionValue, actionUnit), (timeValue, timeUnit) -> + when { + actionUnit is MetricAndImperialAction && timeUnit is Time -> DefaultScientificValue(actionValue, actionUnit) / DefaultScientificValue(timeValue, timeUnit) + actionUnit is MetricAction && timeUnit is Time -> DefaultScientificValue(actionValue, actionUnit) / DefaultScientificValue(timeValue, timeUnit) + actionUnit is ImperialAction && timeUnit is Time -> DefaultScientificValue(actionValue, actionUnit) / DefaultScientificValue(timeValue, timeUnit) + actionUnit is Action && timeUnit is Time -> DefaultScientificValue(actionValue, actionUnit) / DefaultScientificValue(timeValue, timeUnit) + else -> throw RuntimeException("Unexpected units: $actionUnit, $timeUnit") + } + }, + QuantityConverterWithOperator("Time from Energy", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Energy) { (actionValue, actionUnit), (energyValue, energyUnit) -> + when { + actionUnit is Action && energyUnit is Energy -> DefaultScientificValue(actionValue, actionUnit) / DefaultScientificValue(energyValue, energyUnit) + else -> throw RuntimeException("Unexpected units: $actionUnit, $energyUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AmountOfSubstanceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AmountOfSubstanceConverters.kt new file mode 100644 index 000000000..e9bab036f --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AmountOfSubstanceConverters.kt @@ -0,0 +1,89 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.amountOfSubstance.decaysWithHalfLife +import com.splendo.kaluga.scientific.converter.amountOfSubstance.div +import com.splendo.kaluga.scientific.converter.amountOfSubstance.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.AmountOfSubstance.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Molar Energy", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MolarEnergy) { (amountOfSubstanceValue, amountOfSubstanceUnit), (molarEnergyValue, molarEnergyUnit) -> + when { + amountOfSubstanceUnit is AmountOfSubstance && molarEnergyUnit is MetricAndImperialMolarEnergy -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) * DefaultScientificValue(molarEnergyValue, molarEnergyUnit) + amountOfSubstanceUnit is AmountOfSubstance && molarEnergyUnit is MetricMolarEnergy -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) * DefaultScientificValue(molarEnergyValue, molarEnergyUnit) + amountOfSubstanceUnit is AmountOfSubstance && molarEnergyUnit is ImperialMolarEnergy -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) * DefaultScientificValue(molarEnergyValue, molarEnergyUnit) + amountOfSubstanceUnit is AmountOfSubstance && molarEnergyUnit is MolarEnergy -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) * DefaultScientificValue(molarEnergyValue, molarEnergyUnit) + else -> throw RuntimeException("Unexpected units: $amountOfSubstanceUnit, $molarEnergyUnit") + } + }, + QuantityConverterWithOperator("Molality from Weight", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Weight) { (amountOfSubstanceValue, amountOfSubstanceUnit), (weightValue, weightUnit) -> + when { + amountOfSubstanceUnit is AmountOfSubstance && weightUnit is MetricWeight -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(weightValue, weightUnit) + amountOfSubstanceUnit is AmountOfSubstance && weightUnit is ImperialWeight -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(weightValue, weightUnit) + amountOfSubstanceUnit is AmountOfSubstance && weightUnit is UKImperialWeight -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(weightValue, weightUnit) + amountOfSubstanceUnit is AmountOfSubstance && weightUnit is USCustomaryWeight -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(weightValue, weightUnit) + amountOfSubstanceUnit is AmountOfSubstance && weightUnit is Weight -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(weightValue, weightUnit) + else -> throw RuntimeException("Unexpected units: $amountOfSubstanceUnit, $weightUnit") + } + }, + QuantityConverterWithOperator("Molarity from Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Volume) { (amountOfSubstanceValue, amountOfSubstanceUnit), (volumeValue, volumeUnit) -> + when { + amountOfSubstanceUnit is AmountOfSubstance && volumeUnit is MetricVolume -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(volumeValue, volumeUnit) + amountOfSubstanceUnit is AmountOfSubstance && volumeUnit is ImperialVolume -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(volumeValue, volumeUnit) + amountOfSubstanceUnit is AmountOfSubstance && volumeUnit is UKImperialVolume -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(volumeValue, volumeUnit) + amountOfSubstanceUnit is AmountOfSubstance && volumeUnit is USCustomaryVolume -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(volumeValue, volumeUnit) + amountOfSubstanceUnit is AmountOfSubstance && volumeUnit is Volume -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(volumeValue, volumeUnit) + else -> throw RuntimeException("Unexpected units: $amountOfSubstanceUnit, $volumeUnit") + } + }, + QuantityConverterWithOperator("Radioactivity from Time", QuantityConverter.WithOperator.Type.Custom("with λ"), PhysicalQuantity.Time) { (amountOfSubstanceValue, amountOfSubstanceUnit), (timeValue, timeUnit) -> + when { + amountOfSubstanceUnit is AmountOfSubstance && timeUnit is Time -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit).decaysWithHalfLife(DefaultScientificValue(timeValue, timeUnit)) + else -> throw RuntimeException("Unexpected units: $amountOfSubstanceUnit, $timeUnit") + } + }, + QuantityConverterWithOperator("Time from Catalystic Activity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.CatalysticActivity) { (amountOfSubstanceValue, amountOfSubstanceUnit), (catalysticActivityValue, catalysticActivityUnit) -> + when { + amountOfSubstanceUnit is AmountOfSubstance && catalysticActivityUnit is CatalysticActivity -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(catalysticActivityValue, catalysticActivityUnit) + else -> throw RuntimeException("Unexpected units: $amountOfSubstanceUnit, $catalysticActivityUnit") + } + }, + QuantityConverterWithOperator("Volume from Molarity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Molarity) { (amountOfSubstanceValue, amountOfSubstanceUnit), (molarityValue, molarityUnit) -> + when { + amountOfSubstanceUnit is AmountOfSubstance && molarityUnit is MetricMolarity -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molarityValue, molarityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molarityUnit is ImperialMolarity -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molarityValue, molarityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molarityUnit is UKImperialMolarity -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molarityValue, molarityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molarityUnit is USCustomaryMolarity -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molarityValue, molarityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molarityUnit is Molarity -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molarityValue, molarityUnit) + else -> throw RuntimeException("Unexpected units: $amountOfSubstanceUnit, $molarityUnit") + } + }, + QuantityConverterWithOperator("Weight from Molality", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Molality) { (amountOfSubstanceValue, amountOfSubstanceUnit), (molalityValue, molalityUnit) -> + when { + amountOfSubstanceUnit is AmountOfSubstance && molalityUnit is MetricMolality -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molalityValue, molalityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molalityUnit is ImperialMolality -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molalityValue, molalityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molalityUnit is UKImperialMolality -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molalityValue, molalityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molalityUnit is USCustomaryMolality -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molalityValue, molalityUnit) + amountOfSubstanceUnit is AmountOfSubstance && molalityUnit is Molality -> DefaultScientificValue(amountOfSubstanceValue, amountOfSubstanceUnit) / DefaultScientificValue(molalityValue, molalityUnit) + else -> throw RuntimeException("Unexpected units: $amountOfSubstanceUnit, $molalityUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngleConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngleConverters.kt new file mode 100644 index 000000000..ac8bc4374 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngleConverters.kt @@ -0,0 +1,40 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.angle.div +import com.splendo.kaluga.scientific.unit.Angle +import com.splendo.kaluga.scientific.unit.AngularVelocity +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.Angle.converters get() = listOf>( + QuantityConverterWithOperator("Angular Velocity from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (angleValue, angleUnit), (timeValue, timeUnit) -> + when { + angleUnit is Angle && timeUnit is Time -> DefaultScientificValue(angleValue, angleUnit) / DefaultScientificValue(timeValue, timeUnit) + else -> throw RuntimeException("Unexpected units: $angleUnit $timeUnit") + } + }, + QuantityConverterWithOperator("Time from Angular Velocity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AngularVelocity) { (angleValue, angleUnit), (angularVelocityValue, angularVelocityUnit) -> + when { + angleUnit is Angle && angularVelocityUnit is AngularVelocity -> DefaultScientificValue(angleValue, angleUnit) / DefaultScientificValue(angularVelocityValue, angularVelocityUnit) + else -> throw RuntimeException("Unexpected units: $angleUnit $angularVelocityUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngularAccelerationConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngularAccelerationConverters.kt new file mode 100644 index 000000000..a1437a343 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngularAccelerationConverters.kt @@ -0,0 +1,33 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.angularAcceleration.times +import com.splendo.kaluga.scientific.unit.AngularAcceleration +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.AngularAcceleration.converters get() = listOf>( + QuantityConverterWithOperator("Angular Velocity from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (angularAccelerationValue, angularAccelerationUnit), (timeValue, timeUnit) -> + when { + angularAccelerationUnit is AngularAcceleration && timeUnit is Time -> DefaultScientificValue(angularAccelerationValue, angularAccelerationUnit) * DefaultScientificValue(timeValue, timeUnit) + else -> throw RuntimeException("Unexpected units: $angularAccelerationUnit $timeUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngularVelocityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngularVelocityConverters.kt new file mode 100644 index 000000000..358dc5fa9 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AngularVelocityConverters.kt @@ -0,0 +1,47 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.angularVelocity.div +import com.splendo.kaluga.scientific.converter.angularVelocity.times +import com.splendo.kaluga.scientific.unit.AngularAcceleration +import com.splendo.kaluga.scientific.unit.AngularVelocity +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.AngularVelocity.converters get() = listOf>( + QuantityConverterWithOperator("Angle from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (angularVelocityValue, angularVelocityUnit), (timeValue, timeUnit) -> + when { + angularVelocityUnit is AngularVelocity && timeUnit is Time -> DefaultScientificValue(angularVelocityValue, angularVelocityUnit) * DefaultScientificValue(timeValue, timeUnit) + else -> throw RuntimeException("Unexpected units: $angularVelocityUnit $timeUnit") + } + }, + QuantityConverterWithOperator("Angular Acceleration from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (angularVelocityValue, angularVelocityUnit), (timeValue, timeUnit) -> + when { + angularVelocityUnit is AngularVelocity && timeUnit is Time -> DefaultScientificValue(angularVelocityValue, angularVelocityUnit) / DefaultScientificValue(timeValue, timeUnit) + else -> throw RuntimeException("Unexpected units: $angularVelocityUnit $timeUnit") + } + }, + QuantityConverterWithOperator("Time from Angular Acceleration", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AngularAcceleration) { (angularVelocityValue, angularVelocityUnit), (angularAccelerationValue, angularAccelerationUnit) -> + when { + angularVelocityUnit is AngularVelocity && angularAccelerationUnit is AngularAcceleration -> DefaultScientificValue(angularVelocityValue, angularVelocityUnit) / DefaultScientificValue(angularAccelerationValue, angularAccelerationUnit) + else -> throw RuntimeException("Unexpected units: $angularVelocityUnit $angularAccelerationUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AreaConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AreaConverters.kt new file mode 100644 index 000000000..541f2423b --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AreaConverters.kt @@ -0,0 +1,195 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.area.div +import com.splendo.kaluga.scientific.converter.area.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Area.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Surface Tension", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SurfaceTension) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is SquareCentimeter && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricArea && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareInch && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareInch && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareInch && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is SurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Pressure", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Pressure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is SquareCentimeter && rightUnit is Barye -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareCentimeter && rightUnit is BaryeMultiple -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is OunceSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is KipSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is KipSquareFoot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USTonSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USTonSquareFoot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialTonSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialTonSquareFoot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is Pressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Length", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is SquareMeter && rightUnit is Meter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareNanometer && rightUnit is Nanometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMicrometer && rightUnit is Micrometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMillimeter && rightUnit is Millimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareCentimeter && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareDecimeter && rightUnit is Decimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareDecameter && rightUnit is Decameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareHectometer && rightUnit is Hectometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareKilometer && rightUnit is Kilometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMegameter && rightUnit is Megameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareGigameter && rightUnit is Gigameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricArea && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareInch && rightUnit is Inch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareFoot && rightUnit is Foot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareYard && rightUnit is Yard -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMile && rightUnit is Mile -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Linear Mass Density from Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricArea && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Linear Mass Density from Specific Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricArea && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Energy from Luminous Exposure", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.LuminousExposure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricArea && rightUnit is MetricLuminousExposure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialLuminousExposure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is LuminousExposure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Flux from Illuminance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Illuminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Area && rightUnit is Illuminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Intensity from Luminance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Luminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Area && rightUnit is Luminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Magnetic Induction", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MagneticInduction) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is SquareCentimeter && rightUnit is Gauss -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is MagneticInduction -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Momentum from Dynamic Viscosity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.DynamicViscosity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricArea && rightUnit is MetricDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is UKImperialDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USCustomaryDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is DynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Volume from Linear Mass Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.LinearMassDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricArea && rightUnit is MetricLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is UKImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USCustomaryLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is LinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Length", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is SquareMeter && rightUnit is Meter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareNanometer && rightUnit is Nanometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMicrometer && rightUnit is Micrometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMillimeter && rightUnit is Millimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareCentimeter && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareDecimeter && rightUnit is Decimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareDecameter && rightUnit is Decameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareHectometer && rightUnit is Hectometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareKilometer && rightUnit is Kilometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMegameter && rightUnit is Megameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareGigameter && rightUnit is Gigameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricArea && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareInch && rightUnit is Inch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareFoot && rightUnit is Foot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareYard && rightUnit is Yard -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SquareMile && rightUnit is Mile -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Acre && rightUnit is Inch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Acre && rightUnit is Foot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Volumetric Flow from Volumetric Flux", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.VolumetricFlux) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricArea && rightUnit is MetricVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is UKImperialVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USCustomaryVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is VolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Area Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AreaDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricArea && rightUnit is MetricAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialArea && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Area && rightUnit is AreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AreaDensityConverter.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AreaDensityConverter.kt new file mode 100644 index 000000000..7e5d44126 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/AreaDensityConverter.kt @@ -0,0 +1,85 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.areaDensity.div +import com.splendo.kaluga.scientific.converter.areaDensity.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.AreaDensity.converters get() = listOf>( + QuantityConverterWithOperator("Density from Length", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAreaDensity && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialAreaDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryAreaDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is AreaDensity && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Specific Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAreaDensity && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialAreaDensity && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialAreaDensity && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryAreaDensity && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryAreaDensity && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is AreaDensity && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAreaDensity && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialAreaDensity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialAreaDensity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryAreaDensity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryAreaDensity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is AreaDensity && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Linear Mass Density from Length", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAreaDensity && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialAreaDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryAreaDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is AreaDensity && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAreaDensity && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialAreaDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialAreaDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryAreaDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is AreaDensity && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit $rightUnit") + } + }, +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/CatalysticActivityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/CatalysticActivityConverters.kt new file mode 100644 index 000000000..9e6e5d284 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/CatalysticActivityConverters.kt @@ -0,0 +1,33 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.catalysticActivity.times +import com.splendo.kaluga.scientific.unit.CatalysticActivity +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.CatalysticActivity.converters get() = listOf>( + QuantityConverterWithOperator("Amount of Substance from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is CatalysticActivity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/DensityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/DensityConverters.kt new file mode 100644 index 000000000..3d1b07385 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/DensityConverters.kt @@ -0,0 +1,122 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.density.div +import com.splendo.kaluga.scientific.converter.density.specificVolume +import com.splendo.kaluga.scientific.converter.density.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Density.converters get() = listOf>( + QuantityConverterWithOperator("Area Density from Length", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Dynamic Viscosity from Kinematic Viscosity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.KinematicViscosity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is KinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Linear Mass Density from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molarity from Molality", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Molality) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molarity from Molar Mass", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarMass) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Mass from Molarity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Molarity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molarity from Molar Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MolarVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Specific Volume") { value, unit -> + when (unit) { + is MetricDensity -> DefaultScientificValue(value, unit).specificVolume() + is ImperialDensity -> DefaultScientificValue(value, unit).specificVolume() + is UKImperialDensity -> DefaultScientificValue(value, unit).specificVolume() + is USCustomaryDensity -> DefaultScientificValue(value, unit).specificVolume() + is Density -> DefaultScientificValue(value, unit).specificVolume() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Weight from Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Volume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDensity && rightUnit is MetricVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDensity && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDensity && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDensity && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Density && rightUnit is Volume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/DynamicViscosityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/DynamicViscosityConverters.kt new file mode 100644 index 000000000..dfdf9496b --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/DynamicViscosityConverters.kt @@ -0,0 +1,94 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.dynamicViscosity.div +import com.splendo.kaluga.scientific.converter.dynamicViscosity.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.Density +import com.splendo.kaluga.scientific.unit.DynamicViscosity +import com.splendo.kaluga.scientific.unit.ImperialArea +import com.splendo.kaluga.scientific.unit.ImperialDensity +import com.splendo.kaluga.scientific.unit.ImperialDynamicViscosity +import com.splendo.kaluga.scientific.unit.ImperialKinematicViscosity +import com.splendo.kaluga.scientific.unit.KinematicViscosity +import com.splendo.kaluga.scientific.unit.MetricArea +import com.splendo.kaluga.scientific.unit.MetricDensity +import com.splendo.kaluga.scientific.unit.MetricDynamicViscosity +import com.splendo.kaluga.scientific.unit.MetricKinematicViscosity +import com.splendo.kaluga.scientific.unit.Pressure +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.UKImperialDensity +import com.splendo.kaluga.scientific.unit.UKImperialDynamicViscosity +import com.splendo.kaluga.scientific.unit.USCustomaryDensity +import com.splendo.kaluga.scientific.unit.USCustomaryDynamicViscosity + +val PhysicalQuantity.DynamicViscosity.converters get() = listOf>( + QuantityConverterWithOperator("Density from Kinematic Viscosity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.KinematicViscosity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDynamicViscosity && rightUnit is MetricKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDynamicViscosity && rightUnit is ImperialKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDynamicViscosity && rightUnit is ImperialKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDynamicViscosity && rightUnit is ImperialKinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DynamicViscosity && rightUnit is KinematicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Kinematic Viscosity from Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDynamicViscosity && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDynamicViscosity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDynamicViscosity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDynamicViscosity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDynamicViscosity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDynamicViscosity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDynamicViscosity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDynamicViscosity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DynamicViscosity && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Momentum from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDynamicViscosity && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDynamicViscosity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDynamicViscosity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDynamicViscosity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is DynamicViscosity && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Pressure from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricDynamicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialDynamicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialDynamicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryDynamicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DynamicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Pressure", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Pressure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is DynamicViscosity && rightUnit is Pressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricCapacitanceUnits.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricCapacitanceUnits.kt new file mode 100644 index 000000000..4c9977852 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricCapacitanceUnits.kt @@ -0,0 +1,51 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.electricCapacitance.times +import com.splendo.kaluga.scientific.unit.Abfarad +import com.splendo.kaluga.scientific.unit.Abvolt +import com.splendo.kaluga.scientific.unit.ElectricCapacitance +import com.splendo.kaluga.scientific.unit.ElectricResistance +import com.splendo.kaluga.scientific.unit.Frequency +import com.splendo.kaluga.scientific.unit.Voltage + +val PhysicalQuantity.ElectricCapacitance.converters get() = listOf>( + QuantityConverterWithOperator("Electric Charge from Voltage", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abfarad && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCapacitance && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Conductance from Frequency", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Frequency) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abfarad && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCapacitance && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Electric Resistance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ElectricCapacitance && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricChargeConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricChargeConverters.kt new file mode 100644 index 000000000..7bf74e2d8 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricChargeConverters.kt @@ -0,0 +1,77 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.electricCharge.div +import com.splendo.kaluga.scientific.converter.electricCharge.times +import com.splendo.kaluga.scientific.unit.Abcoulomb +import com.splendo.kaluga.scientific.unit.Abfarad +import com.splendo.kaluga.scientific.unit.Abohm +import com.splendo.kaluga.scientific.unit.Abvolt +import com.splendo.kaluga.scientific.unit.ElectricCapacitance +import com.splendo.kaluga.scientific.unit.ElectricCharge +import com.splendo.kaluga.scientific.unit.ElectricCurrent +import com.splendo.kaluga.scientific.unit.ElectricResistance +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.Voltage + +val PhysicalQuantity.ElectricCharge.converters get() = listOf>( + QuantityConverterWithOperator("Electric Charge from Voltage", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abcoulomb && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCharge && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Current from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abcoulomb && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCharge && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Voltage", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abcoulomb && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCharge && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Electric Resistance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abcoulomb && rightUnit is Abohm -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCharge && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Electric Current", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ElectricCharge && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Voltage from Electric Capacitance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCapacitance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abcoulomb && rightUnit is Abfarad -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCharge && rightUnit is ElectricCapacitance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricConductanceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricConductanceConverters.kt new file mode 100644 index 000000000..1fb3dd4dd --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricConductanceConverters.kt @@ -0,0 +1,60 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.electricConductance.div +import com.splendo.kaluga.scientific.converter.electricConductance.resistance +import com.splendo.kaluga.scientific.converter.electricConductance.times +import com.splendo.kaluga.scientific.unit.Absiemens +import com.splendo.kaluga.scientific.unit.Abvolt +import com.splendo.kaluga.scientific.unit.ElectricCapacitance +import com.splendo.kaluga.scientific.unit.ElectricConductance +import com.splendo.kaluga.scientific.unit.Frequency +import com.splendo.kaluga.scientific.unit.Voltage + +val PhysicalQuantity.ElectricConductance.converters get() = listOf>( + QuantityConverterWithOperator("Electric Capacitance from Frequency", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Frequency) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Absiemens && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricConductance && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Current from Voltage", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Absiemens && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricConductance && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Electric Resistance") { value, unit -> + when (unit) { + is Absiemens -> DefaultScientificValue(value, unit).resistance() + is ElectricConductance -> DefaultScientificValue(value, unit).resistance() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Frequency from Electric Capacitance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCapacitance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ElectricConductance && rightUnit is ElectricCapacitance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricCurrentConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricCurrentConverters.kt new file mode 100644 index 000000000..1bc79369a --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricCurrentConverters.kt @@ -0,0 +1,96 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.electricCurrent.div +import com.splendo.kaluga.scientific.converter.electricCurrent.times +import com.splendo.kaluga.scientific.unit.Abampere +import com.splendo.kaluga.scientific.unit.Abhenry +import com.splendo.kaluga.scientific.unit.Abohm +import com.splendo.kaluga.scientific.unit.Absiemens +import com.splendo.kaluga.scientific.unit.Abvolt +import com.splendo.kaluga.scientific.unit.Biot +import com.splendo.kaluga.scientific.unit.ElectricConductance +import com.splendo.kaluga.scientific.unit.ElectricCurrent +import com.splendo.kaluga.scientific.unit.ElectricInductance +import com.splendo.kaluga.scientific.unit.ElectricResistance +import com.splendo.kaluga.scientific.unit.MagneticFlux +import com.splendo.kaluga.scientific.unit.Maxwell +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.Voltage + +val PhysicalQuantity.ElectricCurrent.converters get() = listOf>( + QuantityConverterWithOperator("Electric Charge from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abampere && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Biot && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCurrent && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Conductance from Voltage", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abampere && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Biot && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCurrent && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Magnetic Flux", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MagneticFlux) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abampere && rightUnit is Maxwell -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Biot && rightUnit is Maxwell -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCurrent && rightUnit is MagneticFlux -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Electric Inductance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricInductance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abampere && rightUnit is Abhenry -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Biot && rightUnit is Abhenry -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCurrent && rightUnit is ElectricInductance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Power from Voltage", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abampere && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Biot && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCurrent && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Voltage from Electric Conductance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricConductance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abampere && rightUnit is Absiemens -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Biot && rightUnit is Absiemens -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCurrent && rightUnit is ElectricConductance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Voltage from Electric Resistance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abampere && rightUnit is Abohm -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Biot && rightUnit is Abohm -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricCurrent && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricInductanceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricInductanceConverters.kt new file mode 100644 index 000000000..29aa62757 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricInductanceConverters.kt @@ -0,0 +1,62 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.electricInductance.div +import com.splendo.kaluga.scientific.converter.electricInductance.times +import com.splendo.kaluga.scientific.unit.Abampere +import com.splendo.kaluga.scientific.unit.Abhenry +import com.splendo.kaluga.scientific.unit.Biot +import com.splendo.kaluga.scientific.unit.ElectricCurrent +import com.splendo.kaluga.scientific.unit.ElectricInductance +import com.splendo.kaluga.scientific.unit.ElectricResistance +import com.splendo.kaluga.scientific.unit.Frequency +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.ElectricInductance.converters get() = listOf>( + QuantityConverterWithOperator("Electric Resistance from Frequency", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Frequency) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abhenry && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricInductance && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Resistance from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abhenry && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricInductance && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Electric Current", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abhenry && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Abhenry && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricInductance && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Electric Resistance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ElectricInductance && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricResistanceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricResistanceConverters.kt new file mode 100644 index 000000000..2210598a1 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ElectricResistanceConverters.kt @@ -0,0 +1,86 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.electricResistance.conductance +import com.splendo.kaluga.scientific.converter.electricResistance.div +import com.splendo.kaluga.scientific.converter.electricResistance.times +import com.splendo.kaluga.scientific.unit.Abampere +import com.splendo.kaluga.scientific.unit.Abcoulomb +import com.splendo.kaluga.scientific.unit.Abohm +import com.splendo.kaluga.scientific.unit.Biot +import com.splendo.kaluga.scientific.unit.ElectricCapacitance +import com.splendo.kaluga.scientific.unit.ElectricCharge +import com.splendo.kaluga.scientific.unit.ElectricCurrent +import com.splendo.kaluga.scientific.unit.ElectricInductance +import com.splendo.kaluga.scientific.unit.ElectricResistance +import com.splendo.kaluga.scientific.unit.Frequency +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.ElectricResistance.converters get() = listOf>( + SingleQuantityConverter("Electric Conductance") { value, unit -> + when (unit) { + is Abohm -> DefaultScientificValue(value, unit).conductance() + is ElectricResistance -> DefaultScientificValue(value, unit).conductance() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Electric Inductance from Frequency", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Frequency) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abohm && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricResistance && rightUnit is Frequency -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Inductance from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abohm && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricResistance && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Frequency from Electric Inductance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricInductance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ElectricResistance && rightUnit is ElectricInductance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Electric Charge", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCharge) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abohm && rightUnit is Abcoulomb -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricResistance && rightUnit is ElectricCharge -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Electric Capacitance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCapacitance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ElectricResistance && rightUnit is ElectricCapacitance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Voltage from Electric Current", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abohm && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Abohm && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ElectricResistance && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/EnergyConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/EnergyConverters.kt new file mode 100644 index 000000000..43a3183cd --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/EnergyConverters.kt @@ -0,0 +1,319 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.energy.absorbedBy +import com.splendo.kaluga.scientific.converter.energy.div +import com.splendo.kaluga.scientific.converter.energy.equivalentDoseBy +import com.splendo.kaluga.scientific.converter.energy.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Energy.converters get() = listOf>( + QuantityConverterWithOperator("Action from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Amount of Substance from Molar Energy", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Energy && rightUnit is MolarEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area from Surface Tension", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SurfaceTension) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is SurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Charge from Voltage", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Current from Magnetic Flux", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MagneticFlux) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Maxwell -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Maxwell -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is MagneticFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Length", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundal && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Heat Capacity from Temperature", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Temperature) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialEnergy && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomaryTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomaryTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Temperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Ionizing Radiation Absorbed Dose from Weight", QuantityConverter.WithOperator.Type.Custom("absorbed by"), PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) absorbedBy DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) absorbedBy DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) absorbedBy DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Ionizing Radiation Equivalent Dose from Weight", QuantityConverter.WithOperator.Type.Custom("equivalent dose by"), PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) equivalentDoseBy DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) equivalentDoseBy DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) equivalentDoseBy DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Force", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Force) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Dyne -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Erg && rightUnit is DyneMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Dyne -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is DyneMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundal && rightUnit is Poundal -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundForce && rightUnit is PoundForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is PoundForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is OunceForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is UKImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomaryForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is UKImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomaryForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Force -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Electric Current", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Erg && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Energy from Amount of Substance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AmountOfSubstance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialEnergy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Power from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is WattHour && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is WattHourMultiple && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Joule && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is JouleMultiple && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Erg && rightUnit is Second -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Second -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundal && rightUnit is Second -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundal && rightUnit is Minute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundForce && rightUnit is Second -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundForce && rightUnit is Minute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is Second -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is Minute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is Second -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is Minute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnit && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnit && rightUnit is Minute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnit && rightUnit is Second -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Pressure from Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Volume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is CubicCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is CubicCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundal && rightUnit is CubicFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundForce && rightUnit is CubicFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Volume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Energy from Weight", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialEnergy && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Surface Tension from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundal && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is SquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is SquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Temperature from Heat Capacity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.HeatCapacity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialEnergy && rightUnit is MetricAndUKImperialHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is MetricHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is UKImperialHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomaryHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is MetricHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is MetricAndUKImperialHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is UKImperialHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomaryHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is HeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Power", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Power) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is WattHour && rightUnit is Watt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is WattHour && rightUnit is WattMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is WattHourMultiple && rightUnit is Watt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is WattHourMultiple && rightUnit is Watt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is HorsepowerHour && rightUnit is Horsepower -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundForce && rightUnit is FootPoundForcePerMinute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is InchPoundForcePerMinute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnit && rightUnit is BritishThermalUnitPerHour -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnit && rightUnit is BritishThermalUnitPerMinute -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Power -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Voltage from Electric Charge", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCharge) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Abcoulomb -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Abcoulomb -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is ElectricCharge -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Pressure", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Pressure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Barye -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Erg && rightUnit is BaryeMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Barye -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is BaryeMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundal && rightUnit is PoundSquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForce && rightUnit is PoundSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchOunceForce && rightUnit is PoundSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is UKImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomaryPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is UKImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomaryPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is Pressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Ionizing Radiation Absorbed Dose", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.IonizingRadiationAbsorbedDose) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is Rad -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Erg && rightUnit is RadMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is Rad -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is RadMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is IonizingRadiationAbsorbedDose -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Ionizing Radiation Equivalent Dose", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.IonizingRadiationEquivalentDose) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Erg && rightUnit is RoentgenEquivalentMan -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Erg && rightUnit is RoentgenEquivalentManMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is RoentgenEquivalentMan -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgMultiple && rightUnit is RoentgenEquivalentManMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is IonizingRadiationEquivalentDose -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Specific Energy", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialEnergy && rightUnit is MetricSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is UKImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialEnergy && rightUnit is USCustomarySpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricEnergy && rightUnit is MetricSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is UKImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialEnergy && rightUnit is USCustomarySpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Energy && rightUnit is SpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ForceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ForceConverters.kt new file mode 100644 index 000000000..6e0ff1b3b --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/ForceConverters.kt @@ -0,0 +1,197 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.force.div +import com.splendo.kaluga.scientific.converter.force.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Force.converters get() = listOf>( + QuantityConverterWithOperator("Acceleration from Weight", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Dyne && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area from Pressure", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Pressure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Dyne && rightUnit is Barye -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Dyne && rightUnit is BaryeMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is Barye -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is BaryeMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is PoundForce && rightUnit is PoundSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is PoundForce && rightUnit is PoundSquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is PoundForce && rightUnit is KiloPoundSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is OunceForce && rightUnit is OunceSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Kip && rightUnit is KipSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Kip && rightUnit is KipSquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UsTonForce && rightUnit is USTonSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UsTonForce && rightUnit is USTonSquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonForce && rightUnit is ImperialTonSquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonForce && rightUnit is ImperialTonSquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is UKImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is USCustomaryPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is UKImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is USCustomaryPressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Pressure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Length", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Dyne && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricForce && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Poundal && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Surface Tension", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SurfaceTension) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricForce && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is SurfaceTension -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Momentum from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Dyne && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is TonneForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is GramForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MilligramForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Poundal && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is OunceForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is GrainForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UsTonForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Power from Speed", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Speed) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Dyne && rightUnit is MetricSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is MetricSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Speed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Pressure from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Dyne && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Poundal && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Poundal && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is PoundForce && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is PoundForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is OunceForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is GrainForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Kip && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Kip && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UsTonForce && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UsTonForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonForce && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Surface Tension from Length", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricForce && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Yank", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Yank) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Force && rightUnit is Yank -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Acceleration", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Acceleration) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Dyne && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is DyneMultiple && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is KilogramForce && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is GramForce && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MilligramForce && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is TonneForce && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricForce && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is OunceForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is OunceForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is GrainForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UsTonForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Acceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Yank from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryForce && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Force && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/FrequencyConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/FrequencyConverters.kt new file mode 100644 index 000000000..194402330 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/FrequencyConverters.kt @@ -0,0 +1,53 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.frequency.time +import com.splendo.kaluga.scientific.converter.frequency.times +import com.splendo.kaluga.scientific.unit.Abfarad +import com.splendo.kaluga.scientific.unit.Abhenry +import com.splendo.kaluga.scientific.unit.BeatsPerMinute +import com.splendo.kaluga.scientific.unit.ElectricCapacitance +import com.splendo.kaluga.scientific.unit.ElectricInductance +import com.splendo.kaluga.scientific.unit.Frequency + +val PhysicalQuantity.Frequency.converters get() = listOf>( + QuantityConverterWithOperator("Electric Conductance from Electric Capacitance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCapacitance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Frequency && rightUnit is Abfarad -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Frequency && rightUnit is ElectricCapacitance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Resistance from Electric Inductance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricInductance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Frequency && rightUnit is Abhenry -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Frequency && rightUnit is ElectricInductance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Time") { value, unit -> + when (unit) { + is BeatsPerMinute -> DefaultScientificValue(value, unit).time() + is Frequency -> DefaultScientificValue(value, unit).time() + else -> throw RuntimeException("Unexpected unit: $unit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/HeatCapacityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/HeatCapacityConverters.kt new file mode 100644 index 000000000..c4e9e5169 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/HeatCapacityConverters.kt @@ -0,0 +1,78 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.heatCapacity.div +import com.splendo.kaluga.scientific.converter.heatCapacity.times +import com.splendo.kaluga.scientific.unit.HeatCapacity +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.MetricAndUKImperialHeatCapacity +import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature +import com.splendo.kaluga.scientific.unit.MetricHeatCapacity +import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.MetricWeight +import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.Temperature +import com.splendo.kaluga.scientific.unit.UKImperialHeatCapacity +import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.USCustomaryHeatCapacity +import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.USCustomaryTemperature +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.Weight + +val PhysicalQuantity.HeatCapacity.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Temperature", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Temperature) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialHeatCapacity && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricHeatCapacity && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialHeatCapacity && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryHeatCapacity && rightUnit is USCustomaryTemperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is HeatCapacity && rightUnit is Temperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Heat Capacity from Weight", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialHeatCapacity && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialHeatCapacity && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialHeatCapacity && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricHeatCapacity && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialHeatCapacity && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialHeatCapacity && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryHeatCapacity && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryHeatCapacity && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is HeatCapacity && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Specific Heat Capacity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificHeatCapacity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialHeatCapacity && rightUnit is MetricSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialHeatCapacity && rightUnit is UKImperialSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricHeatCapacity && rightUnit is MetricSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialHeatCapacity && rightUnit is UKImperialSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryHeatCapacity && rightUnit is USCustomarySpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is HeatCapacity && rightUnit is SpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IlluminanceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IlluminanceConverters.kt new file mode 100644 index 000000000..d5df9e4aa --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IlluminanceConverters.kt @@ -0,0 +1,64 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.illuminance.div +import com.splendo.kaluga.scientific.converter.illuminance.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.Illuminance +import com.splendo.kaluga.scientific.unit.ImperialIlluminance +import com.splendo.kaluga.scientific.unit.Luminance +import com.splendo.kaluga.scientific.unit.MetricIlluminance +import com.splendo.kaluga.scientific.unit.Phot +import com.splendo.kaluga.scientific.unit.PhotMultiple +import com.splendo.kaluga.scientific.unit.SolidAngle +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.Illuminance.converters get() = listOf>( + QuantityConverterWithOperator("Luminance from Solid Angle", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SolidAngle) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Phot && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is PhotMultiple && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialIlluminance && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Illuminance && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Exposure from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricIlluminance && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialIlluminance && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Illuminance && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Flux from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Illuminance && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Solid Angle from Luminance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Luminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Illuminance && rightUnit is Luminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IonizingRadiationAbsobedDoseConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IonizingRadiationAbsobedDoseConverters.kt new file mode 100644 index 000000000..a009f61ef --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IonizingRadiationAbsobedDoseConverters.kt @@ -0,0 +1,53 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose.asSpecificEnergy +import com.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose.times +import com.splendo.kaluga.scientific.unit.Gram +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose +import com.splendo.kaluga.scientific.unit.Rad +import com.splendo.kaluga.scientific.unit.RadMultiple +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.Weight + +val PhysicalQuantity.IonizingRadiationAbsorbedDose.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Rad && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is RadMultiple && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationAbsorbedDose && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationAbsorbedDose && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationAbsorbedDose && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationAbsorbedDose && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Specific Energy") { value, unit -> + when (unit) { + is Rad -> DefaultScientificValue(value, unit).asSpecificEnergy() + is RadMultiple -> DefaultScientificValue(value, unit).asSpecificEnergy() + is IonizingRadiationAbsorbedDose -> DefaultScientificValue(value, unit).asSpecificEnergy() + else -> throw RuntimeException("Unexpected unit: $unit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IonizingRadiationEquivalentDoseConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IonizingRadiationEquivalentDoseConverters.kt new file mode 100644 index 000000000..7759899db --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/IonizingRadiationEquivalentDoseConverters.kt @@ -0,0 +1,53 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose.asSpecificEnergy +import com.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose.times +import com.splendo.kaluga.scientific.unit.Gram +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose +import com.splendo.kaluga.scientific.unit.RoentgenEquivalentMan +import com.splendo.kaluga.scientific.unit.RoentgenEquivalentManMultiple +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.Weight + +val PhysicalQuantity.IonizingRadiationEquivalentDose.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is RoentgenEquivalentMan && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is RoentgenEquivalentManMultiple && rightUnit is Gram -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationEquivalentDose && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationEquivalentDose && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationEquivalentDose && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is IonizingRadiationEquivalentDose && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Specific Energy") { value, unit -> + when (unit) { + is RoentgenEquivalentMan -> DefaultScientificValue(value, unit).asSpecificEnergy() + is RoentgenEquivalentManMultiple -> DefaultScientificValue(value, unit).asSpecificEnergy() + is IonizingRadiationEquivalentDose -> DefaultScientificValue(value, unit).asSpecificEnergy() + else -> throw RuntimeException("Unexpected unit: $unit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/JoltConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/JoltConverters.kt new file mode 100644 index 000000000..416da4788 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/JoltConverters.kt @@ -0,0 +1,52 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.jolt.times +import com.splendo.kaluga.scientific.unit.ImperialJolt +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.Jolt +import com.splendo.kaluga.scientific.unit.MetricJolt +import com.splendo.kaluga.scientific.unit.MetricWeight +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.Weight + +val PhysicalQuantity.Jolt.converters get() = listOf>( + QuantityConverterWithOperator("Acceleration from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricJolt && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialJolt && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Jolt && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Yank from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricJolt && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialJolt && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialJolt && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialJolt && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Jolt && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/KinematicViscosityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/KinematicViscosityConverters.kt new file mode 100644 index 000000000..f7a62e4d7 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/KinematicViscosityConverters.kt @@ -0,0 +1,59 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.kinematicViscosity.div +import com.splendo.kaluga.scientific.converter.kinematicViscosity.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.KinematicViscosity.converters get() = listOf>( + QuantityConverterWithOperator("Area from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricKinematicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialKinematicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is KinematicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Dynamic Viscosity from Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricKinematicViscosity && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialKinematicViscosity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialKinematicViscosity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialKinematicViscosity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is KinematicViscosity && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Energy from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricKinematicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialKinematicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is KinematicViscosity && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Specific Energy", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is KinematicViscosity && rightUnit is SpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LengthConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LengthConverters.kt new file mode 100644 index 000000000..60a484cc2 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LengthConverters.kt @@ -0,0 +1,165 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.length.div +import com.splendo.kaluga.scientific.converter.length.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Length.converters get() = listOf>( + QuantityConverterWithOperator("Area from Length", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Meter && rightUnit is Meter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Nanometer && rightUnit is Nanometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Micrometer && rightUnit is Micrometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Millimeter && rightUnit is Millimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Centimeter && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Decimeter && rightUnit is Decimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Decameter && rightUnit is Decameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hectometer && rightUnit is Hectometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Kilometer && rightUnit is Kilometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Megameter && rightUnit is Megameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Gigameter && rightUnit is Gigameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricLength && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Inch && rightUnit is Inch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Foot && rightUnit is Foot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Yard && rightUnit is Yard -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Mile && rightUnit is Mile -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area Density from Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLength && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area Density from Specific Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLength && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Force", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Force) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Centimeter && rightUnit is Dyne -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Centimeter && rightUnit is DyneMultiple -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricLength && rightUnit is MetricForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is Poundal -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Inch && rightUnit is PoundForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Inch && rightUnit is OunceForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is PoundForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is UKImperialForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is USCustomaryForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is Force -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Surface Tension", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SurfaceTension) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLength && rightUnit is MetricSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is UKImperialSurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is USCustomarySurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is SurfaceTension -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Linear Mass Density from Area Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AreaDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLength && rightUnit is MetricAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is AreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Volume from Area Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AreaDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLength && rightUnit is MetricAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is AreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Speed from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLength && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Speed", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Speed) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Length && rightUnit is Speed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Meter && rightUnit is SquareMeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Nanometer && rightUnit is SquareNanometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Micrometer && rightUnit is SquareMicrometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Millimeter && rightUnit is SquareMillimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Centimeter && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Decimeter && rightUnit is SquareDecimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Decameter && rightUnit is SquareDecameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hectometer && rightUnit is SquareHectometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Kilometer && rightUnit is SquareKilometer -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Megameter && rightUnit is SquareMegameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Gigameter && rightUnit is SquareGigameter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricLength && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Inch && rightUnit is SquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Foot && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Yard && rightUnit is SquareYard -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Mile && rightUnit is SquareMile -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Inch && rightUnit is Acre -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Foot && rightUnit is Acre -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Linear Mass Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.LinearMassDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLength && rightUnit is MetricLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is UKImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLength && rightUnit is USCustomaryLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Length && rightUnit is LinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LinearMassDensityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LinearMassDensityConverters.kt new file mode 100644 index 000000000..abcd47a9f --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LinearMassDensityConverters.kt @@ -0,0 +1,99 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.linearMassDensity.div +import com.splendo.kaluga.scientific.converter.linearMassDensity.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.LinearMassDensity.converters get() = listOf>( + QuantityConverterWithOperator("Area from Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLinearMassDensity && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LinearMassDensity && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area from Specific Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLinearMassDensity && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is LinearMassDensity && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area Density from Length", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLinearMassDensity && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LinearMassDensity && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Density from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLinearMassDensity && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LinearMassDensity && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Area Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AreaDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLinearMassDensity && rightUnit is MetricAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LinearMassDensity && rightUnit is AreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Length", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLinearMassDensity && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLinearMassDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialLinearMassDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryLinearMassDensity && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is LinearMassDensity && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminanceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminanceConverters.kt new file mode 100644 index 000000000..ff3e91bf0 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminanceConverters.kt @@ -0,0 +1,48 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.luminance.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.ImperialLuminance +import com.splendo.kaluga.scientific.unit.Lambert +import com.splendo.kaluga.scientific.unit.Luminance +import com.splendo.kaluga.scientific.unit.MetricLuminance +import com.splendo.kaluga.scientific.unit.SolidAngle +import com.splendo.kaluga.scientific.unit.Stilb + +val PhysicalQuantity.Luminance.converters get() = listOf>( + QuantityConverterWithOperator("Illuminance from Solid Angle", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SolidAngle) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Stilb && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Lambert && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricLuminance && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLuminance && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Luminance && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Intensity from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Luminance && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousEnergyConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousEnergyConverters.kt new file mode 100644 index 000000000..b12a51d29 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousEnergyConverters.kt @@ -0,0 +1,62 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.luminousEnergy.div +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.ImperialArea +import com.splendo.kaluga.scientific.unit.ImperialLuminousExposure +import com.splendo.kaluga.scientific.unit.LuminousEnergy +import com.splendo.kaluga.scientific.unit.LuminousExposure +import com.splendo.kaluga.scientific.unit.LuminousFlux +import com.splendo.kaluga.scientific.unit.MetricArea +import com.splendo.kaluga.scientific.unit.MetricLuminousExposure +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.LuminousEnergy.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Luminous Exposure", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.LuminousExposure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousEnergy && rightUnit is MetricLuminousExposure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousEnergy && rightUnit is ImperialLuminousExposure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousEnergy && rightUnit is LuminousExposure -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Exposure from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousEnergy && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousEnergy && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousEnergy && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Flux from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Luminous Flux", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.LuminousFlux) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousEnergy && rightUnit is LuminousFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousExposureConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousExposureConverters.kt new file mode 100644 index 000000000..823d271d3 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousExposureConverters.kt @@ -0,0 +1,60 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.luminousExposure.div +import com.splendo.kaluga.scientific.converter.luminousExposure.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.Illuminance +import com.splendo.kaluga.scientific.unit.ImperialArea +import com.splendo.kaluga.scientific.unit.ImperialIlluminance +import com.splendo.kaluga.scientific.unit.ImperialLuminousExposure +import com.splendo.kaluga.scientific.unit.LuminousExposure +import com.splendo.kaluga.scientific.unit.MetricArea +import com.splendo.kaluga.scientific.unit.MetricIlluminance +import com.splendo.kaluga.scientific.unit.MetricLuminousExposure +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.LuminousExposure.converters get() = listOf>( + QuantityConverterWithOperator("Illuminance from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLuminousExposure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLuminousExposure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousExposure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Energy from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLuminousExposure && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLuminousExposure && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousExposure && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Illuminance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Illuminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricLuminousExposure && rightUnit is MetricIlluminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialLuminousExposure && rightUnit is ImperialIlluminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousExposure && rightUnit is Illuminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousFluxConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousFluxConverters.kt new file mode 100644 index 000000000..60431667c --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousFluxConverters.kt @@ -0,0 +1,74 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.luminousFlux.div +import com.splendo.kaluga.scientific.converter.luminousFlux.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.Illuminance +import com.splendo.kaluga.scientific.unit.ImperialArea +import com.splendo.kaluga.scientific.unit.ImperialIlluminance +import com.splendo.kaluga.scientific.unit.LuminousFlux +import com.splendo.kaluga.scientific.unit.LuminousIntensity +import com.splendo.kaluga.scientific.unit.MetricArea +import com.splendo.kaluga.scientific.unit.Phot +import com.splendo.kaluga.scientific.unit.PhotMultiple +import com.splendo.kaluga.scientific.unit.SolidAngle +import com.splendo.kaluga.scientific.unit.SquareCentimeter +import com.splendo.kaluga.scientific.unit.Time + +val PhysicalQuantity.LuminousFlux.converters get() = listOf>( + QuantityConverterWithOperator("Area from Illuminance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Illuminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousFlux && rightUnit is Phot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousFlux && rightUnit is PhotMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousFlux && rightUnit is ImperialIlluminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousFlux && rightUnit is Illuminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Illuminance from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousFlux && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousFlux && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousFlux && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousFlux && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Energy from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousFlux && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Intensity from Solid Angle", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SolidAngle) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousFlux && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Solid Angle from Luminous Intensity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.LuminousIntensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousFlux && rightUnit is LuminousIntensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousIntensityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousIntensityConverters.kt new file mode 100644 index 000000000..0541acb3d --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/LuminousIntensityConverters.kt @@ -0,0 +1,62 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.luminousIntensity.div +import com.splendo.kaluga.scientific.converter.luminousIntensity.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.ImperialArea +import com.splendo.kaluga.scientific.unit.ImperialLuminance +import com.splendo.kaluga.scientific.unit.Lambert +import com.splendo.kaluga.scientific.unit.Luminance +import com.splendo.kaluga.scientific.unit.LuminousIntensity +import com.splendo.kaluga.scientific.unit.MetricArea +import com.splendo.kaluga.scientific.unit.MetricLuminance +import com.splendo.kaluga.scientific.unit.SolidAngle +import com.splendo.kaluga.scientific.unit.SquareCentimeter +import com.splendo.kaluga.scientific.unit.Stilb + +val PhysicalQuantity.LuminousIntensity.converters get() = listOf>( + QuantityConverterWithOperator("Area from Luminance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Luminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousIntensity && rightUnit is Stilb -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousIntensity && rightUnit is Lambert -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousIntensity && rightUnit is MetricLuminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousIntensity && rightUnit is ImperialLuminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousIntensity && rightUnit is Luminance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminance from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousIntensity && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousIntensity && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousIntensity && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is LuminousIntensity && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Flux from Solid Angle", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SolidAngle) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is LuminousIntensity && rightUnit is SolidAngle -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MagneticFluxConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MagneticFluxConverters.kt new file mode 100644 index 000000000..8d83f8205 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MagneticFluxConverters.kt @@ -0,0 +1,107 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.magneticFlux.div +import com.splendo.kaluga.scientific.converter.magneticFlux.times +import com.splendo.kaluga.scientific.unit.Abampere +import com.splendo.kaluga.scientific.unit.Abcoulomb +import com.splendo.kaluga.scientific.unit.Abhenry +import com.splendo.kaluga.scientific.unit.Abohm +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.Biot +import com.splendo.kaluga.scientific.unit.ElectricCharge +import com.splendo.kaluga.scientific.unit.ElectricCurrent +import com.splendo.kaluga.scientific.unit.ElectricInductance +import com.splendo.kaluga.scientific.unit.ElectricResistance +import com.splendo.kaluga.scientific.unit.Gauss +import com.splendo.kaluga.scientific.unit.MagneticFlux +import com.splendo.kaluga.scientific.unit.MagneticInduction +import com.splendo.kaluga.scientific.unit.Maxwell +import com.splendo.kaluga.scientific.unit.SquareCentimeter +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.Voltage + +val PhysicalQuantity.MagneticFlux.converters get() = listOf>( + QuantityConverterWithOperator("Area from Magnetic Induction", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MagneticInduction) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is Gauss -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is MagneticInduction -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Charge from Electric Resistance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is Abohm -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Current from Electric Inductance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricInductance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is Abhenry -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is ElectricInductance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Inductance from Electric Current", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Maxwell && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Resistance from Electric Charge", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCharge) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is Abcoulomb -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is ElectricCharge -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Electric Current", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Maxwell && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Induction from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Voltage", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MagneticFlux && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Voltage from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Maxwell && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticFlux && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MagneticInductionConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MagneticInductionConverters.kt new file mode 100644 index 000000000..48ef89306 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MagneticInductionConverters.kt @@ -0,0 +1,36 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.magneticInduction.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.Gauss +import com.splendo.kaluga.scientific.unit.MagneticInduction +import com.splendo.kaluga.scientific.unit.SquareCentimeter + +val PhysicalQuantity.MagneticInduction.converters get() = listOf>( + QuantityConverterWithOperator("Magnetic Flux from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Gauss && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MagneticInduction && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MassFlowRateConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MassFlowRateConverters.kt new file mode 100644 index 000000000..53873f955 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MassFlowRateConverters.kt @@ -0,0 +1,41 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.massFlowRate.times +import com.splendo.kaluga.scientific.unit.ImperialMassFlowRate +import com.splendo.kaluga.scientific.unit.MassFlowRate +import com.splendo.kaluga.scientific.unit.MetricMassFlowRate +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.UKImperialMassFlowRate +import com.splendo.kaluga.scientific.unit.USCustomaryMassFlowRate + +val PhysicalQuantity.MassFlowRate.converters get() = listOf>( + QuantityConverterWithOperator("Weight from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMassFlowRate && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMassFlowRate && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMassFlowRate && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMassFlowRate && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MassFlowRate && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolalityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolalityConverters.kt new file mode 100644 index 000000000..45763269e --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolalityConverters.kt @@ -0,0 +1,106 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.molality.div +import com.splendo.kaluga.scientific.converter.molality.molarMass +import com.splendo.kaluga.scientific.converter.molality.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Molality.converters get() = listOf>( + QuantityConverterWithOperator("Amount of Substance from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Molality && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Molar Mass") { value, unit -> + when (unit) { + is MetricMolality -> DefaultScientificValue(value, unit).molarMass() + is ImperialMolality -> DefaultScientificValue(value, unit).molarMass() + is UKImperialMolality -> DefaultScientificValue(value, unit).molarMass() + is USCustomaryMolality -> DefaultScientificValue(value, unit).molarMass() + is Molality -> DefaultScientificValue(value, unit).molarMass() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Molarity from Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Molality && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molarity from Specific Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Molality && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Energy from Molar Energy", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MolarEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolality && rightUnit is MetricAndImperialMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is MetricAndImperialMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolality && rightUnit is MetricAndImperialMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolality && rightUnit is MetricAndImperialMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricMolality && rightUnit is MetricMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is ImperialMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolality && rightUnit is ImperialMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolality && rightUnit is ImperialMolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is MolarEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Volume from Molar Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MolarVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolality && rightUnit is MetricMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is UKImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is USCustomaryMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolality && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolality && rightUnit is UKImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolality && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolality && rightUnit is USCustomaryMolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Volume from Molarity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Molarity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolality && rightUnit is MetricMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is UKImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolality && rightUnit is USCustomaryMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolality && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolality && rightUnit is UKImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolality && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolality && rightUnit is USCustomaryMolarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molality && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarEnergyConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarEnergyConverters.kt new file mode 100644 index 000000000..0df131888 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarEnergyConverters.kt @@ -0,0 +1,74 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.molarEnergy.div +import com.splendo.kaluga.scientific.converter.molarEnergy.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.MolarEnergy.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Amount of Substance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AmountOfSubstance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialMolarEnergy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricMolarEnergy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarEnergy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarEnergy && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Mass from Specific Energy", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MolarEnergy && rightUnit is MetricSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarEnergy && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarEnergy && rightUnit is UKImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarEnergy && rightUnit is USCustomarySpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarEnergy && rightUnit is SpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Energy from Molar Mass", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarMass) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialMolarEnergy && rightUnit is MetricMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialMolarEnergy && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialMolarEnergy && rightUnit is UKImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialMolarEnergy && rightUnit is USCustomaryMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricMolarEnergy && rightUnit is MetricMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarEnergy && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarEnergy && rightUnit is UKImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarEnergy && rightUnit is USCustomaryMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarEnergy && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Energy from Molality", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Molality) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndImperialMolarEnergy && rightUnit is MetricMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialMolarEnergy && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialMolarEnergy && rightUnit is UKImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialMolarEnergy && rightUnit is USCustomaryMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricMolarEnergy && rightUnit is MetricMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarEnergy && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarEnergy && rightUnit is UKImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarEnergy && rightUnit is USCustomaryMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarEnergy && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarMassConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarMassConverters.kt new file mode 100644 index 000000000..b8cdee8f1 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarMassConverters.kt @@ -0,0 +1,106 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.molarMass.div +import com.splendo.kaluga.scientific.converter.molarMass.molality +import com.splendo.kaluga.scientific.converter.molarMass.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.MolarMass.converters get() = listOf>( + QuantityConverterWithOperator("Density from Molarity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Molarity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarMass && rightUnit is MetricMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarMass && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarMass && rightUnit is UKImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarMass && rightUnit is USCustomaryMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarMass && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarMass && rightUnit is UKImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarMass && rightUnit is ImperialMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarMass && rightUnit is USCustomaryMolarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Density from Molar Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarMass && rightUnit is MetricMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarMass && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarMass && rightUnit is UKImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarMass && rightUnit is USCustomaryMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarMass && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarMass && rightUnit is UKImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarMass && rightUnit is ImperialMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarMass && rightUnit is USCustomaryMolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Molality") { value, unit -> + when (unit) { + is MetricMolarMass -> DefaultScientificValue(value, unit).molality() + is ImperialMolarMass -> DefaultScientificValue(value, unit).molality() + is UKImperialMolarMass -> DefaultScientificValue(value, unit).molality() + is USCustomaryMolarMass -> DefaultScientificValue(value, unit).molality() + is MolarMass -> DefaultScientificValue(value, unit).molality() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Molar Energy from Specific Energy", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MolarMass && rightUnit is MetricSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is UKImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is USCustomarySpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is SpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Volume from Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MolarMass && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Volume from Specific Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MolarMass && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Amount of Substance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AmountOfSubstance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarMass && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarMass && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarMass && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarMass && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarMass && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarVolumeConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarVolumeConverters.kt new file mode 100644 index 000000000..7944030c4 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarVolumeConverters.kt @@ -0,0 +1,96 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.molarVolume.div +import com.splendo.kaluga.scientific.converter.molarVolume.molarity +import com.splendo.kaluga.scientific.converter.molarVolume.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.MolarVolume.converters get() = listOf>( + SingleQuantityConverter("Molarity") { value, unit -> + when (unit) { + is MetricMolarVolume -> DefaultScientificValue(value, unit).molarity() + is ImperialMolarVolume -> DefaultScientificValue(value, unit).molarity() + is UKImperialMolarVolume -> DefaultScientificValue(value, unit).molarity() + is USCustomaryMolarVolume -> DefaultScientificValue(value, unit).molarity() + is MolarVolume -> DefaultScientificValue(value, unit).molarity() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Molar Mass from Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MolarVolume && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Mass from Specific Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MolarVolume && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Volume from Molality", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Molality) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarVolume && rightUnit is MetricMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarVolume && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarVolume && rightUnit is UKImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarVolume && rightUnit is USCustomaryMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarVolume && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarVolume && rightUnit is UKImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarVolume && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarVolume && rightUnit is USCustomaryMolality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Volume from Molar Mass", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarMass) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarVolume && rightUnit is MetricMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarVolume && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarVolume && rightUnit is UKImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarVolume && rightUnit is USCustomaryMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarVolume && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarVolume && rightUnit is UKImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarVolume && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarVolume && rightUnit is USCustomaryMolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Amount of Substance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AmountOfSubstance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MolarVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarityConverters.kt new file mode 100644 index 000000000..5e6b6cc33 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MolarityConverters.kt @@ -0,0 +1,92 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.molarity.div +import com.splendo.kaluga.scientific.converter.molarity.molarVolume +import com.splendo.kaluga.scientific.converter.molarity.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Molarity.converters get() = listOf>( + QuantityConverterWithOperator("Amount of Substance from Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Volume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Molarity && rightUnit is Volume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Density from Molality", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Molality) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarity && rightUnit is MetricMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarity && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarity && rightUnit is UKImperialMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarity && rightUnit is USCustomaryMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarity && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarity && rightUnit is UKImperialMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarity && rightUnit is ImperialMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarity && rightUnit is USCustomaryMolality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Density from Molar Mass", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MolarMass) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMolarity && rightUnit is MetricMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarity && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarity && rightUnit is UKImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMolarity && rightUnit is USCustomaryMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarity && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMolarity && rightUnit is UKImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarity && rightUnit is ImperialMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMolarity && rightUnit is USCustomaryMolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molality from Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Molarity && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molality from Specific Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Molarity && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Molarity && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Molar Volume") { value, unit -> + when (unit) { + is MetricMolarity -> DefaultScientificValue(value, unit).molarVolume() + is ImperialMolarity -> DefaultScientificValue(value, unit).molarVolume() + is UKImperialMolarity -> DefaultScientificValue(value, unit).molarVolume() + is USCustomaryMolarity -> DefaultScientificValue(value, unit).molarVolume() + is Molarity -> DefaultScientificValue(value, unit).molarVolume() + else -> throw RuntimeException("Unexpected unit: $unit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MomentumConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MomentumConverters.kt new file mode 100644 index 000000000..30e97ffcd --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/MomentumConverters.kt @@ -0,0 +1,84 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.momentum.div +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Momentum.converters get() = listOf>( + QuantityConverterWithOperator("Area from Dynamic Viscosity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.DynamicViscosity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMomentum && rightUnit is MetricDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is ImperialDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is UKImperialDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is USCustomaryDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMomentum && rightUnit is ImperialDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMomentum && rightUnit is UKImperialDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMomentum && rightUnit is ImperialDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMomentum && rightUnit is USCustomaryDynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Momentum && rightUnit is DynamicViscosity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Dynamic Viscosity from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMomentum && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMomentum && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMomentum && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Momentum && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMomentum && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMomentum && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMomentum && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Momentum && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Speed from Weight", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMomentum && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMomentum && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMomentum && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMomentum && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMomentum && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Momentum && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Speed", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Speed) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricMomentum && rightUnit is MetricSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialMomentum && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialMomentum && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryMomentum && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Momentum && rightUnit is Speed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/PowerConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/PowerConverters.kt new file mode 100644 index 000000000..fc8d22e8d --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/PowerConverters.kt @@ -0,0 +1,113 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.power.div +import com.splendo.kaluga.scientific.converter.power.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Power.converters get() = listOf>( + QuantityConverterWithOperator("Electric Current from Voltage", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ErgPerSecond && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Power && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Watt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Nanowatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Microwatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Milliwatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Centiwatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Deciwatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Decawatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hectowatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Kilowatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Megawatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Gigawatt && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgPerSecond && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricPower && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForcePerSecond && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForcePerMinute && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Horsepower && rightUnit is Hour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnitPerSecond && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnitPerMinute && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnitPerHour && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPower && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Power && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Speed", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Speed) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ErgPerSecond && rightUnit is MetricSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricPower && rightUnit is MetricSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPower && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialPower && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Power && rightUnit is Speed -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Speed from Force", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Force) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ErgPerSecond && rightUnit is Dyne -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgPerSecond && rightUnit is DyneMultiple -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricPower && rightUnit is MetricForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundForcePerSecond && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is FootPoundForcePerMinute && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForcePerSecond && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is InchPoundForcePerMinute && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnitPerSecond && rightUnit is PoundForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnitPerMinute && rightUnit is PoundForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is BritishThermalUnitPerHour && rightUnit is PoundForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPower && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPower && rightUnit is UKImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPower && rightUnit is USCustomaryForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialPower && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialPower && rightUnit is UKImperialForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialPower && rightUnit is USCustomaryForce -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Power && rightUnit is Force -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Temperature from Thermal Resistance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ThermalResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Power && rightUnit is MetricAndUKImperialThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialPower && rightUnit is MetricThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricPower && rightUnit is MetricThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialPower && rightUnit is UKImperialThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPower && rightUnit is UKImperialThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndImperialPower && rightUnit is USCustomaryThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPower && rightUnit is USCustomaryThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Power && rightUnit is ThermalResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Voltage from Electric Current", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is ErgPerSecond && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ErgPerSecond && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Power && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/PressureConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/PressureConverters.kt new file mode 100644 index 000000000..bf4f518e7 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/PressureConverters.kt @@ -0,0 +1,76 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.pressure.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Pressure.converters get() = listOf>( + QuantityConverterWithOperator("Dynamic Viscosity from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricPressure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPressure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialPressure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryPressure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Pressure && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Volume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Barye && rightUnit is CubicCentimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is BaryeMultiple && rightUnit is CubicCentimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is PoundSquareFoot && rightUnit is CubicFoot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is PoundSquareInch && rightUnit is CubicInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is OunceSquareInch && rightUnit is CubicInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is KiloPoundSquareInch && rightUnit is CubicInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is KipSquareInch && rightUnit is CubicInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USTonSquareInch && rightUnit is CubicInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonSquareInch && rightUnit is CubicInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPressure && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPressure && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPressure && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialPressure && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialPressure && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryPressure && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryPressure && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Pressure && rightUnit is Volume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Barye && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is BaryeMultiple && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is OunceSquareInch && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is KipSquareInch && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is KipSquareFoot && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USTonSquareInch && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USTonSquareFoot && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonSquareInch && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTonSquareFoot && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialPressure && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialPressure && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryPressure && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Pressure && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/QuantityConverter.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/QuantityConverter.kt new file mode 100644 index 000000000..8621f6aeb --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/QuantityConverter.kt @@ -0,0 +1,104 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.base.utils.Decimal +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.ScientificValue +import com.splendo.kaluga.scientific.unit.ScientificUnit +import kotlin.reflect.KClass + +sealed class QuantityConverter { + abstract val name: String + + data class Single( + val fromQuantity: KClass, + val resultQuantity: KClass, + override val name: String, + val converter: (Decimal, ScientificUnit) -> ScientificValue + ) : QuantityConverter() { + + @Suppress("UNCHECKED_CAST") + fun convert( + value: Decimal, + unit: ScientificUnit<*> + ): ScientificValue<*, *>? { + return if (fromQuantity.isInstance(unit.quantity)) { + converter(value, unit as ScientificUnit) + } else { + null + } + } + } + + data class WithOperator( + val leftQuantity: KClass, + val rightQuantity: Right, + val resultQuantity: KClass, + override val name: String, + val type: Type, + val converter: (left: Pair>, right: Pair>) -> ScientificValue + ) : QuantityConverter() { + sealed class Type { + + abstract val operatorSymbol: String + + object Multiplication : Type() { + override val operatorSymbol: String = "*" + } + + object Division : Type() { + override val operatorSymbol: String = "/" + } + + data class Custom(override val operatorSymbol: String) : Type() + } + + @Suppress("UNCHECKED_CAST") + fun convert( + left: Decimal, + leftUnit: ScientificUnit<*>, + right: Decimal, + rightUnit: ScientificUnit<*> + ): ScientificValue<*, *>? { + return if (leftQuantity.isInstance(leftUnit.quantity) && rightQuantity::class.isInstance( + rightUnit.quantity + ) + ) { + converter( + left to leftUnit as ScientificUnit, + right to rightUnit as ScientificUnit + ) + } else { + null + } + } + } +} + +inline fun SingleQuantityConverter( + name: String, + noinline converter: (value: Decimal, unit: ScientificUnit) -> ScientificValue +) = QuantityConverter.Single(From::class, Result::class, name, converter) + +inline fun QuantityConverterWithOperator( + name: String, + type: QuantityConverter.WithOperator.Type, + right: Right, + noinline converter: (left: Pair>, right: Pair>) -> ScientificValue +) = QuantityConverter.WithOperator(Left::class, right, Result::class, name, type, converter) diff --git a/links/src/androidLibMain/kotlin/com/splendo/kaluga/links/LinksBuilder.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/RadioactivityConverters.kt similarity index 65% rename from links/src/androidLibMain/kotlin/com/splendo/kaluga/links/LinksBuilder.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/RadioactivityConverters.kt index dae1ca54a..a5d021d9c 100644 --- a/links/src/androidLibMain/kotlin/com/splendo/kaluga/links/LinksBuilder.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/RadioactivityConverters.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 Splendo Consulting B.V. The Netherlands + Copyright 2023 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,11 +15,8 @@ */ -package com.splendo.kaluga.links +package com.splendo.kaluga.example.shared.model.scientific.converters -import com.splendo.kaluga.links.manager.LinksManagerBuilder +import com.splendo.kaluga.scientific.PhysicalQuantity -actual class LinksBuilder : Links.Builder { - override fun create(): Links = - Links(LinksManagerBuilder()) -} +val PhysicalQuantity.Radioactivity.converters get() = listOf>() diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SolidAngleConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SolidAngleConverters.kt new file mode 100644 index 000000000..2ad6582ab --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SolidAngleConverters.kt @@ -0,0 +1,42 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.solidAngle.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.SolidAngle.converters get() = listOf>( + QuantityConverterWithOperator("Illuminance from Luminance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Luminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is SolidAngle && rightUnit is Luminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SolidAngle && rightUnit is Lambert -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SolidAngle && rightUnit is MetricLuminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SolidAngle && rightUnit is ImperialLuminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SolidAngle && rightUnit is Luminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Flux from Luminous Intensity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.LuminousIntensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is SolidAngle && rightUnit is LuminousIntensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificEnergyConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificEnergyConverters.kt new file mode 100644 index 000000000..75f81b61b --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificEnergyConverters.kt @@ -0,0 +1,117 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.specificEnergy.asAbsorbedDose +import com.splendo.kaluga.scientific.converter.specificEnergy.asEquivalentDose +import com.splendo.kaluga.scientific.converter.specificEnergy.div +import com.splendo.kaluga.scientific.converter.specificEnergy.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.SpecificEnergy.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificEnergy && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificEnergy && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Ionizing Radiation Absorbed Dose") { value, unit -> + when (unit) { + is SpecificEnergy -> DefaultScientificValue(value, unit).asAbsorbedDose() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + SingleQuantityConverter("Ionizing Radiation Equivalent Dose") { value, unit -> + when (unit) { + is SpecificEnergy -> DefaultScientificValue(value, unit).asEquivalentDose() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Kinematic Viscosity from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificEnergy && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molality from Molar Energy", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificEnergy && rightUnit is MolarEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is MolarEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is MolarEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is MolarEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificEnergy && rightUnit is MolarEnergy -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Energy from Molar Mass", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MolarMass) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificEnergy && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificEnergy && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Energy from Molality", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Molality) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificEnergy && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificEnergy && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Heat Capacity from Temperature", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Temperature) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificEnergy && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is USCustomaryTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is USCustomaryTemperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificEnergy && rightUnit is Temperature -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Temperature from Specific Heat Capacity", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificHeatCapacity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificEnergy && rightUnit is MetricSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is UKImperialSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificEnergy && rightUnit is USCustomarySpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificEnergy && rightUnit is UKImperialSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificEnergy && rightUnit is USCustomarySpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificEnergy && rightUnit is SpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificHeatCapacityConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificHeatCapacityConverters.kt new file mode 100644 index 000000000..2dc53c94d --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificHeatCapacityConverters.kt @@ -0,0 +1,57 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.specificHeatCapacity.times +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature +import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.MetricWeight +import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.Temperature +import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity +import com.splendo.kaluga.scientific.unit.USCustomaryTemperature +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.Weight + +val PhysicalQuantity.SpecificHeatCapacity.converters get() = listOf>( + QuantityConverterWithOperator("Heat Capacity from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificHeatCapacity && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificHeatCapacity && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificHeatCapacity && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificHeatCapacity && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificHeatCapacity && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificHeatCapacity && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Energy from Temperature", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Temperature) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificHeatCapacity && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificHeatCapacity && rightUnit is MetricAndUKImperialTemperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificHeatCapacity && rightUnit is USCustomaryTemperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificHeatCapacity && rightUnit is Temperature -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificVolumeConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificVolumeConverters.kt new file mode 100644 index 000000000..9bcc2a12f --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpecificVolumeConverters.kt @@ -0,0 +1,120 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.specificVolume.density +import com.splendo.kaluga.scientific.converter.specificVolume.div +import com.splendo.kaluga.scientific.converter.specificVolume.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.SpecificVolume.converters get() = listOf>( + QuantityConverterWithOperator("Area from Linear Mass Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.LinearMassDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificVolume && rightUnit is MetricLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is UKImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is USCustomaryLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is UKImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is USCustomaryLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificVolume && rightUnit is LinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Density") { value, unit -> + when (unit) { + is MetricSpecificVolume -> DefaultScientificValue(value, unit).density() + is ImperialSpecificVolume -> DefaultScientificValue(value, unit).density() + is UKImperialSpecificVolume -> DefaultScientificValue(value, unit).density() + is USCustomarySpecificVolume -> DefaultScientificValue(value, unit).density() + is SpecificVolume -> DefaultScientificValue(value, unit).density() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Length from Area Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AreaDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificVolume && rightUnit is MetricAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificVolume && rightUnit is AreaDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molality from Molar Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificVolume && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificVolume && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molality from Molarity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Molarity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificVolume && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificVolume && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Volume from Molar Mass", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MolarMass) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificVolume && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificVolume && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Volume from Molality", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Molality) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificVolume && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificVolume && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpecificVolume && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpecificVolume && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSpecificVolume && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySpecificVolume && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SpecificVolume && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpeedConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpeedConverters.kt new file mode 100644 index 000000000..fe47ba732 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SpeedConverters.kt @@ -0,0 +1,85 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.speed.div +import com.splendo.kaluga.scientific.converter.speed.times +import com.splendo.kaluga.scientific.unit.Acceleration +import com.splendo.kaluga.scientific.unit.Dyne +import com.splendo.kaluga.scientific.unit.DyneMultiple +import com.splendo.kaluga.scientific.unit.Force +import com.splendo.kaluga.scientific.unit.ImperialForce +import com.splendo.kaluga.scientific.unit.ImperialSpeed +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.MetricSpeed +import com.splendo.kaluga.scientific.unit.MetricWeight +import com.splendo.kaluga.scientific.unit.Speed +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.UKImperialForce +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.USCustomaryForce +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.Weight + +val PhysicalQuantity.Speed.converters get() = listOf>( + QuantityConverterWithOperator("Acceleration from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpeed && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Speed && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpeed && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Speed && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Momentum from Weight", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpeed && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Speed && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Power from Force", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Force) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSpeed && rightUnit is Dyne -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricSpeed && rightUnit is DyneMultiple -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is UKImperialForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSpeed && rightUnit is USCustomaryForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Speed && rightUnit is Force -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Acceleration", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Acceleration) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Speed && rightUnit is Acceleration -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SurfaceTensionConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SurfaceTensionConverters.kt new file mode 100644 index 000000000..7db98dc5d --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/SurfaceTensionConverters.kt @@ -0,0 +1,50 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.surfaceTension.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.SurfaceTension.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSurfaceTension && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricSurfaceTension && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSurfaceTension && rightUnit is SquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSurfaceTension && rightUnit is SquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySurfaceTension && rightUnit is SquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSurfaceTension && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSurfaceTension && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySurfaceTension && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SurfaceTension && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Length", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricSurfaceTension && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialSurfaceTension && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialSurfaceTension && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomarySurfaceTension && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is SurfaceTension && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TemperatureConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TemperatureConverters.kt new file mode 100644 index 000000000..4f93f01ad --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TemperatureConverters.kt @@ -0,0 +1,66 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.temperature.div +import com.splendo.kaluga.scientific.converter.temperature.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Temperature.converters get() = listOf>( + QuantityConverterWithOperator("Energy from Heat Capacity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.HeatCapacity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialTemperature && rightUnit is MetricAndUKImperialHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialTemperature && rightUnit is MetricHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialTemperature && rightUnit is UKImperialHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryTemperature && rightUnit is USCustomaryHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Temperature && rightUnit is HeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Power from Thermal Resistance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ThermalResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialTemperature && rightUnit is MetricAndUKImperialThermalResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialTemperature && rightUnit is MetricThermalResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialTemperature && rightUnit is UKImperialThermalResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryTemperature && rightUnit is USCustomaryThermalResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Temperature && rightUnit is ThermalResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Energy from Specific Heat Capacity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificHeatCapacity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialTemperature && rightUnit is MetricSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialTemperature && rightUnit is UKImperialSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryTemperature && rightUnit is USCustomarySpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Temperature && rightUnit is SpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Thermal Resistance from Power", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Power) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialTemperature && rightUnit is MetricAndImperialPower -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialTemperature && rightUnit is MetricPower -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricAndUKImperialTemperature && rightUnit is ImperialPower -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryTemperature && rightUnit is ImperialPower -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Temperature && rightUnit is Power -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TheralResistanceConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TheralResistanceConverters.kt new file mode 100644 index 000000000..d5604dd51 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TheralResistanceConverters.kt @@ -0,0 +1,47 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.thermalResistance.times +import com.splendo.kaluga.scientific.unit.ImperialPower +import com.splendo.kaluga.scientific.unit.MetricAndImperialPower +import com.splendo.kaluga.scientific.unit.MetricAndUKImperialThermalResistance +import com.splendo.kaluga.scientific.unit.MetricPower +import com.splendo.kaluga.scientific.unit.MetricThermalResistance +import com.splendo.kaluga.scientific.unit.Power +import com.splendo.kaluga.scientific.unit.ThermalResistance +import com.splendo.kaluga.scientific.unit.UKImperialThermalResistance +import com.splendo.kaluga.scientific.unit.USCustomaryThermalResistance + +val PhysicalQuantity.ThermalResistance.converters get() = listOf>( + QuantityConverterWithOperator("Temperature from Power", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Power) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricAndUKImperialThermalResistance && rightUnit is Power -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricThermalResistance && rightUnit is MetricAndImperialPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricThermalResistance && rightUnit is MetricPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialThermalResistance && rightUnit is MetricAndImperialPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialThermalResistance && rightUnit is ImperialPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryThermalResistance && rightUnit is MetricAndImperialPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryThermalResistance && rightUnit is ImperialPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ThermalResistance && rightUnit is Power -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TimeConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TimeConverters.kt new file mode 100644 index 000000000..e32155543 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/TimeConverters.kt @@ -0,0 +1,232 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.time.div +import com.splendo.kaluga.scientific.converter.time.frequency +import com.splendo.kaluga.scientific.converter.time.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Time.converters get() = listOf>( + QuantityConverterWithOperator("Acceleration from Jolt", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Jolt) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricJolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialJolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Jolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Action from Energy", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Energy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricAndImperialEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is MetricEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Energy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Amount of Substance from Catalystic Activity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.CatalysticActivity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is CatalysticActivity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Angle from Angular Velocity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AngularVelocity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is AngularVelocity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Angular Velocity from Angular Acceleration", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.AngularAcceleration) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is AngularAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Dynamic Viscosity from Pressure", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Pressure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is UKImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is USCustomaryPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Pressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Capacitance from Electric Resistance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is Abohm -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Charge from Electric Current", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Inductance from Electric Resistance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is Abohm -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Resistance from Electric Capacitance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCapacitance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is Abfarad -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ElectricCapacitance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Power", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Power) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Hour && rightUnit is Watt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Nanowatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Microwatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Milliwatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Centiwatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Deciwatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Decawatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Hectowatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Kilowatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Megawatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Gigawatt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ErgPerSecond -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is MetricPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is InchPoundForcePerSecond -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is InchPoundForcePerMinute -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Hour && rightUnit is Horsepower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is BritishThermalUnitPerSecond -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is BritishThermalUnitPerMinute -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is BritishThermalUnitPerHour -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialPower -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Power -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Yank", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Yank) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricYank -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialYank -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is UKImperialYank -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is USCustomaryYank -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Yank -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + SingleQuantityConverter("Frequency") { value, unit -> + when (unit) { + is Minute -> DefaultScientificValue(value, unit).frequency() + is Time -> DefaultScientificValue(value, unit).frequency() + else -> throw RuntimeException("Unexpected unit: $unit") + } + }, + QuantityConverterWithOperator("Kinematic Viscosity from Specific Energy", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is UKImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is USCustomarySpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is SpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Speed", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Speed) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Speed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Energy from Luminous Flux", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.LuminousFlux) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is LuminousFlux -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Luminous Exposure from Illuminance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Illuminance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricIlluminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialIlluminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Illuminance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Voltage", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Voltage) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is Abvolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Voltage -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Momentum from Force", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Force) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is Dyne -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is DyneMultiple -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is TonneForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is GramForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is MilligramForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is MetricForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Poundal -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is OunceForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is GrainForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is UsTonForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialTonForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is UKImperialForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is USCustomaryForce -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Force -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Speed from Acceleration", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Acceleration) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is Acceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Volumetric Flow", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.VolumetricFlow) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricVolumetricFlow -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialVolumetricFlow -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is UKImperialVolumetricFlow -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is USCustomaryVolumetricFlow -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is VolumetricFlow -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Mass Flow Rate", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.MassFlowRate) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Time && rightUnit is MetricMassFlowRate -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is ImperialMassFlowRate -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is UKImperialMassFlowRate -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is USCustomaryMassFlowRate -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Time && rightUnit is MassFlowRate -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VoltageConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VoltageConverters.kt new file mode 100644 index 000000000..e004dc6c9 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VoltageConverters.kt @@ -0,0 +1,91 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.voltage.div +import com.splendo.kaluga.scientific.converter.voltage.times +import com.splendo.kaluga.scientific.unit.Abampere +import com.splendo.kaluga.scientific.unit.Abcoulomb +import com.splendo.kaluga.scientific.unit.Abfarad +import com.splendo.kaluga.scientific.unit.Abohm +import com.splendo.kaluga.scientific.unit.Absiemens +import com.splendo.kaluga.scientific.unit.Abvolt +import com.splendo.kaluga.scientific.unit.Biot +import com.splendo.kaluga.scientific.unit.ElectricCapacitance +import com.splendo.kaluga.scientific.unit.ElectricCharge +import com.splendo.kaluga.scientific.unit.ElectricConductance +import com.splendo.kaluga.scientific.unit.ElectricCurrent +import com.splendo.kaluga.scientific.unit.ElectricResistance +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.Voltage + +val PhysicalQuantity.Voltage.converters get() = listOf>( + QuantityConverterWithOperator("Electric Charge from Electric Capacitance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCapacitance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abvolt && rightUnit is Abfarad -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Voltage && rightUnit is ElectricCapacitance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Current from Electric Conductance", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricConductance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abvolt && rightUnit is Absiemens -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Voltage && rightUnit is ElectricConductance -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Current from Electric Resistance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricResistance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abvolt && rightUnit is Abohm -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Voltage && rightUnit is ElectricResistance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Electric Resistance from Electric Current", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abvolt && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Abvolt && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Voltage && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Electric Charge", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCharge) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abvolt && rightUnit is Abcoulomb -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Voltage && rightUnit is ElectricCharge -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Magnetic Flux from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abvolt && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Voltage && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Power from Electric Current", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.ElectricCurrent) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Abvolt && rightUnit is Abampere -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Abvolt && rightUnit is Biot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Voltage && rightUnit is ElectricCurrent -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumeConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumeConverters.kt new file mode 100644 index 000000000..3e1fda037 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumeConverters.kt @@ -0,0 +1,177 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.volume.div +import com.splendo.kaluga.scientific.converter.volume.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Volume.converters get() = listOf>( + QuantityConverterWithOperator("Amount of Substance from Molarity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Molarity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Volume && rightUnit is Molarity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Amount of Substance from Molar Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Volume && rightUnit is MolarVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area from Length", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is CubicMeter && rightUnit is Meter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicNanometer && rightUnit is Nanometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMicrometer && rightUnit is Micrometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMillimeter && rightUnit is Millimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicCentimeter && rightUnit is Centimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicDecimeter && rightUnit is Decimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicDecameter && rightUnit is Decameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicHectometer && rightUnit is Hectometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicKilometer && rightUnit is Kilometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMegameter && rightUnit is Megameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicGigameter && rightUnit is Gigameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricVolume && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is Inch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicFoot && rightUnit is Foot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicYard && rightUnit is Yard -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMile && rightUnit is Mile -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is AcreInch && rightUnit is Inch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is AcreFoot && rightUnit is Foot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Pressure", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Pressure) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is CubicCentimeter && rightUnit is Barye -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicCentimeter && rightUnit is BaryeMultiple -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicFoot && rightUnit is PoundSquareFoot -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is PoundSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is OunceSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is KiloPoundSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is KipSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is USTonSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is ImperialTonSquareInch -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is UKImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is USCustomaryPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is UKImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is ImperialPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is USCustomaryPressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is Pressure -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is CubicMeter && rightUnit is SquareMeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicNanometer && rightUnit is SquareNanometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMicrometer && rightUnit is SquareMicrometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMillimeter && rightUnit is SquareMillimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicCentimeter && rightUnit is SquareCentimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicDecimeter && rightUnit is SquareDecimeter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicDecameter && rightUnit is SquareDecameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicHectometer && rightUnit is SquareHectometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicKilometer && rightUnit is SquareKilometer -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMegameter && rightUnit is SquareMegameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicGigameter && rightUnit is SquareGigameter -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricVolume && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicInch && rightUnit is SquareInch -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicFoot && rightUnit is SquareFoot -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicYard && rightUnit is SquareYard -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is CubicMile && rightUnit is SquareMile -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is AcreInch && rightUnit is Acre -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is AcreFoot && rightUnit is Acre -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Volume from Amount of Substance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AmountOfSubstance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Specific Volume from Weight", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolume && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volumetric Flow from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolume && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Density", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolume && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Specific Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolume && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolume && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolume && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolume && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Volume && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumetricFlowConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumetricFlowConverters.kt new file mode 100644 index 000000000..c7a46dd9e --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumetricFlowConverters.kt @@ -0,0 +1,61 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.volumetricFlow.div +import com.splendo.kaluga.scientific.converter.volumetricFlow.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.VolumetricFlow.converters get() = listOf>( + QuantityConverterWithOperator("Area from Volumetric Flux", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.VolumetricFlux) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolumetricFlow && rightUnit is MetricVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolumetricFlow && rightUnit is ImperialVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolumetricFlow && rightUnit is UKImperialVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolumetricFlow && rightUnit is USCustomaryVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolumetricFlow && rightUnit is ImperialVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolumetricFlow && rightUnit is UKImperialVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolumetricFlow && rightUnit is ImperialVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolumetricFlow && rightUnit is USCustomaryVolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is VolumetricFlow && rightUnit is VolumetricFlux -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolumetricFlow && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolumetricFlow && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolumetricFlow && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolumetricFlow && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is VolumetricFlow && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volumetric Flux from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolumetricFlow && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolumetricFlow && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolumetricFlow && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolumetricFlow && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is VolumetricFlow && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumetricFluxConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumetricFluxConverters.kt new file mode 100644 index 000000000..9b7fc4e50 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/VolumetricFluxConverters.kt @@ -0,0 +1,43 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.volumetricFlux.times +import com.splendo.kaluga.scientific.unit.Area +import com.splendo.kaluga.scientific.unit.ImperialArea +import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlux +import com.splendo.kaluga.scientific.unit.MetricArea +import com.splendo.kaluga.scientific.unit.MetricVolumetricFlux +import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlux +import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlux +import com.splendo.kaluga.scientific.unit.VolumetricFlux + +val PhysicalQuantity.VolumetricFlux.converters get() = listOf>( + QuantityConverterWithOperator("Volumetric Flow from Area", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricVolumetricFlux && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialVolumetricFlux && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialVolumetricFlux && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryVolumetricFlux && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is VolumetricFlux && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/WeightConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/WeightConverters.kt new file mode 100644 index 000000000..d6915e960 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/WeightConverters.kt @@ -0,0 +1,238 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.weight.div +import com.splendo.kaluga.scientific.converter.weight.times +import com.splendo.kaluga.scientific.unit.* + +val PhysicalQuantity.Weight.converters get() = listOf>( + QuantityConverterWithOperator("Amount of Substance from Molality", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Molality) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Weight && rightUnit is Molality -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Amount of Substance from Molar Mass", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MolarMass) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Weight && rightUnit is MolarMass -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area from Area Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AreaDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is UKImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is USCustomaryAreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is AreaDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Area Density from Area", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Area) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialArea -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Area -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Density from Volume", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Volume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is UKImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is USCustomaryVolume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Volume -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Ionizing Radiation Absorbed Dose", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.IonizingRadiationAbsorbedDose) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Gram && rightUnit is Rad -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Gram && rightUnit is RadMultiple -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is IonizingRadiationAbsorbedDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is IonizingRadiationAbsorbedDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is IonizingRadiationAbsorbedDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is IonizingRadiationAbsorbedDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Ionizing Radiation Equivalent Dose", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.IonizingRadiationEquivalentDose) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Gram && rightUnit is RoentgenEquivalentMan -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Gram && rightUnit is RoentgenEquivalentManMultiple -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is IonizingRadiationEquivalentDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is IonizingRadiationEquivalentDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is IonizingRadiationEquivalentDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is IonizingRadiationEquivalentDose -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Energy from Specific Energy", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificEnergy) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is UKImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is USCustomarySpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is UKImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialSpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is USCustomarySpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is SpecificEnergy -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Force from Acceleration", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Acceleration) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Gram && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is MetricWeight && rightUnit is MetricAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Pound && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Ounce && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Grain && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UsTon && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialTon && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialAcceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Acceleration -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Heat Capacity from Specific Heat Capacity", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificHeatCapacity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is UKImperialSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is UKImperialSpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is USCustomarySpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is USCustomarySpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is SpecificHeatCapacity -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Length from Linear Mass Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.LinearMassDensity) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is UKImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is USCustomaryLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is UKImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is USCustomaryLinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is LinearMassDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Linear Mass Density from Length", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Length) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialLength -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Length -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Mass Flow Rate from Time", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Molar Mass from Amount of Substance", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.AmountOfSubstance) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is AmountOfSubstance -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Momentum from Speed", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Speed) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialSpeed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Speed -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Time from Mass Flow Rate", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.MassFlowRate) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is Weight && rightUnit is MassFlowRate -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Density", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Density) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is UKImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is USCustomaryDensity -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Density -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Volume from Specific Volume", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.SpecificVolume) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is UKImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialSpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is USCustomarySpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is SpecificVolume -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Yank from Jolt", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Jolt) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricWeight && rightUnit is MetricJolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialWeight && rightUnit is ImperialJolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialWeight && rightUnit is ImperialJolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryWeight && rightUnit is ImperialJolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Weight && rightUnit is Jolt -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/YankConverters.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/YankConverters.kt new file mode 100644 index 000000000..a82394698 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/converters/YankConverters.kt @@ -0,0 +1,74 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific.converters + +import com.splendo.kaluga.scientific.DefaultScientificValue +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.converter.yank.div +import com.splendo.kaluga.scientific.converter.yank.times +import com.splendo.kaluga.scientific.unit.ImperialJolt +import com.splendo.kaluga.scientific.unit.ImperialWeight +import com.splendo.kaluga.scientific.unit.ImperialYank +import com.splendo.kaluga.scientific.unit.Jolt +import com.splendo.kaluga.scientific.unit.MetricJolt +import com.splendo.kaluga.scientific.unit.MetricWeight +import com.splendo.kaluga.scientific.unit.MetricYank +import com.splendo.kaluga.scientific.unit.Time +import com.splendo.kaluga.scientific.unit.UKImperialWeight +import com.splendo.kaluga.scientific.unit.UKImperialYank +import com.splendo.kaluga.scientific.unit.USCustomaryWeight +import com.splendo.kaluga.scientific.unit.USCustomaryYank +import com.splendo.kaluga.scientific.unit.Weight +import com.splendo.kaluga.scientific.unit.Yank + +val PhysicalQuantity.Yank.converters get() = listOf>( + QuantityConverterWithOperator("Force from Time", QuantityConverter.WithOperator.Type.Multiplication, PhysicalQuantity.Time) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricYank && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialYank && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialYank && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryYank && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + leftUnit is Yank && rightUnit is Time -> DefaultScientificValue(leftValue, leftUnit) * DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Jolt from Weight", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Weight) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricYank && rightUnit is MetricWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialYank && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialYank && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialYank && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialYank && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialYank && rightUnit is UKImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryYank && rightUnit is ImperialWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryYank && rightUnit is USCustomaryWeight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Yank && rightUnit is Weight -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + }, + QuantityConverterWithOperator("Weight from Jolt", QuantityConverter.WithOperator.Type.Division, PhysicalQuantity.Jolt) { (leftValue, leftUnit), (rightValue, rightUnit) -> + when { + leftUnit is MetricYank && rightUnit is MetricJolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is ImperialYank && rightUnit is ImperialJolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is UKImperialYank && rightUnit is ImperialJolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is USCustomaryYank && rightUnit is ImperialJolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + leftUnit is Yank && rightUnit is Jolt -> DefaultScientificValue(leftValue, leftUnit) / DefaultScientificValue(rightValue, rightUnit) + else -> throw RuntimeException("Unexpected units: $leftUnit, $rightUnit") + } + } +) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/scientificUnitName.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/scientificUnitName.kt new file mode 100644 index 000000000..620262a4b --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/model/scientific/scientificUnitName.kt @@ -0,0 +1,65 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.model.scientific + +import com.splendo.kaluga.scientific.unit.* + +val ScientificUnit<*>.name: String get() = when (this) { + is Acceleration -> "${speed.name} per ${per.name}" + is Action -> "${energy.name}-${time.name}" + is AngularAcceleration -> "${angularVelocity.name} per ${per.name}" + is AngularVelocity -> "${angle.name} per ${per.name}" + is AreaDensity -> "${weight.name} per ${per.name}" + is Density -> "${weight.name} per ${per.name}" + is DynamicViscosity -> "${pressure.name}-${time.name}" + is ImperialMetricAndImperialEnergyWrapper -> "${metricAndImperialEnergy.name} (Imperial)" + is ImperialMetricAndImperialPowerWrapper -> "${metricAndImperialPower.name} (Imperial)" + is HeatCapacity -> "${energy.name} per ${per.name}" + is Jolt -> "${acceleration.name} per ${per.name}" + is KinematicViscosity -> "${area.name} per ${time.name}" + is LinearMassDensity -> "${weight.name} per ${per.name}" + is LuminousEnergy -> "${luminousFlux.name}-${time.name}" + is LuminousExposure -> "${illuminance.name}-${time.name}" + is MassFlowRate -> "${weight.name} per ${per.name}" + is Molality -> "${amountOfSubstance.name} per ${per.name}" + is MolarEnergy -> "${energy.name} per ${per.name}" + is Molarity -> "${amountOfSubstance.name} per ${per.name}" + is MolarMass -> "${weight.name} per ${per.name}" + is MolarVolume -> "${volume.name} per ${per.name}" + is Momentum -> "${mass.name}-${speed.name}" + is MetricMetricAndImperialEnergyWrapper -> "${metricAndImperialEnergy.name} (Metric)" + is MetricMetricAndImperialPowerWrapper -> "${metricAndImperialPower.name} (Metric)" + is SpecificEnergy -> "${energy.name} per ${per.name}" + is SpecificHeatCapacity -> "${heatCapacity.name} per ${perWeight.name}" + is SpecificVolume -> "${volume.name} per ${per.name}" + is Speed -> "${distance.name} per ${per.name}" + is SurfaceTension -> "${force.name} per ${per.name}" + is ThermalResistance -> "${temperature.name} per ${per.name}" + is USCustomaryImperialForceWrapper -> "${imperial.name} (US)" + is USCustomaryImperialPressureWrapper -> "${imperial.name} (US)" + is USCustomaryImperialVolumeWrapper -> "${imperial.name} (US)" + is USCustomaryImperialWeightWrapper -> "${imperial.name} (US)" + is UKImperialImperialForceWrapper -> "${imperial.name} (UK)" + is UKImperialPressureWrapper -> "${imperial.name} (UK)" + is UKImperialImperialVolumeWrapper -> "${imperial.name} (UK)" + is UKImperialImperialWeightWrapper -> "${imperial.name} (UK)" + is VolumetricFlow -> "${volume.name} per ${per.name}" + is VolumetricFlux -> "${volumetricFlow.name}-${per.name}" + is Yank -> "${force.name} per ${per.name}" + else -> this::class.simpleName ?: "" +} 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 new file mode 100644 index 000000000..26756332c --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/ButtonStyles.kt @@ -0,0 +1,251 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 +import com.splendo.kaluga.resources.defaultFont +import com.splendo.kaluga.resources.stylable.KalugaBackgroundStyle +import com.splendo.kaluga.resources.stylable.ButtonStateStyle +import com.splendo.kaluga.resources.stylable.KalugaButtonStyle +import com.splendo.kaluga.resources.stylable.GradientStyle + +object ButtonStyles { + + val default by lazy { + KalugaButtonStyle( + TextStyles.defaultTitle, + backgroundColor = DefaultColors.lightGray, + pressedBackgroundColor = DefaultColors.gray, + disabledBackgroundColor = DefaultColors.dimGray, + shape = KalugaBackgroundStyle.Shape.Rectangle(4.0f) + ) + } + + val textButton by lazy { + KalugaButtonStyle(TextStyles.redText) + } + val redButton by lazy { + KalugaButtonStyle( + TextStyles.whiteText, + backgroundColor = DefaultColors.red, + pressedBackgroundColor = DefaultColors.maroon, + disabledBackgroundColor = DefaultColors.lightGray + ) + } + val roundedButton by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle(DefaultColors.white, DefaultColors.deepSkyBlue, KalugaBackgroundStyle.Shape.Rectangle(10.0f, setOf(KalugaBackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, KalugaBackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT))), + pressedStyle = ButtonStateStyle(DefaultColors.azure, DefaultColors.lightSkyBlue, KalugaBackgroundStyle.Shape.Rectangle(5.0f, setOf(KalugaBackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, KalugaBackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT))), + disabledStyle = ButtonStateStyle(DefaultColors.black, DefaultColors.lightGray, KalugaBackgroundStyle.Shape.Rectangle(10.0f)) + ) + } + val ovalButton by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle(DefaultColors.white, DefaultColors.deepSkyBlue, KalugaBackgroundStyle.Shape.Oval), + pressedStyle = ButtonStateStyle(DefaultColors.azure, DefaultColors.lightSkyBlue, KalugaBackgroundStyle.Shape.Oval), + disabledStyle = ButtonStateStyle(DefaultColors.black, DefaultColors.lightGray, KalugaBackgroundStyle.Shape.Oval) + ) + } + val redButtonWithStroke by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle( + DefaultColors.white, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.red), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + pressedStyle = ButtonStateStyle( + DefaultColors.white, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.maroon), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + disabledStyle = ButtonStateStyle( + DefaultColors.black, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.lightGray), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ) + ) + } + val roundedButtonWithStroke by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle( + DefaultColors.white, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.deepSkyBlue), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), + KalugaBackgroundStyle.Shape.Rectangle(10.0f, setOf(KalugaBackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, KalugaBackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT)) + ) + ), + pressedStyle = ButtonStateStyle( + DefaultColors.azure, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.lightSkyBlue), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.azure), + KalugaBackgroundStyle.Shape.Rectangle(5.0f, setOf(KalugaBackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, KalugaBackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT)) + ) + ), + disabledStyle = ButtonStateStyle( + DefaultColors.black, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.lightGray), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), + KalugaBackgroundStyle.Shape.Rectangle(10.0f) + ) + ) + ) + } + val ovalButtonWithStroke by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle( + DefaultColors.white, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.deepSkyBlue), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), + KalugaBackgroundStyle.Shape.Oval + ) + ), + pressedStyle = ButtonStateStyle( + DefaultColors.azure, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.lightSkyBlue), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.azure), + KalugaBackgroundStyle.Shape.Oval + ) + ), + disabledStyle = ButtonStateStyle( + DefaultColors.black, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.lightGray), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), + KalugaBackgroundStyle.Shape.Oval + ) + ) + ) + } + + val linearGradientButton by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle( + DefaultColors.white, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), GradientStyle.Linear.Orientation.LEFT_RIGHT)), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + pressedStyle = ButtonStateStyle( + DefaultColors.azure, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), GradientStyle.Linear.Orientation.LEFT_RIGHT)), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + disabledStyle = ButtonStateStyle( + DefaultColors.black, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.lightGray, DefaultColors.gray), GradientStyle.Linear.Orientation.LEFT_RIGHT)), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ) + ) + } + + val radialGradientButton by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle( + DefaultColors.white, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), 50.0f, GradientStyle.CenterPoint(0.3f, 0.3f))), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + pressedStyle = ButtonStateStyle( + DefaultColors.azure, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), 25.0f, GradientStyle.CenterPoint(0.6f, 0.6f))), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + disabledStyle = ButtonStateStyle( + DefaultColors.black, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.lightGray, DefaultColors.gray), 50.0f)), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ) + ) + } + + val angularGradientButton by lazy { + KalugaButtonStyle( + defaultFont, + 12.0f, + defaultStyle = ButtonStateStyle( + DefaultColors.white, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), GradientStyle.CenterPoint(0.3f, 0.3f))), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + pressedStyle = ButtonStateStyle( + DefaultColors.azure, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), GradientStyle.CenterPoint(0.6f, 0.6f))), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ), + disabledStyle = ButtonStateStyle( + DefaultColors.black, + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.lightGray, DefaultColors.gray))), + KalugaBackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black), + KalugaBackgroundStyle.Shape.Rectangle() + ) + ) + ) + } +} diff --git a/example/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 new file mode 100644 index 000000000..2f36de94d --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/stylable/TextStyles.kt @@ -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. + + */ + +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.KalugaTextAlignment +import com.splendo.kaluga.resources.stylable.KalugaTextStyle + +object TextStyles { + + val defaultText by lazy { + KalugaTextStyle(defaultFont, DefaultColors.dimGray, 12.0f) + } + val defaultTitle by lazy { + KalugaTextStyle(defaultFont, DefaultColors.dimGray, 16.0f) + } + val defaultBoldText by lazy { + KalugaTextStyle(defaultBoldFont, DefaultColors.dimGray, 12.0f) + } + val defaultItalicText by lazy { + KalugaTextStyle(defaultItalicFont, DefaultColors.dimGray, 12.0f) + } + val defaultMonospaceText by lazy { + KalugaTextStyle(defaultMonospaceFont, DefaultColors.dimGray, 12.0f) + } + val whiteText by lazy { + KalugaTextStyle(defaultFont, DefaultColors.white, 12.0f) + } + val redText by lazy { + KalugaTextStyle(defaultFont, DefaultColors.red, 12.0f) + } + val oppositeText by lazy { + KalugaTextStyle(defaultFont, DefaultColors.dimGray, 12.0f, KalugaTextAlignment.END) + } +} 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 97% 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 index 122bf8e3f..0d04f4d26 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/ExampleViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/ExampleViewModel.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/example/shared/src/commonMain/kotlin/AlertViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt similarity index 73% rename from example/shared/src/commonMain/kotlin/AlertViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt index 8df6539d7..99dd62863 100644 --- a/example/shared/src/commonMain/kotlin/AlertViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/alert/AlertViewModel.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -16,7 +16,7 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.shared +package com.splendo.kaluga.example.shared.viewmodel.alert import com.splendo.kaluga.alerts.Alert import com.splendo.kaluga.alerts.BaseAlertPresenter @@ -24,13 +24,22 @@ 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(private val builder: BaseAlertPresenter.Builder) : BaseLifecycleViewModel(builder) { - fun showAlert() { + companion object { + private val dismissTime: Duration = 3.seconds + } + + 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 +55,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_input".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 +89,7 @@ class AlertViewModel(val builder: BaseAlertPresenter.Builder) : BaseLifecycleVie } } - fun showAlertWithList() { + val showAlertWithListButton = KalugaButton.Plain("alert_list".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 new file mode 100644 index 000000000..d99abd034 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsViewModel.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.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.toInitializedObservable +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.MutableStateFlow +import kotlinx.serialization.Serializable + +@Serializable +data class InputDetails( + val name: String, + val number: Int +) + +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) { + + private val _name = MutableStateFlow(initialDetail.name) + val name = _name.toInitializedObservable(coroutineScope) + private val _number = MutableStateFlow(initialDetail.number.toString()) + val number = _number.toInitializedObservable(coroutineScope) + + val inverseButton = KalugaButton.Plain("architecture_details_inverse".localized(), ButtonStyles.default) { + _name.value = _name.value.reversed() + _number.value = _number.value.reversed() + } + + val finishButton = KalugaButton.Plain("architecture_finish".localized(), ButtonStyles.default) { + navigator.navigate(ArchitectureDetailsNavigationAction.FinishWithDetails(InputDetails(_name.value, _number.value.toIntOrNull() ?: 0))) + } + + fun onBackPressed() { + navigator.navigate(ArchitectureDetailsNavigationAction.Close) + } +} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt similarity index 58% rename from example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.kt index fcf42858e..8a0e2755f 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureInputViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureViewModel.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. @@ -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.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 +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) @@ -53,21 +55,24 @@ class ArchitectureInputViewModel(navigator: Navigator 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 -> 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(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/viewmodel/beacons/BeaconsListBeaconViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListBeaconViewModel.kt similarity index 96% 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 index 5555b3d3c..de8408407 100644 --- 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 @@ -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/example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt similarity index 82% rename from example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.kt index 9a6557120..d59f4b767 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/beacons/BeaconsListViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/beacons/BeaconsListViewModel.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. @@ -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) @@ -58,10 +62,12 @@ class BeaconsListViewModel(private val service: Beacons) : BaseLifecycleViewMode } fun onScanPressed() { - if (_isScanning.value) { - service.stopMonitoring() - } else { - service.startMonitoring(coroutineScope) + coroutineScope.launch { + if (_isScanning.value) { + service.stopMonitoring() + } else { + service.startMonitoring() + } } } 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 98% 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 index fcc6ca36e..9e7ee26a8 100644 --- 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 @@ -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/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 97% 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 index ddbd0eac6..513be4486 100644 --- 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 @@ -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/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt similarity index 84% rename from example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt index d13d7383b..fb46dfd0c 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothDeviceDetailViewModel.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. @@ -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,22 +41,20 @@ 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 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 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/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt similarity index 86% rename from example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt index c702f6072..3962b7ca9 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListDeviceViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListDeviceViewModel.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. @@ -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 @@ -44,7 +43,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,23 +100,11 @@ 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 -> - if (result.isEmpty()) - next.toString() - else - "$result, $next" + if (result.isEmpty()) next.toString() else "$result, $next" } return "bluetooth_service_uuids".localized().format(uuidString) } @@ -125,10 +112,7 @@ class BluetoothListDeviceViewModel(private val identifier: Identifier, bluetooth 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" + if (result.isEmpty()) nextString else "$result\n$nextString" } return "bluetooth_service_data".localized().format(dataString) } diff --git a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt similarity index 81% rename from example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.kt index a4d51fa62..19d3e5455 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/bluetooth/BluetoothListViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/bluetooth/BluetoothListViewModel.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. @@ -17,23 +17,21 @@ 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(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/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 97% 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 index e1fa77b8e..ec7e7bb66 100644 --- 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 @@ -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/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetime/TimerViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetime/TimerViewModel.kt new file mode 100644 index 000000000..d515feca9 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/datetime/TimerViewModel.kt @@ -0,0 +1,47 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.datetime + +import com.splendo.kaluga.architecture.observable.toInitializedObservable +import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel +import com.splendo.kaluga.datetime.timer.RecurringTimer +import com.splendo.kaluga.datetime.timer.Timer +import com.splendo.kaluga.datetime.timer.elapsed +import com.splendo.kaluga.example.shared.stylable.ButtonStyles +import com.splendo.kaluga.resources.view.KalugaButton +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.minutes + +class TimerViewModel : BaseLifecycleViewModel() { + + private val timer = MutableStateFlow(RecurringTimer(1.minutes, coroutineScope = coroutineScope)) + + val elapsed = timer.flatMapLatest { timer -> timer.elapsed().map { "${it.inWholeSeconds} s" } }.toInitializedObservable("0 s", coroutineScope) + val button = timer.flatMapLatest { timer -> + timer.state.map { state -> + when (state) { + is Timer.State.NotRunning.Paused -> KalugaButton.Plain("Start", ButtonStyles.default) { coroutineScope.launch { timer.start() } } + is Timer.State.NotRunning.Finished -> KalugaButton.Plain("Reset", ButtonStyles.default) { this.timer.value = RecurringTimer(1.minutes, coroutineScope = coroutineScope) } + is Timer.State.Running -> KalugaButton.Plain("Pause", ButtonStyles.default) { coroutineScope.launch { timer.pause() } } + } + } + }.toInitializedObservable(KalugaButton.Plain("", ButtonStyles.default) {}, coroutineScope) +} 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 75% 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 index 0a72e95f4..8a54b9ce8 100644 --- 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 @@ -1,6 +1,6 @@ package com.splendo.kaluga.example.shared.viewmodel.datetimepicker -import com.splendo.kaluga.architecture.observable.toUninitializedObservable +import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import com.splendo.kaluga.base.text.DateFormatStyle import com.splendo.kaluga.base.text.KalugaDateFormatter @@ -8,12 +8,14 @@ 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 -class DateTimePickerViewModel(val dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder) : BaseLifecycleViewModel() { +class DateTimePickerViewModel(private val dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder) : BaseLifecycleViewModel(dateTimePickerPresenterBuilder) { companion object { private val formatter = KalugaDateFormatter.dateTimeFormat(DateFormatStyle.Long, DateFormatStyle.Long) @@ -25,9 +27,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 +46,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/viewmodel/featureList/FeatureListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/FeatureListViewModel.kt similarity index 89% 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 index 56f9a5af6..cb7b5e2d6 100644 --- 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 @@ -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. @@ -27,6 +27,7 @@ sealed class FeatureListNavigationAction : NavigationAction(null) { object Location : FeatureListNavigationAction() object Permissions : FeatureListNavigationAction() object Alerts : FeatureListNavigationAction() + object DateTime : FeatureListNavigationAction() object DateTimePicker : FeatureListNavigationAction() object LoadingIndicator : FeatureListNavigationAction() object Architecture : FeatureListNavigationAction() @@ -36,6 +37,7 @@ sealed class FeatureListNavigationAction : NavigationAction(null) { object System : FeatureListNavigationAction() object Beacons : FeatureListNavigationAction() object Resources : FeatureListNavigationAction() + object Scientific : FeatureListNavigationAction() object PlatformSpecific : FeatureListNavigationAction() } @@ -43,6 +45,7 @@ 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 DateTime : Feature("feature_date_time".localized()) object DateTimePicker : Feature("feature_date_time_picker".localized()) object Keyboard : Feature("feature_keyboard".localized()) object LoadingIndicator : Feature("feature_hud".localized()) @@ -52,6 +55,7 @@ sealed class Feature(val title: String) { object System : Feature("feature_system".localized()) object Beacons : Feature("feature_beacons".localized()) object Resource : Feature("feature_resources".localized()) + object Scientific : Feature("feature_scientific".localized()) object PlatformSpecific : Feature("feature_platform_specific".localized()) } @@ -62,6 +66,7 @@ class FeatureListViewModel(navigator: Navigator) : Feature.Alerts, Feature.Architecture, Feature.Bluetooth, + Feature.DateTime, Feature.DateTimePicker, Feature.Keyboard, Feature.Links, @@ -71,6 +76,7 @@ class FeatureListViewModel(navigator: Navigator) : Feature.System, Feature.Beacons, Feature.Resource, + Feature.Scientific, Feature.PlatformSpecific.takeIf { showPlatformSpecificFeatures } ) ) @@ -81,6 +87,7 @@ class FeatureListViewModel(navigator: Navigator) : is Feature.Alerts -> FeatureListNavigationAction.Alerts is Feature.Architecture -> FeatureListNavigationAction.Architecture is Feature.Bluetooth -> FeatureListNavigationAction.Bluetooth + is Feature.DateTime -> FeatureListNavigationAction.DateTime is Feature.DateTimePicker -> FeatureListNavigationAction.DateTimePicker is Feature.Keyboard -> FeatureListNavigationAction.Keyboard is Feature.Links -> FeatureListNavigationAction.Links @@ -90,6 +97,7 @@ class FeatureListViewModel(navigator: Navigator) : is Feature.System -> FeatureListNavigationAction.System is Feature.Beacons -> FeatureListNavigationAction.Beacons is Feature.Resource -> FeatureListNavigationAction.Resources + is Feature.Scientific -> FeatureListNavigationAction.Scientific is Feature.PlatformSpecific -> FeatureListNavigationAction.PlatformSpecific } ) 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 92% 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 index 4fea38b36..3852fd997 100644 --- 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 @@ -1,5 +1,5 @@ /* - Copyright 2021 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/example/shared/src/commonMain/kotlin/HudViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt similarity index 69% rename from example/shared/src/commonMain/kotlin/HudViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt index 729f65ddb..b52a1f12d 100644 --- a/example/shared/src/commonMain/kotlin/HudViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/hud/HudViewModel.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -16,20 +16,23 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.shared +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() { +class HudViewModel(private val builder: BaseHUD.Builder) : BaseLifecycleViewModel(builder) { - 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/info/InfoViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt new file mode 100644 index 000000000..cd45f8073 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/info/InfoViewModel.kt @@ -0,0 +1,71 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.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.review.ReviewManager +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable + +@Serializable +data class DialogSpec(val title: String, val message: String) +@Serializable +data class MailSpec(val to: List, val subject: String) + +sealed class InfoNavigation(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { + + 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( + reviewManagerBuilder: ReviewManager.Builder, + navigator: Navigator> +) : NavigatingViewModel>(navigator, reviewManagerBuilder) { + + 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("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() + } + null + } + }?.let { navigator.navigate(it) } + } +} 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 56% 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 index 7da1fb48d..9a4b2a010 100644 --- 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 @@ -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. @@ -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() { +class KeyboardViewModel(keyboardManagerBuilder: BaseKeyboardManager.Builder, private val editFieldFocusHandler: FH) : BaseLifecycleViewModel(keyboardManagerBuilder) { - private val keyboardManager: BaseKeyboardManager = keyboardManagerBuilder.create(coroutineScope) + 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/viewmodel/link/LinksViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt similarity index 60% rename from example/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt index 4ab42b44d..98ed0f9cc 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/link/LinksViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/link/LinksViewModel.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -18,18 +18,14 @@ 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.BaseAlertPresenter 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.navigation.SingleValueNavigationAction import com.splendo.kaluga.architecture.observable.observableOf import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel -import com.splendo.kaluga.links.LinksBuilder +import com.splendo.kaluga.links.LinksManager import com.splendo.kaluga.resources.localized import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -40,31 +36,25 @@ 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) { + linkManagerBuilder: LinksManager.Builder, + private val alertPresenterBuilder: BaseAlertPresenter.Builder, + navigator: Navigator> +) : NavigatingViewModel>(navigator, alertPresenterBuilder) { val browserButtonText = observableOf("browser_button_text".localized()) val linksInstructions = observableOf("links_instructions".localized()) - private val linksRepo = linkRepoBuilder.create() + private val linksRepo = linkManagerBuilder.create() fun showAlert(title: String, message: String, style: Alert.Action.Style) { coroutineScope.launch { val action = Alert.Action("Ok", style) - val alert = builder.buildAlert(this) { + val alert = alertPresenterBuilder.buildAlert(this) { setTitle(title) setMessage(message) addActions(action) @@ -77,15 +67,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/example/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt similarity index 72% rename from example/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.kt index 5483800e2..7d3ec401f 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/location/LocationViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/location/LocationViewModel.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. @@ -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/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt similarity index 80% rename from example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.kt index f9a6a3310..429fcacb5 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionViewModel.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. @@ -22,16 +22,26 @@ 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 +import com.splendo.kaluga.permissions.base.PermissionsBuilder 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(private val permissions: Permissions, private val permission: Permission) : BaseLifecycleViewModel() { +private val permissionsDispatcher = singleThreadDispatcher("PermissionsDispatcher") +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) val permissionStateMessage: UninitializedObservable = permissions[permission] .map { permissionState -> when (permissionState) { diff --git a/example/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 new file mode 100644 index 000000000..d42361c25 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/permissions/PermissionsListViewModel.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.shared.viewmodel.permissions + +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.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.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 +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +@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()); + + 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) + } +} + +expect val notificationOptions: NotificationOptions + +class PermissionsListNavigationAction(permissionView: PermissionView) : SingleValueNavigationAction(permissionView, NavigationBundleSpecType.SerializedType(PermissionView.serializer())) + +class PermissionsListViewModel(navigator: Navigator) : NavigatingViewModel(navigator), KoinComponent { + + val permissions = observableOf( + listOf( + PermissionView.Bluetooth, + PermissionView.Calendar, + PermissionView.Camera, + PermissionView.Contacts, + PermissionView.Location, + PermissionView.Microphone, + PermissionView.Notifications, + PermissionView.Storage + ) + ) + private val permissionsBuilder: PermissionsBuilder by inject() + + fun onPermissionPressed(permissionView: PermissionView) { + coroutineScope.launch { + permissionsBuilder.registerAllPermissionsNotRegistered( + settings = BasePermissionManager.Settings( + logger = RestrictedLogger(RestrictedLogLevel.None) + ) + ) + navigator.navigate(PermissionsListNavigationAction(permissionView)) + } + } +} 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 90% 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 index e178808a7..dfdc5f5d9 100644 --- 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 @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -17,22 +17,22 @@ package com.splendo.kaluga.example.shared.viewmodel.resources -import com.splendo.kaluga.alerts.AlertPresenter +import com.splendo.kaluga.alerts.BaseAlertPresenter 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.stylable.KalugaButtonStyle 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() { + private val alertPresenterBuilder: BaseAlertPresenter.Builder +) : BaseLifecycleViewModel(alertPresenterBuilder) { val buttons = observableOf( listOf( @@ -76,7 +76,7 @@ class ButtonViewModel( } private fun String.toButton( - style: ButtonStyle, + style: KalugaButtonStyle, isEnabled: Boolean = true ): List = listOf( KalugaButton.Plain(this, style, isEnabled) { 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 88% 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 index d14206a3c..4a3cac421 100644 --- 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 @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -18,10 +18,10 @@ 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.BaseAlertPresenter 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.BaseInitializedObservable import com.splendo.kaluga.architecture.observable.toInitializedObservable import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel import com.splendo.kaluga.example.shared.stylable.ButtonStyles @@ -47,7 +47,7 @@ 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.stylable.KalugaBackgroundStyle import com.splendo.kaluga.resources.view.KalugaButton import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -77,8 +77,8 @@ enum class SelectableBlendMode { } class ColorViewModel( - val alertPresenterBuilder: AlertPresenter.Builder -) : BaseLifecycleViewModel() { + private val alertPresenterBuilder: BaseAlertPresenter.Builder +) : BaseLifecycleViewModel(alertPresenterBuilder) { private val backdropColor = MutableStateFlow(DefaultColors.mediumPurple) private val sourceColor = MutableStateFlow(DefaultColors.darkCyan) @@ -121,15 +121,15 @@ class ColorViewModel( 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)) + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(color.lightenBy(it)) ) } }.toInitializedObservable(emptyList(), coroutineScope) private fun Flow.darkenList() = map { color -> steps.map { - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(color.darkenBy(it)) + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(color.darkenBy(it)) ) } }.toInitializedObservable(emptyList(), coroutineScope) @@ -184,9 +184,9 @@ class ColorViewModel( } } - private val Flow.backgroundStyleObservable: InitializedObservable get() = map { - BackgroundStyle( - BackgroundStyle.FillStyle.Solid(it) + private val Flow.backgroundStyleObservable: BaseInitializedObservable get() = map { + KalugaBackgroundStyle( + KalugaBackgroundStyle.FillStyle.Solid(it) ) - }.toInitializedObservable(BackgroundStyle(BackgroundStyle.FillStyle.Solid(DefaultColors.clear)), coroutineScope) + }.toInitializedObservable(KalugaBackgroundStyle(KalugaBackgroundStyle.FillStyle.Solid(DefaultColors.clear)), coroutineScope) } diff --git a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/BottomSheetActivity.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ImagesViewModel.kt similarity index 50% rename from example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/BottomSheetActivity.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ImagesViewModel.kt index 88018c94d..60147345b 100644 --- a/example/android/src/main/java/com/splendo/kaluga/example/platformspecific/compose/bottomSheet/BottomSheetActivity.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/resources/ImagesViewModel.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 Splendo Consulting B.V. The Netherlands + Copyright 2023 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,18 +15,24 @@ */ -package com.splendo.kaluga.example.platformspecific.compose.bottomSheet +package com.splendo.kaluga.example.shared.viewmodel.resources -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() - } +import com.splendo.kaluga.resources.DefaultColors +import com.splendo.kaluga.resources.asImage +import com.splendo.kaluga.resources.tinted + +class ImagesViewModel : BaseLifecycleViewModel() { + + val images = listOfNotNull( + "check".asImage(), + "star".asImage(), + "cancel".asImage() + ) + + val tintedImages = listOfNotNull( + "check".asImage()?.tinted(DefaultColors.green), + "star".asImage()?.tinted(DefaultColors.gold), + "cancel".asImage()?.tinted(DefaultColors.red) + ) } 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 95% 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 index 4cb28f4c6..b2773950c 100644 --- 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 @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -25,7 +25,7 @@ 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.stylable.KalugaTextAlignment import com.splendo.kaluga.resources.styled import com.splendo.kaluga.resources.view.KalugaLabel @@ -94,14 +94,14 @@ class LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider) loremIpsum.styled( styledStringBuilderProvider, TextStyles.defaultText, - { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent(10.0f)) } + { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent(10.0f, 15.0f)) } ), ), KalugaLabel.Styled( loremIpsum.styled( styledStringBuilderProvider, TextStyles.defaultText, - { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.Alignment(TextAlignment.END)) } + { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.Alignment(KalugaTextAlignment.END)) } ), ), KalugaLabel.Styled( @@ -111,7 +111,7 @@ class LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider) { 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(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.Alignment(KalugaTextAlignment.END)) }, { attributeSubstring(loremIpsumParagraph2, StringStyleAttribute.CharacterStyleAttribute.Kerning(0.08f)) } ), ) 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 89% 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 index 7162feb76..c5510fcee 100644 --- 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 @@ -10,12 +10,14 @@ sealed class ResourcesListNavigationAction : NavigationAction(null) { object Color : ResourcesListNavigationAction() object Button : ResourcesListNavigationAction() + object Image : ResourcesListNavigationAction() object Label : ResourcesListNavigationAction() } enum class Resource(private val titleKey: String) { COLOR("feature_resources_color"), BUTTON("feature_resources_button"), + IMAGE("feature_resources_image"), LABEL("feature_resources_label"); val title: String get() = titleKey.localized() @@ -30,6 +32,7 @@ class ResourcesListViewModel(navigator: Navigator when (resource) { Resource.COLOR -> ResourcesListNavigationAction.Color Resource.BUTTON -> ResourcesListNavigationAction.Button + Resource.IMAGE -> ResourcesListNavigationAction.Image Resource.LABEL -> ResourcesListNavigationAction.Label } ) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificConverterViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificConverterViewModel.kt new file mode 100644 index 000000000..ade553f36 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificConverterViewModel.kt @@ -0,0 +1,141 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +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.BaseInitializedObservable +import com.splendo.kaluga.architecture.observable.toInitializedObservable +import com.splendo.kaluga.architecture.observable.toInitializedSubject +import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.base.text.NumberFormatStyle +import com.splendo.kaluga.base.text.NumberFormatter +import com.splendo.kaluga.base.utils.toDecimal +import com.splendo.kaluga.example.shared.model.scientific.converters.QuantityConverter +import com.splendo.kaluga.example.shared.model.scientific.name +import com.splendo.kaluga.example.shared.model.scientific.quantityDetails +import com.splendo.kaluga.example.shared.stylable.ButtonStyles +import com.splendo.kaluga.resources.view.KalugaButton +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.unit.ScientificUnit +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map + +sealed class ScientificConverterNavigationAction(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { + object Close : ScientificConverterNavigationAction(Unit, NavigationBundleSpecType.UnitType) + sealed class SelectUnit(quantity: PhysicalQuantity) : ScientificConverterNavigationAction(quantity, NavigationBundleSpecType.SerializedType(PhysicalQuantity.serializer())) { + data class Left(val quantity: PhysicalQuantity) : SelectUnit(quantity) + data class Right(val quantity: PhysicalQuantity) : SelectUnit(quantity) + } +} + +class ScientificConverterViewModel internal constructor( + private val leftUnits: Set>, + private val converter: QuantityConverter<*, *>?, + navigator: Navigator> +) : NavigatingViewModel>(navigator) { + + companion object { + val numberFormatterDecimal = NumberFormatter(style = NumberFormatStyle.Decimal(maxIntegerDigits = 10U, minIntegerDigits = 1U, minFractionDigits = 0U, maxFractionDigits = 10U)) + val numberFormatterScientific = NumberFormatter(style = NumberFormatStyle.Scientific()) + } + + @kotlinx.serialization.Serializable + data class Arguments(val physicalQuantity: PhysicalQuantity, val converterIndex: Int) { + val id: String get() = "${physicalQuantity.name}_$converterIndex" + } + + private val currentLeftUnit = MutableStateFlow(leftUnits.firstOrNull()) + val currentLeftUnitButton: BaseInitializedObservable = currentLeftUnit.map { createLeftUnitButton(it) }.toInitializedObservable(createLeftUnitButton(null), coroutineScope) + val isRightUnitSelectable = converter is QuantityConverter.WithOperator<*, *, *> + private val currentRightUnit = MutableStateFlow((converter as? QuantityConverter.WithOperator<*, *, *>)?.rightQuantity?.quantityDetails?.units?.firstOrNull()) + val currentRightUnitButton: BaseInitializedObservable = currentRightUnit.map { createRightUnitButton(it) }.toInitializedObservable(createRightUnitButton(null), coroutineScope) + private val _leftValue = MutableStateFlow(numberFormatterDecimal.format(0.0)) + val leftValue = _leftValue.toInitializedSubject(coroutineScope) + private val _rightValue = MutableStateFlow(numberFormatterDecimal.format(0.0)) + val rightValue = _rightValue.toInitializedSubject(coroutineScope) + val calculateOperatorSymbol = (converter as? QuantityConverter.WithOperator<*, *, *>)?.type?.operatorSymbol.orEmpty() + private val _resultValue = MutableStateFlow("") + val resultValue = _resultValue.toInitializedObservable(coroutineScope) + + val calculateButton = KalugaButton.Plain("Calculate", ButtonStyles.default) { + val left = numberFormatterScientific.parse(_leftValue.value)?.toDecimal() + val leftUnit = currentLeftUnit.value + val right = numberFormatterScientific.parse(_rightValue.value)?.toDecimal() + val rightUnit = currentRightUnit.value + _resultValue.value = when (converter) { + is QuantityConverter.Single<*, *> -> { + if (left != null && leftUnit != null) { + converter.convert(left, leftUnit) + } else { + null + } + } + is QuantityConverter.WithOperator<*, *, *> -> { + if (left != null && leftUnit != null && right != null && rightUnit != null) { + converter.convert(left, leftUnit, right, rightUnit) + } else { + null + } + } + null -> { + null + } + }?.let { result -> + val formattedDecimal = numberFormatterDecimal.format(result.value) + val parsedDecimal = numberFormatterDecimal.parse(formattedDecimal) + val formatter = if (parsedDecimal?.toDouble() == result.value.toDouble()) { + numberFormatterDecimal + } else { + numberFormatterScientific + } + "${formatter.format(result.value)} ${result.unit.symbol}" + } ?: "Invalid Result" + } + + fun didSelectLeftUnit(unitIndex: Int) { + currentLeftUnit.value = leftUnits.toList().getOrNull(unitIndex) + } + + fun didSelectRightUnit(unitIndex: Int) { + currentRightUnit.value = (converter as? QuantityConverter.WithOperator<*, *, *>)?.rightQuantity?.quantityDetails?.units?.toList()?.getOrNull(unitIndex) + } + + fun onClosePressed() { + navigator.navigate(ScientificConverterNavigationAction.Close) + } + + private fun createLeftUnitButton(currentUnit: ScientificUnit<*>?) = KalugaButton.Plain(currentUnit?.symbol.orEmpty(), ButtonStyles.default) { + currentLeftUnit.value?.let { + navigator.navigate(ScientificConverterNavigationAction.SelectUnit.Left(it.quantity)) + } + } + + private fun createRightUnitButton(currentUnit: ScientificUnit<*>?) = KalugaButton.Plain(currentUnit?.symbol.orEmpty(), ButtonStyles.default) { + currentRightUnit.value?.let { + navigator.navigate(ScientificConverterNavigationAction.SelectUnit.Right(it.quantity)) + } + } +} + +fun ScientificConverterViewModel(arguments: ScientificConverterViewModel.Arguments, navigator: Navigator>) = arguments.physicalQuantity.quantityDetails?.let { details -> + details.converters.getOrNull(arguments.converterIndex)?.let { converter -> + ScientificConverterViewModel(details.units, converter, navigator) + } +} ?: ScientificConverterViewModel(emptySet(), null, navigator) diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificUnitSelectionViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificUnitSelectionViewModel.kt new file mode 100644 index 000000000..4b581e4de --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificUnitSelectionViewModel.kt @@ -0,0 +1,65 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +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.toInitializedObservable +import com.splendo.kaluga.architecture.observable.toInitializedSubject +import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.example.shared.model.scientific.name +import com.splendo.kaluga.example.shared.model.scientific.quantityDetails +import com.splendo.kaluga.example.shared.stylable.ButtonStyles +import com.splendo.kaluga.resources.view.KalugaButton +import com.splendo.kaluga.scientific.PhysicalQuantity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +sealed class ScientificUnitSelectionAction(value: T, type: NavigationBundleSpecType) : SingleValueNavigationAction(value, type) { + object Cancelled : ScientificUnitSelectionAction(Unit, NavigationBundleSpecType.UnitType) + data class DidSelect(val index: Int) : ScientificUnitSelectionAction(index, NavigationBundleSpecType.IntegerType) +} + +class ScientificUnitSelectionViewModel( + private val quantity: PhysicalQuantity, + navigator: Navigator> +) : NavigatingViewModel>(navigator) { + + private val allUnits = MutableStateFlow>(emptyList()) + private val _filter = MutableStateFlow("") + val filter = _filter.toInitializedSubject(coroutineScope) + + init { + coroutineScope.launch(Dispatchers.Default) { + allUnits.value = quantity.quantityDetails?.units?.map { it.name }.orEmpty() + } + } + + val currentUnits = combine(allUnits, _filter) { units, filterToApply -> + units.withIndex() + .filter { it.value.lowercase().contains(filterToApply.lowercase()) } + .map { KalugaButton.Plain(it.value, ButtonStyles.default) { navigator.navigate(ScientificUnitSelectionAction.DidSelect(it.index)) } } + }.toInitializedObservable(emptyList(), coroutineScope) + + fun onCancel() { + navigator.navigate(ScientificUnitSelectionAction.Cancelled) + } +} diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificViewModel.kt new file mode 100644 index 000000000..1bfd4f57c --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificViewModel.kt @@ -0,0 +1,156 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +import com.splendo.kaluga.alerts.Alert +import com.splendo.kaluga.alerts.BaseAlertPresenter +import com.splendo.kaluga.alerts.buildActionSheet +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.BaseInitializedObservable +import com.splendo.kaluga.architecture.observable.toInitializedObservable +import com.splendo.kaluga.architecture.observable.toInitializedSubject +import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.base.text.NumberFormatStyle +import com.splendo.kaluga.base.text.NumberFormatter +import com.splendo.kaluga.base.utils.toDecimal +import com.splendo.kaluga.example.shared.model.scientific.QuantityDetails +import com.splendo.kaluga.example.shared.model.scientific.allPhysicalQuantities +import com.splendo.kaluga.example.shared.model.scientific.quantityDetails +import com.splendo.kaluga.example.shared.model.scientific.name +import com.splendo.kaluga.example.shared.stylable.ButtonStyles +import com.splendo.kaluga.resources.view.KalugaButton +import com.splendo.kaluga.scientific.PhysicalQuantity +import com.splendo.kaluga.scientific.unit.ScientificUnit +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +sealed class ScientificNavigationAction(value: T, spec: NavigationBundleSpecType) : SingleValueNavigationAction(value, spec) { + sealed class SelectUnit(quantity: PhysicalQuantity) : ScientificNavigationAction(quantity, NavigationBundleSpecType.SerializedType(PhysicalQuantity.serializer())) { + data class Left(val quantity: PhysicalQuantity) : SelectUnit(quantity) + data class Right(val quantity: PhysicalQuantity) : SelectUnit(quantity) + } + data class Converter(val quantity: PhysicalQuantity, val index: Int) : ScientificNavigationAction( + ScientificConverterViewModel.Arguments(quantity, index), + NavigationBundleSpecType.SerializedType(ScientificConverterViewModel.Arguments.serializer()) + ) +} + +class ScientificViewModel( + private val alertPresenterBuilder: BaseAlertPresenter.Builder, + navigator: Navigator> +) : NavigatingViewModel>(navigator, alertPresenterBuilder) { + + data class Button(val name: String, val quantity: PhysicalQuantity, val index: Int, val navigator: Navigator) { + val id: String get() = "${quantity.name}_$index" + val button: KalugaButton.Plain get() = KalugaButton.Plain(name, ButtonStyles.default) { + navigator.navigate(ScientificNavigationAction.Converter(quantity, index)) + } + } + + companion object { + val numberFormatterDecimal = NumberFormatter(style = NumberFormatStyle.Decimal(maxIntegerDigits = 10U, minIntegerDigits = 1U, minFractionDigits = 0U, maxFractionDigits = 10U)) + val numberFormatterScientific = NumberFormatter(style = NumberFormatStyle.Scientific()) + } + + private val quantityDetails = allPhysicalQuantities.mapNotNull { it.quantityDetails } + private val currentQuantityDetails = MutableStateFlow(quantityDetails.firstOrNull()) + val quantityDetailsButton: BaseInitializedObservable = currentQuantityDetails.map { createQuantityButton(it) }.toInitializedObservable(createQuantityButton(null), coroutineScope) + private val units = currentQuantityDetails.map { it?.units ?: emptySet() }.stateIn(coroutineScope, SharingStarted.Eagerly, emptySet()) + private val currentLeftUnit = MutableStateFlow?>(null) + val currentLeftUnitButton: BaseInitializedObservable = currentLeftUnit.map { createLeftUnitButton(it) }.toInitializedObservable(createLeftUnitButton(null), coroutineScope) + private val currentRightUnit = MutableStateFlow?>(null) + val currentRightUnitButton: BaseInitializedObservable = currentRightUnit.map { createRightUnitButton(it) }.toInitializedObservable(createRightUnitButton(null), coroutineScope) + private val _leftValue = MutableStateFlow(numberFormatterDecimal.format(0.0)) + val leftValue = _leftValue.toInitializedSubject(coroutineScope) + private val _rightValue = MutableStateFlow(numberFormatterDecimal.format(0.0)) + val rightValue = _rightValue.toInitializedObservable(coroutineScope) + + val converters = currentQuantityDetails.map { details -> + details?.let { + details.converters.mapIndexed { index, converter -> + Button(converter.name, details.quantity, index, navigator) + } + }.orEmpty() + }.toInitializedObservable(emptyList(), coroutineScope) + + init { + coroutineScope.launch { + units.collect { + currentLeftUnit.value = it.firstOrNull() + currentRightUnit.value = it.firstOrNull() + } + } + } + + val calculateButton = KalugaButton.Plain("Calculate", ButtonStyles.default) { + val details = currentQuantityDetails.value + val left = numberFormatterScientific.parse(_leftValue.value)?.toDecimal() + val leftUnit = currentLeftUnit.value + val rightUnit = currentRightUnit.value + _rightValue.value = if (details != null && left != null && leftUnit != null && rightUnit != null) { + details.convert(left, leftUnit, rightUnit)?.let { result -> + val formattedDecimal = numberFormatterDecimal.format(result.value) + val parsedDecimal = numberFormatterDecimal.parse(formattedDecimal) + val formatter = if (parsedDecimal?.toDouble() == result.value.toDouble()) { + numberFormatterDecimal + } else { + numberFormatterScientific + } + formatter.format(result.value) + } + } else { + null + } ?: "Invalid Result" + } + + private fun createQuantityButton(currentQuantityDetails: QuantityDetails<*>?) = KalugaButton.Plain(currentQuantityDetails?.let { it.quantity::class.simpleName }.orEmpty(), ButtonStyles.default) { selectQuantity() } + + private fun selectQuantity() { + coroutineScope.launch { + alertPresenterBuilder.buildActionSheet(coroutineScope) { + setTitle("Select Quantity") + addActions(quantityDetails.map { Alert.Action(it.quantity.name) { currentQuantityDetails.value = it } }) + }.show() + } + } + + fun didSelectLeftUnit(unitIndex: Int) { + currentLeftUnit.value = currentQuantityDetails.value?.units?.withIndex()?.toList()?.getOrNull(unitIndex)?.value + } + + fun didSelectRightUnit(unitIndex: Int) { + currentRightUnit.value = currentQuantityDetails.value?.units?.withIndex()?.toList()?.getOrNull(unitIndex)?.value + } + + private fun createLeftUnitButton(currentUnit: ScientificUnit<*>?) = KalugaButton.Plain(currentUnit?.symbol.orEmpty(), ButtonStyles.default) { + currentQuantityDetails.value?.let { + navigator.navigate(ScientificNavigationAction.SelectUnit.Left(it.quantity)) + } + } + + private fun createRightUnitButton(currentUnit: ScientificUnit<*>?) = KalugaButton.Plain(currentUnit?.symbol.orEmpty(), ButtonStyles.default) { + currentQuantityDetails.value?.let { + navigator.navigate(ScientificNavigationAction.SelectUnit.Right(it.quantity)) + } + } +} diff --git a/example/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt similarity index 76% rename from example/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt index f52fec64f..1a6ae21ba 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/system/SystemViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/SystemViewModel.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -28,18 +28,15 @@ 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 = + val systemFeatures = observableOf( listOf( SystemFeatures.Network diff --git a/example/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt similarity index 81% rename from example/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt rename to example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt index e8d4fa6a0..f3ae571f9 100644 --- a/example/shared/src/commonMain/kotlin/viewmodel/system/network/NetworkViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/system/network/NetworkViewModel.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -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/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/ArchitectureDetailsViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureDetailsViewModel.kt deleted file mode 100644 index 09b3027c7..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/architecture/ArchitectureDetailsViewModel.kt +++ /dev/null @@ -1,57 +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. - - */ - -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/example/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt deleted file mode 100644 index a3205fdc4..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/info/InfoViewModel.kt +++ /dev/null @@ -1,115 +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. - - */ - -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/permissions/PermissionsListViewModel.kt b/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt deleted file mode 100644 index 5031166c5..000000000 --- a/example/shared/src/commonMain/kotlin/viewmodel/permissions/PermissionsListViewModel.kt +++ /dev/null @@ -1,66 +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. - - */ - -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/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/di/DependencyInjection.kt new file mode 100644 index 000000000..886a84ce3 --- /dev/null +++ b/example/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 = { +} 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..72bcbc3ec --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureDetailsNavigator.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.shared.viewmodel.architecture + +import com.splendo.kaluga.architecture.navigation.DefaultNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.UIKit.UIViewController + +fun ArchitectureDetailsNavigator( + onFinishWithDetails: (InputDetails) -> Unit, + onClose: () -> Unit +) = DefaultNavigator> { action -> + when (action) { + is ArchitectureDetailsNavigationAction.FinishWithDetails -> onFinishWithDetails(action.value) + is ArchitectureDetailsNavigationAction.Close -> onClose() + } +} + +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..e31333c02 --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/architecture/ArchitectureNavigator.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.shared.viewmodel.architecture + +import com.splendo.kaluga.architecture.navigation.DefaultNavigator +import com.splendo.kaluga.architecture.navigation.NavigationSpec +import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator +import platform.UIKit.UIViewController + +fun ArchitectureNavigator( + onDetails: (InputDetails) -> Unit, + onBottomSheet: () -> Unit +) = DefaultNavigator> { action -> + when (action) { + is ArchitectureNavigationAction.Details -> onDetails(action.value) + is ArchitectureNavigationAction.BottomSheet -> onBottomSheet() + } +} + +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() + } +} diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt index a8703de5e..f00fd3397 100644 --- a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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/example/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 new file mode 100644 index 000000000..924fa6f1b --- /dev/null +++ b/example/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/example/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 new file mode 100644 index 000000000..9672a7e6e --- /dev/null +++ b/example/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/example/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 new file mode 100644 index 000000000..621259f1e --- /dev/null +++ b/example/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/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificConverterNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificConverterNavigator.kt new file mode 100644 index 000000000..08739282d --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificConverterNavigator.kt @@ -0,0 +1,33 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +import com.splendo.kaluga.architecture.navigation.DefaultNavigator +import com.splendo.kaluga.scientific.PhysicalQuantity + +fun ScientificConverterNavigator( + onSelectLeftUnit: (PhysicalQuantity) -> Unit, + onSelectRightUnit: (PhysicalQuantity) -> Unit, + onClose: () -> Unit +) = DefaultNavigator> { action -> + when (action) { + is ScientificConverterNavigationAction.SelectUnit.Left -> onSelectLeftUnit(action.value) + is ScientificConverterNavigationAction.SelectUnit.Right -> onSelectRightUnit(action.value) + is ScientificConverterNavigationAction.Close -> onClose() + } +} diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificNavigator.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificNavigator.kt new file mode 100644 index 000000000..e150c924a --- /dev/null +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificNavigator.kt @@ -0,0 +1,33 @@ +/* + Copyright 2023 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.scientific + +import com.splendo.kaluga.architecture.navigation.DefaultNavigator +import com.splendo.kaluga.scientific.PhysicalQuantity + +fun ScientificNavigator( + onSelectLeftUnit: (PhysicalQuantity) -> Unit, + onSelectRightUnit: (PhysicalQuantity) -> Unit, + onConverter: (ScientificConverterViewModel.Arguments) -> Unit +) = DefaultNavigator> { action -> + when (action) { + is ScientificNavigationAction.SelectUnit.Left -> onSelectLeftUnit(action.value) + is ScientificNavigationAction.SelectUnit.Right -> onSelectRightUnit(action.value) + is ScientificNavigationAction.Converter -> onConverter(action.value) + } +} diff --git a/links/src/commonMain/kotlin/com/splendo/kaluga/links/manager/LinksManager.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificUnitSelectionNavigator.kt similarity index 52% rename from links/src/commonMain/kotlin/com/splendo/kaluga/links/manager/LinksManager.kt rename to example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificUnitSelectionNavigator.kt index ddfcb793e..b87822dd1 100644 --- a/links/src/commonMain/kotlin/com/splendo/kaluga/links/manager/LinksManager.kt +++ b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/scientific/ScientificUnitSelectionNavigator.kt @@ -1,5 +1,5 @@ /* - Copyright 2020 Splendo Consulting B.V. The Netherlands + Copyright 2023 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,16 +15,16 @@ */ -package com.splendo.kaluga.links.manager +package com.splendo.kaluga.example.shared.viewmodel.scientific -import kotlinx.serialization.KSerializer +import com.splendo.kaluga.architecture.navigation.DefaultNavigator -interface LinksManager { - - interface Builder { - fun create(): LinksManager +fun ScientificUnitSelectionNavigator( + onDidSelect: (Int) -> Unit, + onCancelled: () -> Unit +) = DefaultNavigator> { action -> + when (action) { + is ScientificUnitSelectionAction.DidSelect -> onDidSelect(action.value) + is ScientificUnitSelectionAction.Cancelled -> onCancelled() } - - fun handleIncomingLink(url: String, serializer: KSerializer): T? - fun validateLink(url: String): String? } 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..87cb78879 --- /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(uiType: UIType) { + navigator.navigate( + when (uiType) { + is UIType.SwiftUI -> SwiftUIOrUIKitNavigationAction.SwiftUI + is UIType.UIKit -> SwiftUIOrUIKitNavigationAction.UIKit + } + ) + } +} diff --git a/example/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 new file mode 100644 index 000000000..2fc8f1287 --- /dev/null +++ b/example/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/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt index a8703de5e..f00fd3397 100644 --- a/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ b/example/shared/src/jsMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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/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 new file mode 100644 index 000000000..5c0a64d55 --- /dev/null +++ b/example/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() diff --git a/example/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 new file mode 100644 index 000000000..2fc8f1287 --- /dev/null +++ b/example/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 = { } diff --git a/example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt b/example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt index a8703de5e..f00fd3397 100644 --- a/example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt +++ b/example/shared/src/jvmMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/featureList/PlatformSpecific.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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/example/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 new file mode 100644 index 000000000..5c0a64d55 --- /dev/null +++ b/example/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() 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/gradle.properties b/gradle.properties index 11811e7e1..b99bacbfa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,20 +4,23 @@ 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" +kaluga.androidGradlePluginVersion=7.4.0 +kaluga.kotlinVersion=1.8.0 +kaluga.googleServicesGradlePluginVersion=4.3.14 +kaluga.ktLintGradlePluginVersion=11.0.0 +kaluga.atomicFuGradlePluginVersion=0.19.0 +org.gradle.jvmargs=-Xmx8G -Dkotlin.daemon.jvm.options\="-Xmx8G" #MPP kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.native.enableDependencyPropagation=false kotlin.mpp.enableCInteropCommonization=true +kotlin.mpp.androidSourceSetLayoutVersion=2 ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## # # below is not used by example and does not need to be kept in sync # kaluga.binaryCompatibilityValidatorVersion=0.11.0 +kaluga.dokkaVersion=1.7.20 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_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/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/componentskt.gradle.kts b/gradle/componentskt.gradle.kts deleted file mode 100644 index 582a803ea..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 b/gradle/ext.gradle deleted file mode 100644 index aa62adb6b..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.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' - napier_version = '2.4.0' - android_ble_scanner_version = '1.6.0' - library_version_base = '0.5.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.1.1" - androidx_compose_version = "1.2.1" - androidx_activity_compose_version = "1.5.1" - androidx_navigation_compose_version = "2.5.1" - - 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/gitBranch.gradle.kts b/gradle/gitBranch.gradle.kts deleted file mode 100644 index 644fe3298..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 GITHUB_GIT_BRANCH = System.getenv("GITHUB_REF_NAME") // could also be a tag name -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 github's branch detection -// - else: try to get it via the `git` CLI. -val branch = (kaluga_branch ?: GITHUB_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: GITHUB_GIT_BRANCH env: $GITHUB_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/jacoco.gradle b/gradle/jacoco.gradle index 7ef66d238..821c22072 100644 --- a/gradle/jacoco.gradle +++ b/gradle/jacoco.gradle @@ -91,8 +91,9 @@ project.afterEvaluate { coverageFilePaths.add("$connectedTestCoverageDirectoryPath/$it") } - if (!coverageFilePaths.isEmpty()) + if (!coverageFilePaths.isEmpty()) { executionData.setFrom(files(coverageFilePaths)) + } reports { xml.enabled = true diff --git a/gradle/newModule.gradle.kts b/gradle/newModule.gradle.kts index 1913bcc59..cc19307b8 100644 --- a/gradle/newModule.gradle.kts +++ b/gradle/newModule.gradle.kts @@ -1,5 +1,5 @@ /* - Copyright 2021 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/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/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/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee..7f0baca3f 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 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/hud/README.md b/hud/README.md index 18abfac41..f6ea0f155 100644 --- a/hud/README.md +++ b/hud/README.md @@ -55,15 +55,24 @@ The `HUD` has methods to show and dismiss a loading indicator view: The `HUD.Builder` class can be used to build HUDs. +- `build(coroutineScope: CoroutineScope, initialize: HudConfig.Builder.() -> Unit): BaseHUD` — builder to create `BaseHUD`, thread-safe +- +## Platform Specific Building +The `HUD.Builder` object should be created from the platform side. + ### Android -On Android the builder is a `LifecycleSubscribable` (see Architecture) that needs a `LifecycleSubscribable.LifecycleManager` object to provide the current context in which to display the HUD. -For `BaseLifecycleViewModel`, the builder should be made **publicly** visible and bound to a `KalugaViewModelLifecycleObserver`. +On Android the builder is an `ActivityLifecycleSubscribable` (see Architecture) that needs an `ActivityLifecycleSubscribable.LifecycleManager` object to provide the current context in which to display the HUD. +For `BaseLifecycleViewModel`, the builder should be provided to `BaseLifecycleViewModel.activeLifecycleSubscribables` (using the constructor or `BaseLifecycleViewModel.addLifecycleSubscribables`) and bound to a `KalugaViewModelLifecycleObserver` or `ViewModelComposable`. ```kotlin class HudViewModel: BaseLifecycleViewModel() { val builder = HUD.Builder() + init { + addLifecycleSubscribables(builder) + } + fun present() { coroutineScope.launch { viewModel.builder.build(this) { @@ -91,7 +100,7 @@ class MyActivity: KalugaViewModelActivity() { } ``` -For other usages, make sure to call `LifecycleSubscriber.subscribe` and `LifecycleSubscriber.unsubscribe` to manage the lifecycle manually. +For other usages, make sure to call `ActivityLifecycleSubscribable.subscribe` and `ActivityLifecycleSubscribable.unsubscribe` to manage the lifecycle manually. ```kotlin // Android specific @@ -104,8 +113,6 @@ MainScope().launch { } ``` -You can use the `AppCompatActivity.hudBuilder` convenience method to get a builder that is valid during the lifespan of the Activity it belongs to. - Define your custom colors inside `colors.xml` if using `.CUSTOM` style: ```xml @@ -126,6 +133,9 @@ On iOS this builder should be instantiated with `UIViewController`: let builder = HUD.Builder(viewController) ``` +Since a `UIViewController` is required, for SwiftUI the `View` displaying the HUD should have a `UIViewControllerRepresentable` wrapping the `UIViewController` associated with the `HUD.Builder` attached. +The [Kaluga SwiftUI scripts](https://github.com/splendo/kaluga-swiftui) provide a `ContainerView` that offers this functionality out of the box (if the `includeHud` setting is set to `true`) + Define your Color Sets in project's assets if using `.CUSTOM` style: - `li_colorBackground` for surface color diff --git a/hud/api/androidLib/hud.api b/hud/api/androidLib/hud.api index ef8ff2005..705ab9c2d 100644 --- a/hud/api/androidLib/hud.api +++ b/hud/api/androidLib/hud.api @@ -1,7 +1,3 @@ -public final class com/splendo/kaluga/hud/AndroidHUD { - public static final fun hudBuilder (Landroidx/appcompat/app/AppCompatActivity;)Lcom/splendo/kaluga/hud/HUD$Builder; -} - public abstract class com/splendo/kaluga/hud/BaseHUD : kotlinx/coroutines/CoroutineScope { public fun (Lkotlinx/coroutines/CoroutineScope;)V public abstract fun dismiss (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -13,11 +9,9 @@ public abstract class com/splendo/kaluga/hud/BaseHUD : kotlinx/coroutines/Corout public static synthetic fun present$default (Lcom/splendo/kaluga/hud/BaseHUD;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } -public abstract class com/splendo/kaluga/hud/BaseHUD$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { +public abstract class com/splendo/kaluga/hud/BaseHUD$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { 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 { @@ -28,14 +22,14 @@ public final class com/splendo/kaluga/hud/HUD : com/splendo/kaluga/hud/BaseHUD { public fun present (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final class com/splendo/kaluga/hud/HUD$Builder : com/splendo/kaluga/hud/BaseHUD$Builder, com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { +public final class com/splendo/kaluga/hud/HUD$Builder : com/splendo/kaluga/hud/BaseHUD$Builder, com/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable { 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 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/hud/HUD; - public fun getManager ()Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager; - public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager;)V + public fun getManager ()Lcom/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable$LifecycleManager; + public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable$LifecycleManager;)V public fun unsubscribe ()V } @@ -44,6 +38,8 @@ public final class com/splendo/kaluga/hud/HUDKt { public static synthetic fun build$default (Lcom/splendo/kaluga/hud/BaseHUD$Builder;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/splendo/kaluga/hud/BaseHUD; public static final fun dismissAfter (Lcom/splendo/kaluga/hud/BaseHUD;JZ)Lcom/splendo/kaluga/hud/BaseHUD; public static synthetic fun dismissAfter$default (Lcom/splendo/kaluga/hud/BaseHUD;JZILjava/lang/Object;)Lcom/splendo/kaluga/hud/BaseHUD; + public static final fun dismissAfter-8Mi8wO0 (Lcom/splendo/kaluga/hud/BaseHUD;JZ)Lcom/splendo/kaluga/hud/BaseHUD; + public static synthetic fun dismissAfter-8Mi8wO0$default (Lcom/splendo/kaluga/hud/BaseHUD;JZILjava/lang/Object;)Lcom/splendo/kaluga/hud/BaseHUD; public static final fun getStyle (Lcom/splendo/kaluga/hud/BaseHUD;)Lcom/splendo/kaluga/hud/HUDStyle; public static final fun getTitle (Lcom/splendo/kaluga/hud/BaseHUD;)Ljava/lang/String; public static final fun presentDuring (Lcom/splendo/kaluga/hud/BaseHUD;ZLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -72,3 +68,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..e87d4e8c6 100644 --- a/hud/api/jvm/hud.api +++ b/hud/api/jvm/hud.api @@ -9,11 +9,9 @@ public abstract class com/splendo/kaluga/hud/BaseHUD : kotlinx/coroutines/Corout public static synthetic fun present$default (Lcom/splendo/kaluga/hud/BaseHUD;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } -public abstract class com/splendo/kaluga/hud/BaseHUD$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { +public abstract class com/splendo/kaluga/hud/BaseHUD$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { 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 { @@ -35,6 +33,8 @@ public final class com/splendo/kaluga/hud/HUDKt { public static synthetic fun build$default (Lcom/splendo/kaluga/hud/BaseHUD$Builder;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/splendo/kaluga/hud/BaseHUD; public static final fun dismissAfter (Lcom/splendo/kaluga/hud/BaseHUD;JZ)Lcom/splendo/kaluga/hud/BaseHUD; public static synthetic fun dismissAfter$default (Lcom/splendo/kaluga/hud/BaseHUD;JZILjava/lang/Object;)Lcom/splendo/kaluga/hud/BaseHUD; + public static final fun dismissAfter-8Mi8wO0 (Lcom/splendo/kaluga/hud/BaseHUD;JZ)Lcom/splendo/kaluga/hud/BaseHUD; + public static synthetic fun dismissAfter-8Mi8wO0$default (Lcom/splendo/kaluga/hud/BaseHUD;JZILjava/lang/Object;)Lcom/splendo/kaluga/hud/BaseHUD; public static final fun getStyle (Lcom/splendo/kaluga/hud/BaseHUD;)Lcom/splendo/kaluga/hud/HUDStyle; public static final fun getTitle (Lcom/splendo/kaluga/hud/BaseHUD;)Ljava/lang/String; public static final fun presentDuring (Lcom/splendo/kaluga/hud/BaseHUD;ZLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -63,3 +63,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/build.gradle.kts b/hud/build.gradle.kts index d8114e8a8..1d8d41b39 100644 --- a/hud/build.gradle.kts +++ b/hud/build.gradle.kts @@ -3,28 +3,21 @@ plugins { id("jacoco") id("convention.publication") id("com.android.library") + id("org.jetbrains.dokka") 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"]!! +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"]}") + implementationDependency(Dependencies.AndroidX.Fragment) + debugImplementationDependency(Dependencies.AndroidX.FragmentKtx) } kotlin { sourceSets { getByName("commonMain") { dependencies { - val ext = (gradle as ExtensionAware).extra implementation(project(":architecture", "")) implementation(project(":base", "")) } @@ -32,7 +25,7 @@ kotlin { getByName("commonTest") { dependencies { - api(project(":test-utils-base", "")) + api(project(":test-utils-hud", "")) } } } diff --git a/hud/src/androidLibAndroidTest/AndroidManifest.xml b/hud/src/androidLibInstrumentedTest/AndroidManifest.xml similarity index 100% rename from hud/src/androidLibAndroidTest/AndroidManifest.xml rename to hud/src/androidLibInstrumentedTest/AndroidManifest.xml diff --git a/hud/src/androidLibAndroidTest/kotlin/AndroidHUDTests.kt b/hud/src/androidLibInstrumentedTest/kotlin/AndroidHUDTests.kt similarity index 84% rename from hud/src/androidLibAndroidTest/kotlin/AndroidHUDTests.kt rename to hud/src/androidLibInstrumentedTest/kotlin/AndroidHUDTests.kt index 656bc4b11..02449b4be 100644 --- a/hud/src/androidLibAndroidTest/kotlin/AndroidHUDTests.kt +++ b/hud/src/androidLibInstrumentedTest/kotlin/AndroidHUDTests.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -31,9 +31,9 @@ import com.splendo.kaluga.hud.AndroidHUDTests.AndroidHUDTestContext import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.junit.Rule import kotlin.test.BeforeTest @@ -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 = 30.seconds.inWholeMilliseconds fun UiDevice.assertTextAppears(text: String) { waitForIdle() @@ -88,7 +89,7 @@ class AndroidHUDTests : HUDTests() { @Test fun indicatorShow() = testOnUIThread(cancelScopeAfterTest = true) { - val indicator = builder.build(MainScope()) { + val indicator = builder.build(this) { setTitle(LOADING) } launch { @@ -148,22 +149,28 @@ class AndroidHUDTests : HUDTests() { val loading2 = EmptyCompletableDeferred() val processing = EmptyCompletableDeferred() - val indicatorLoading = builder.build(MainScope()) { - setTitle(LOADING) - } + val deferredIndicatorLoading = CompletableDeferred() val indicatorProcessing = CompletableDeferred() - launch { + val job = launch(Dispatchers.Main.immediate) { + val indicatorLoading = builder.build(this) { + setTitle(LOADING) + } + deferredIndicatorLoading.complete(indicatorLoading) indicatorLoading.presentDuring { presenting.complete() loading1.await() - val processingDialog = builder.build(MainScope()) { - setTitle(PROCESSING) - } - indicatorProcessing.complete(processingDialog) - processingDialog.presentDuring { - processing.await() + val test = launch { + val processingDialog = builder.build(this) { + setTitle(PROCESSING) + } + indicatorProcessing.complete(processingDialog) + processingDialog.presentDuring { + processing.await() + } } + loading2.await() + test.cancel() } } @@ -172,7 +179,7 @@ class AndroidHUDTests : HUDTests() { withContext(Dispatchers.Default) { device.assertTextAppears(LOADING) - assertTrue(indicatorLoading.isVisible) + assertTrue(deferredIndicatorLoading.await().isVisible) loading1.complete() // check the Processing dialog is popped on top @@ -186,12 +193,13 @@ class AndroidHUDTests : HUDTests() { device.assertTextAppears(LOADING) loading2.complete() } + job.cancel() } @Test @Ignore("Rotating in test framework is unstable") fun rotateActivity() = testOnUIThread { - val indicator = builder.build(MainScope()) { + val indicator = builder.build(this) { setTitle(LOADING) } launch { @@ -225,9 +233,10 @@ class AndroidHUDTests : HUDTests() { } @Test - fun testBuilderFromActivity() { - MainScope().launch { activity.showHUD() } + fun testBuilderFromActivity() = runBlocking { + val job = launch(Dispatchers.Main.immediate) { activity.showHUD() } device.assertTextAppears("Activity") + job.cancel() } override val createTestContext: suspend (scope: CoroutineScope) -> AndroidHUDTestContext = { AndroidHUDTestContext(it) } diff --git a/hud/src/androidLibAndroidTest/kotlin/HudViewModel.kt b/hud/src/androidLibInstrumentedTest/kotlin/HudViewModel.kt similarity index 86% rename from hud/src/androidLibAndroidTest/kotlin/HudViewModel.kt rename to hud/src/androidLibInstrumentedTest/kotlin/HudViewModel.kt index e7811b742..48d3030ea 100644 --- a/hud/src/androidLibAndroidTest/kotlin/HudViewModel.kt +++ b/hud/src/androidLibInstrumentedTest/kotlin/HudViewModel.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. @@ -22,4 +22,8 @@ import com.splendo.kaluga.architecture.viewmodel.BaseLifecycleViewModel open class HudViewModel : BaseLifecycleViewModel() { val builder = HUD.Builder() + + init { + addLifecycleSubscribables(builder) + } } diff --git a/hud/src/androidLibAndroidTest/kotlin/TestActivity.kt b/hud/src/androidLibInstrumentedTest/kotlin/TestActivity.kt similarity index 90% rename from hud/src/androidLibAndroidTest/kotlin/TestActivity.kt rename to hud/src/androidLibInstrumentedTest/kotlin/TestActivity.kt index 5ebc49b54..49bc3df03 100644 --- a/hud/src/androidLibAndroidTest/kotlin/TestActivity.kt +++ b/hud/src/androidLibInstrumentedTest/kotlin/TestActivity.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -20,6 +20,7 @@ package com.splendo.kaluga.hud import androidx.activity.viewModels import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity +import com.splendo.kaluga.test.hud.hudBuilder class TestActivity : KalugaViewModelActivity() { diff --git a/hud/src/androidLibMain/kotlin/HUD.kt b/hud/src/androidLibMain/kotlin/HUD.kt index f52108dcd..720bdda41 100644 --- a/hud/src/androidLibMain/kotlin/HUD.kt +++ b/hud/src/androidLibMain/kotlin/HUD.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -33,13 +33,10 @@ import android.widget.TextView import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.LayoutRes -import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver -import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable -import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache -import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver +import com.splendo.kaluga.architecture.lifecycle.ActivityLifecycleSubscribable import com.splendo.kaluga.base.mainHandler import com.splendo.kaluga.base.utils.byOrdinalOrDefault import kotlinx.coroutines.CoroutineScope @@ -49,10 +46,23 @@ import kotlinx.coroutines.launch import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +/** + * Default [BaseHUD] implementation. + */ actual class HUD private constructor(@LayoutRes viewResId: Int, override val hudConfig: HudConfig, lifecycleManagerObserver: LifecycleManagerObserver, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) { - actual class Builder(private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()) : BaseHUD.Builder(), LifecycleSubscribable by lifecycleManagerObserver { - + /** + * Builder class for creating a [HUD] + * @param lifecycleManagerObserver the [LifecycleManagerObserver] to observe lifecycle changes. + */ + actual class Builder(private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()) : BaseHUD.Builder(), ActivityLifecycleSubscribable by lifecycleManagerObserver { + + /** + * Creates a [HUD] based on [hudConfig]. + * @param hudConfig The [HudConfig] to apply to the [HUD]. + * @param coroutineScope The [CoroutineScope] managing the lifecycle of the HUD. + * @return the created [HUD] + */ actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD( R.layout.loading_indicator_view, hudConfig, @@ -184,16 +194,3 @@ actual class HUD private constructor(@LayoutRes viewResId: Int, override val hud } } } - -/** - * @return A [HUD.Builder] which can be used to show an HUD while this Activity is active. - * Will be created if need but only one instance will exist. - * - * Warning: Do not attempt to use this builder outside of the lifespan of the Activity. - * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.LifecycleViewModel], - * which can automatically track which Activity is active for it. - * - */ -fun AppCompatActivity.hudBuilder(): HUD.Builder = getOrPutAndRemoveOnDestroyFromCache { - HUD.Builder(lifecycleManagerObserver()) -} diff --git a/hud/src/commonMain/kotlin/HUD.kt b/hud/src/commonMain/kotlin/HUD.kt index c8910659c..b1f4cc944 100644 --- a/hud/src/commonMain/kotlin/HUD.kt +++ b/hud/src/commonMain/kotlin/HUD.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -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 com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds /** * Style of the Loading Indicator @@ -38,33 +38,14 @@ enum class HUDStyle { /** * Class showing a loading indicator HUD. + * @param coroutineScope The [CoroutineScope] managing the HUD lifecycle */ abstract class BaseHUD(coroutineScope: CoroutineScope) : CoroutineScope by coroutineScope { /** * Builder class for creating a [BaseHUD] */ - abstract class Builder : LifecycleSubscribableMarker { - - internal val lock = Lock() - - /** 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) - } + abstract class Builder : LifecycleSubscribable { /** */ /** @@ -72,11 +53,14 @@ abstract class BaseHUD(coroutineScope: CoroutineScope) : CoroutineScope by corou * * @param hudConfig The [HudConfig] used for configuring the HUD style. * @param coroutineScope The [CoroutineScope] managing the HUD lifecycle. - * @return The [BaseHUD] to diplay. + * @return The [BaseHUD] to display. */ abstract fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope): BaseHUD } + /** + * The [HudConfig] of the HUD being presented. + */ abstract val hudConfig: HudConfig /** @@ -88,6 +72,7 @@ abstract class BaseHUD(coroutineScope: CoroutineScope) : CoroutineScope by corou * Presents as indicator * * @param animated Pass `true` to animate the presentation + * @return The [BaseHUD] being presented. */ abstract suspend fun present(animated: Boolean = true): BaseHUD @@ -108,27 +93,49 @@ expect class HUD : BaseHUD { * Builder class for creating a [HUD] */ class Builder : BaseHUD.Builder { + + /** + * Creates a [HUD] based on [hudConfig]. + * @param hudConfig The [HudConfig] to apply to the [HUD]. + * @param coroutineScope The [CoroutineScope] managing the lifecycle of the HUD. + * @return the created [HUD] + */ override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope): HUD } } +/** + * The title of the HUD. + */ val BaseHUD.title: String? get() = hudConfig.title + +/** + * The [HUDStyle] of the HUD. + */ val BaseHUD.style: HUDStyle get() = hudConfig.style /** - * Dismisses the indicator after [timeMillis] milliseconds - * @param timeMillis The number of milliseconds to wait + * Dismisses the indicator after [duration] + * @param duration The [Duration] to wait + * @param animated Pass `true` to animate the transition */ -fun BaseHUD.dismissAfter(timeMillis: Long, animated: Boolean = true): BaseHUD = apply { +fun BaseHUD.dismissAfter(duration: Duration, animated: Boolean = true): BaseHUD = apply { launch(Dispatchers.Main) { - delay(timeMillis) + delay(duration) dismiss(animated) } } +/** + * Dismisses the indicator after [timeMillis] milliseconds + * @param timeMillis The number of milliseconds to wait + * @param animated Pass `true` to animate the transition + */ +fun BaseHUD.dismissAfter(timeMillis: Long, animated: Boolean = true): BaseHUD = dismissAfter(timeMillis.milliseconds, animated) /** * Presents and keep presenting the indicator during block execution, * hides view after block finished. + * @param animated Pass `true` to animate the transition * @param block The block to execute with hud visible */ suspend fun BaseHUD.presentDuring(animated: Boolean = true, block: suspend BaseHUD.() -> T): T { @@ -142,8 +149,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] */ -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..2b2cfb0e3 100644 --- a/hud/src/commonMain/kotlin/HudConfig.kt +++ b/hud/src/commonMain/kotlin/HudConfig.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -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/hud/src/commonTest/kotlin/HUDTests.kt b/hud/src/commonTest/kotlin/HUDTests.kt index 39642edad..a6969abc0 100644 --- a/hud/src/commonTest/kotlin/HUDTests.kt +++ b/hud/src/commonTest/kotlin/HUDTests.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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/hud/src/iosMain/kotlin/HUD.kt b/hud/src/iosMain/kotlin/HUD.kt index 2b11db3ef..996f64f55 100644 --- a/hud/src/iosMain/kotlin/HUD.kt +++ b/hud/src/iosMain/kotlin/HUD.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -60,12 +60,30 @@ import platform.UIKit.window import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +/** + * Default [BaseHUD] implementation. + */ actual class HUD private constructor(private val containerView: ContainerView, private val viewController: UIViewController, wrapper: (UIViewController) -> UIViewController, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) { + /** + * Builder class for creating a [HUD] + * @param viewController The [UIViewController] to present the loading indicator + * @param wrapper a modifier method for modifying the ViewController representing the loading indicator before it is presented. + */ actual class Builder(private val viewController: UIViewController, private val wrapper: (UIViewController) -> UIViewController) : BaseHUD.Builder() { + /** + * Constructor + * @param viewController The [UIViewController] to present the loading indicator + */ constructor(viewController: UIViewController) : this(viewController, { it }) + /** + * Creates a [HUD] based on [hudConfig]. + * @param hudConfig The [HudConfig] to apply to the [HUD]. + * @param coroutineScope The [CoroutineScope] managing the lifecycle of the HUD. + * @return the created [HUD] + */ actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD( ContainerView(hudConfig, viewController.view.window?.bounds ?: UIScreen.mainScreen.bounds), viewController, @@ -85,16 +103,14 @@ actual class HUD private constructor(private val containerView: ContainerView, p get() = when (hudConfig.style) { HUDStyle.CUSTOM -> UIColor.colorNamed("li_colorBackground") ?: UIColor.lightGrayColor HUDStyle.SYSTEM -> - if (traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark) - UIColor.blackColor else UIColor.whiteColor + if (traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark) UIColor.blackColor else UIColor.whiteColor } private val foregroundColor: UIColor get() = when (hudConfig.style) { HUDStyle.CUSTOM -> UIColor.colorNamed("li_colorAccent") ?: UIColor.darkGrayColor HUDStyle.SYSTEM -> - if (traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark) - UIColor.whiteColor else UIColor.blackColor + if (traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark) UIColor.whiteColor else UIColor.blackColor } // NOTES: Cast to CGFloat is needed for Arm32 diff --git a/hud/src/iosTest/kotlin/IOSHUDTests.kt b/hud/src/iosTest/kotlin/IOSHUDTests.kt index 7f6a275c0..8024574d5 100644 --- a/hud/src/iosTest/kotlin/IOSHUDTests.kt +++ b/hud/src/iosTest/kotlin/IOSHUDTests.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -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/hud/src/jsMain/kotlin/HUD.kt b/hud/src/jsMain/kotlin/HUD.kt index ca26074b5..aecc97777 100644 --- a/hud/src/jsMain/kotlin/HUD.kt +++ b/hud/src/jsMain/kotlin/HUD.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. @@ -19,8 +19,22 @@ package com.splendo.kaluga.hud import kotlinx.coroutines.CoroutineScope +/** + * Default [BaseHUD] implementation. + */ actual class HUD(override val hudConfig: HudConfig, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) { + + /** + * Builder class for creating a [HUD] + */ actual class Builder : BaseHUD.Builder() { + + /** + * Creates a [HUD] based on [hudConfig]. + * @param hudConfig The [HudConfig] to apply to the [HUD]. + * @param coroutineScope The [CoroutineScope] managing the lifecycle of the HUD. + * @return the created [HUD] + */ actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD(hudConfig, coroutineScope) } diff --git a/hud/src/jvmMain/kotlin/HUD.kt b/hud/src/jvmMain/kotlin/HUD.kt index ca26074b5..aecc97777 100644 --- a/hud/src/jvmMain/kotlin/HUD.kt +++ b/hud/src/jvmMain/kotlin/HUD.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. @@ -19,8 +19,22 @@ package com.splendo.kaluga.hud import kotlinx.coroutines.CoroutineScope +/** + * Default [BaseHUD] implementation. + */ actual class HUD(override val hudConfig: HudConfig, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) { + + /** + * Builder class for creating a [HUD] + */ actual class Builder : BaseHUD.Builder() { + + /** + * Creates a [HUD] based on [hudConfig]. + * @param hudConfig The [HudConfig] to apply to the [HUD]. + * @param coroutineScope The [CoroutineScope] managing the lifecycle of the HUD. + * @return the created [HUD] + */ actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD(hudConfig, coroutineScope) } diff --git a/kaluga-library-components/build.gradle.kts b/kaluga-library-components/build.gradle.kts new file mode 100644 index 000000000..2f2e91698 --- /dev/null +++ b/kaluga-library-components/build.gradle.kts @@ -0,0 +1,36 @@ +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() +} + +gradlePlugin { + plugins.register("kaluga-library-components") { + id = "kaluga-library-components" + implementationClass = "LibraryComponentsPlugin" + } +} + +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:$kotlinVersion") + implementation("com.android.tools.build:gradle:$androidGradleVersion") + implementation("org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin:$ktLintVersion") +} diff --git a/links/src/jsTest/kotlin/com/splendo/kaluga/links/manager/LinksManagerTest.kt b/kaluga-library-components/settings/gradle.kts similarity index 79% rename from links/src/jsTest/kotlin/com/splendo/kaluga/links/manager/LinksManagerTest.kt rename to kaluga-library-components/settings/gradle.kts index 7b1839d5e..ac8878b62 100644 --- a/links/src/jsTest/kotlin/com/splendo/kaluga/links/manager/LinksManagerTest.kt +++ b/kaluga-library-components/settings/gradle.kts @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -15,6 +15,10 @@ */ -package com.splendo.kaluga.links.manager +pluginManagement { -class LinksManagerTestJs + repositories { + gradlePluginPortal() + google() + } +} diff --git a/kaluga-library-components/src/main/kotlin/Accessors.kt b/kaluga-library-components/src/main/kotlin/Accessors.kt new file mode 100644 index 000000000..b6641420f --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/Accessors.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. + + */ + +import com.android.build.api.dsl.CommonExtension +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.internal.dsl.BaseAppModuleExtension +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.androidApp(action: BaseAppModuleExtension.() -> Unit) { + configureAction("android", action) +} + +fun Project.androidLibrary(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 CommonExtension<*, *, *, *>.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/kaluga-library-components/src/main/kotlin/AndroidCommon.kt b/kaluga-library-components/src/main/kotlin/AndroidCommon.kt new file mode 100644 index 000000000..0dcd8985f --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/AndroidCommon.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. + + */ + +import com.android.build.gradle.LibraryExtension +import org.gradle.api.JavaVersion +import org.gradle.kotlin.dsl.dependencies + +fun org.gradle.api.Project.commonAndroidComponent(type: ComponentType = ComponentType.Default) { + androidLibrary { + androidCommon(this@commonAndroidComponent, type) + } + + dependencies { + implementationDependency(Dependencies.KotlinX.Coroutines.Android) + implementationDependency(Dependencies.AndroidX.AppCompat) + + testImplementationDependency(Dependencies.JUnit) + testImplementationDependency(Dependencies.Mockito.Core) + testImplementationDependency(Dependencies.ByteBuddy.Agent) + testImplementationDependency(Dependencies.Kotlin.Test) + testImplementationDependency(Dependencies.Kotlin.JUnit) + + androidTestImplementationDependency(Dependencies.Mockito.Core) + 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) + } +} + +fun LibraryExtension.androidCommon(project: org.gradle.api.Project, componentType: ComponentType = ComponentType.Default) { + compileSdk = LibraryImpl.Android.compileSdk + buildToolsVersion = LibraryImpl.Android.buildTools + + defaultConfig { + minSdk = LibraryImpl.Android.minSdk + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + listOf("-XXLanguage:+InlineClasses", "-Xjvm-default=all") + } + + when (componentType) { + is ComponentType.Compose -> { + project.logger.lifecycle("This project module is a Compose only module") + buildFeatures { + compose = true + } + composeOptions { + 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/AndroidCompose.kt b/kaluga-library-components/src/main/kotlin/AndroidCompose.kt new file mode 100644 index 000000000..1236bc57c --- /dev/null +++ b/kaluga-library-components/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() { + group = Library.group + version = Library.version + commonAndroidComponent(ComponentType.Compose) + dependencies { + 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 { + sourceSets.all { + languageSettings { + optIn("androidx.compose.material.ExperimentalMaterialApi") + } + } + } + + ktlint { disabledRules.set(listOf("no-wildcard-imports", "filename", "import-ordering")) } + + publish(ComponentType.Compose) +} diff --git a/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt b/kaluga-library-components/src/main/kotlin/AndroidDatabinding.kt similarity index 64% rename from keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt rename to kaluga-library-components/src/main/kotlin/AndroidDatabinding.kt index 0b77bb506..0f1021bf3 100644 --- a/keyboard/src/androidLibMain/kotlin/ClearFocusHandler.kt +++ b/kaluga-library-components/src/main/kotlin/AndroidDatabinding.kt @@ -1,3 +1,5 @@ +import org.gradle.kotlin.dsl.dependencies + /* Copyright 2022 Splendo Consulting B.V. The Netherlands @@ -15,16 +17,12 @@ */ -package com.splendo.kaluga.keyboard - -import android.app.Activity +fun org.gradle.api.Project.databindingAndroidComponent() { + group = Library.group + version = Library.version + commonAndroidComponent(ComponentType.DataBinding) -interface ClearFocusHandler { - fun clearFocus(activity: Activity) -} + ktlint { disabledRules.set(listOf("no-wildcard-imports", "filename", "import-ordering")) } -class ViewClearFocusHandler : ClearFocusHandler { - override fun clearFocus(activity: Activity) { - activity.currentFocus?.clearFocus() - } -} + 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 new file mode 100644 index 000000000..f1ec2818c --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/Component.kt @@ -0,0 +1,245 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.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.Framework +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTests +import java.util.Locale + +sealed class ComponentType { + object Default : ComponentType() + object Compose : ComponentType() + object DataBinding : ComponentType() +} + +fun Project.commonComponent(iosExport: (Framework.() -> Unit)? = null) { + group = Library.group + version = Library.version + kotlinMultiplatform { + commonMultiplatformComponent(this@commonComponent, iosExport) + } + + commonAndroidComponent() + androidLibrary { + commonMultiplatformComponentAndroid() + } + + task("printConfigurations") { + doLast { + configurations.all { println(this) } + } + } + + afterEvaluate { + Library.IOS.targets.forEach { + val targetName = it.sourceSetName + if (tasks.names.contains("linkDebugTest${targetName.capitalize(Locale.ENGLISH) }")) { + // creating copy task for the target + val copyTask = tasks.create("copy${targetName.capitalize(Locale.ENGLISH) }TestResources", Copy::class.java) { + from("src/iosTest/resources/.") + into("$buildDir/bin/$targetName/debugTest") + } + + // apply copy task to the target + tasks.named("linkDebugTest${targetName.capitalize(Locale.ENGLISH)}") { + 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 { module -> + afterEvaluate { + logger.info("[connect_check_expansion] :${project.name}:connectedDebugAndroidTest dependsOn:${module.name}:connectedDebugAndroidTest") + tasks.getByPath("connectedDebugAndroidTest") + .dependsOn(":${module.name}:connectedDebugAndroidTest") + } + } + } +} + +fun KotlinMultiplatformExtension.commonMultiplatformComponent(currentProject: Project, iosExport: (Framework.() -> Unit)? = null) { + targets { + configureEach { + compilations.configureEach { + (kotlinOptions as? KotlinJvmOptions)?.jvmTarget = "1.8" + } + } + } + + android("androidLib").publishAllLibraryVariants() + val target: KotlinNativeTarget.() -> Unit = + { + binaries { + iosExport?.let { iosExport -> + framework { + iosExport() + } + } + getTest("DEBUG").apply { + freeCompilerArgs = freeCompilerArgs + listOf("-e", "com.splendo.kaluga.test.base.mainBackground") + } + } + } + val targets = currentProject.Library.IOS.targets + targets.forEach { iosTarget -> + when (iosTarget) { + IOSTarget.X64 -> iosX64(target).applyTestDevice(currentProject) + IOSTarget.Arm64 -> iosArm64(target) + IOSTarget.SimulatorArm64 -> iosSimulatorArm64(target).applyTestDevice(currentProject) + } + } + + jvm() + js(KotlinJsCompilerType.IR) { + browser() + nodejs() + compilations.configureEach { + kotlinOptions { + metaInfo = true + sourceMap = true + moduleKind = "umd" + } + } + binaries.executable() + } + + val commonMain = sourceSets.getByName("commonMain").apply { + dependencies { + implementationDependency(Dependencies.KotlinX.Coroutines.Core) + } + } + + val commonTest = sourceSets.getByName("commonTest").apply { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + + sourceSets.getByName("jvmMain").apply { + dependencies { + implementation(kotlin("stdlib")) + implementationDependency(Dependencies.KotlinX.Coroutines.Swing) + } + } + + sourceSets.getByName("jvmTest").apply { + dependsOn(commonTest) + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + + sourceSets.getByName("jsMain").apply { + dependencies { + implementation(kotlin("stdlib-js")) + implementationDependency(Dependencies.KotlinX.Coroutines.Js) + } + } + + sourceSets.getByName("jsTest").apply { + dependencies { + implementation(kotlin("test-js")) + } + } + + val iosMain = sourceSets.maybeCreate("iosMain").apply { + dependsOn(commonMain) + } + + val iosTest = sourceSets.maybeCreate("iosTest").apply { + dependsOn(commonTest) + } + + targets.forEach { + val sourceSetName = it.sourceSetName + + sourceSets.getByName("${sourceSetName}Main").apply { + dependsOn(iosMain) + } + sourceSets.getByName("${sourceSetName}Test").apply { + dependsOn(iosTest) + } + } + + sourceSets.maybeCreate("androidLibInstrumentedTest").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 LibraryExtension.commonMultiplatformComponentAndroid() { + 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 = project.Library.IOS.TestRunnerDeviceId + } +} + +val IOSTarget.sourceSetName: String get() = when (this) { + IOSTarget.X64 -> "iosX64" + IOSTarget.Arm64 -> "iosArm64" + IOSTarget.SimulatorArm64 -> "iosSimulatorArm64" +} diff --git a/kaluga-library-components/src/main/kotlin/Dependencies.kt b/kaluga-library-components/src/main/kotlin/Dependencies.kt new file mode 100644 index 000000000..19d49e01f --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/Dependencies.kt @@ -0,0 +1,225 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.ModuleDependency +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler + +data class Dependency(val group: String, val name: String, val version: String? = null) { + val notation = "$group:$name${version?.let { ":$it" } ?: ""}" +} + +typealias ModuleDependencyHandler = ModuleDependency.() -> Unit + +fun KotlinDependencyHandler.apiDependency(dependency: Dependency, handler: ModuleDependencyHandler? = null) = api(dependency.notation) { + handler?.invoke(this) +} +fun KotlinDependencyHandler.implementationDependency(dependency: Dependency, handler: ModuleDependencyHandler? = null) = implementation(dependency.notation) { + handler?.invoke(this) +} + +fun DependencyHandler.apiDependency(dependency: Dependency, handler: ModuleDependencyHandler? = null) { + add("api", dependency.notation).apply { + (this as? ModuleDependency)?.let { + handler?.invoke(it) + } + } +} +fun DependencyHandler.implementationDependency(dependency: Dependency, handler: ModuleDependencyHandler? = null) { + add("implementation", dependency.notation).apply { + (this as? ModuleDependency)?.let { + handler?.invoke(it) + } + } +} +fun DependencyHandler.debugImplementationDependency(dependency: Dependency, handler: ModuleDependencyHandler? = null) { + add("debugImplementation", dependency.notation).apply { + (this as? ModuleDependency)?.let { + handler?.invoke(it) + } + } +} +fun DependencyHandler.testImplementationDependency(dependency: Dependency, handler: ModuleDependencyHandler? = null) { + add("testImplementation", dependency.notation).apply { + (this as? ModuleDependency)?.let { + handler?.invoke(it) + } + } +} +fun DependencyHandler.androidTestImplementationDependency(dependency: Dependency, handler: ModuleDependencyHandler? = null) { + add("androidTestImplementation", dependency.notation).apply { + (this as? ModuleDependency)?.let { + handler?.invoke(it) + } + } +} + +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 { + + private const val group = "org.jetbrains.kotlinx" + + object Coroutines { + private const val version = "1.6.4" + 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-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) + } + + object Serialization { + private const val version = "1.4.1" + val Core = Dependency(group, "kotlinx-serialization-core", version) + val Json = Dependency(group, "kotlinx-serialization-json", version) + } + + val AtomicFu = Dependency(group, "atomicfu", "0.19.0") + } + + object Accompanist { + private const val groupBase = "com.google.accompanist" + private const val version = "0.28.0" + + val DrawablePainter = Dependency(groupBase, "accompanist-drawablepainter", version) + val MaterialThemeAdapter = Dependency(groupBase, "accompanist-themeadapter-material", version) + } + + object Android { + internal const val groupBase = "com.google.android" + private const val materialBase = "$groupBase.material" + + val Material = Dependency(materialBase, "material", "1.7.0") + + object Play { + private const val group = "$groupBase.play" + val Core = Dependency(group, "core", "1.10.3") + val CoreKtx = Dependency(group, "core-ktx", "1.8.1") + } + object PlayServices { + private const val group = "$groupBase.gms" + private const val version = "21.0.1" + 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.5" + + object Activity { + private const val group = "$groupBase.activity" + private const val version = "1.6.1" + val Activity = Dependency(group, "activity", version) + val Ktx = Dependency(group, "activity-ktx", version) + val Compose = Dependency(group, "activity-compose", version) + } + val AppCompat = Dependency("$groupBase.appcompat", "appcompat", "1.6.0") + 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.3.1" + private const val uiVersion = "1.3.3" + 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", uiVersion) + val UITooling = Dependency("$composeGroupBase.ui", "ui-tooling", uiVersion) + val UIToolingPreview = Dependency("$composeGroupBase.ui", "ui-tooling-preview", uiVersion) + } + 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.3" + + val Compose = Dependency(group, "navigation-compose", version) + } + object Test { + private const val group = "$groupBase.test" + private const val versionPostfix = "" + private const val version = "1.5.0$versionPostfix" + private const val espressoVersion = "3.5.1$versionPostfix" + private const val junitVersion = "1.1.5$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", espressoVersion) + 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.3.2" + private const val androidVersion = "3.3.2" + private const val composeVersion = "3.4.1" + val Android = Dependency(group, "koin-android", androidVersion) + val AndroidXCompose = Dependency(group, "koin-androidx-compose", composeVersion) + val Core = Dependency(group, "koin-core", version) + } + + val Napier = Dependency("io.github.aakira", "napier", "2.6.1") + + val JUnit = Dependency("junit", "junit", "4.13.2") + + object Mockito { + private const val group = "org.mockito" + private const val version = "5.0.0" + + 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.12.22" + + val Android = Dependency(group, "byte-buddy-android", version) + val Agent = Dependency(group, "byte-buddy-agent", version) + } +} diff --git a/kaluga-library-components/src/main/kotlin/GitBranch.kt b/kaluga-library-components/src/main/kotlin/GitBranch.kt new file mode 100644 index 000000000..0ff23e815 --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/GitBranch.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 org.gradle.api.Project + +data class GitBranch(val branch: String, val kalugaBranchPostfix: String) + +val Project.GitBranch: GitBranch get() { + val GITHUB_GIT_BRANCH = java.lang.System.getenv("GITHUB_REF_NAME") // could also be a tag name + 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 github's branch detection + // - else: try to get it via the `git` CLI. + val branch = (kaluga_branch ?: GITHUB_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 { + + logger.lifecycle("decided branch: '$branch' to postfix '$it', isRelease: $release (from: GITHUB_GIT_BRANCH env: $GITHUB_GIT_BRANCH, kaluga_branch property: $kaluga_branch , MAVEN_CENTRAL_RELEASE env: $MAVEN_CENTRAL_RELEASE , git cli: $branchFromGit)") + } + + return GitBranch(branch, kalugaBranchPostfix) +} diff --git a/kaluga-library-components/src/main/kotlin/Library.kt b/kaluga-library-components/src/main/kotlin/Library.kt new file mode 100644 index 000000000..64062b97a --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/Library.kt @@ -0,0 +1,121 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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.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 + +private val libraries: MutableMap = mutableMapOf() + +/** + * Gets a [LibraryImpl] 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) { LibraryImpl(this) } + +class LibraryImpl(project: Project) { + + private val props: Properties = File("${project.rootProject.buildDir.absolutePath}/../local.properties").let { file -> + if (file.exists) { + file.loadProperties() + } else { + Properties() + } + } + private val logger = project.logger + private val baseVersion = "1.0.0" + val group = "com.splendo.kaluga" + val version: String by lazy { + val libraryVersionLocalProperties: String? = props["kaluga.libraryVersion"] as? String + (libraryVersionLocalProperties ?: "$baseVersion${project.GitBranch.kalugaBranchPostfix}").also { + println("Library version $it") + } + } + 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 { + const val minSdk = 23 + const val compileSdk = 33 + const val targetSdk = 33 + const val buildTools = "33.0.1" + const val composeCompiler = "1.4.0" + } + + 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 { + logger.lifecycle("Run on real ios device: $it from sdk: $sdkName") + } + + // Run on IntelliJ + val ideaActive = (System.getProperty("idea.active") == "true").also { + logger.lifecycle("Run on IntelliJ: $it") + } + + // Run on apple silicon + val isAppleSilicon = (System.getProperty("os.arch") == "aarch64").also { + logger.lifecycle("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 -> + 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 { + 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 { + logger.lifecycle("local.properties read (kaluga.iosTestRunnerDeviceIdLocalProperty=$iosTestRunnerDeviceIdLocalProperty, using $it)") + } + ?: "iPhone 14".also { + logger.lifecycle("local.properties not found, using default value ($it)") + } + } + } + } + val IOS = IOSLibrary(props, logger) + + val connectCheckExpansion = (System.getenv().containsKey("CONNECTED_CHECK_EXPANSION") or System.getenv().containsKey("CI")).also { + if (it) { + logger.lifecycle("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/links/src/jsMain/kotlin/com/splendo/kaluga/links/LinksBuilder.kt b/kaluga-library-components/src/main/kotlin/LibraryComponentsPlugin.kt similarity index 72% rename from links/src/jsMain/kotlin/com/splendo/kaluga/links/LinksBuilder.kt rename to kaluga-library-components/src/main/kotlin/LibraryComponentsPlugin.kt index bda8f8038..dc23d9d2f 100644 --- a/links/src/jsMain/kotlin/com/splendo/kaluga/links/LinksBuilder.kt +++ b/kaluga-library-components/src/main/kotlin/LibraryComponentsPlugin.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -15,10 +15,11 @@ */ -package com.splendo.kaluga.links +import org.gradle.api.Plugin +import org.gradle.api.Project -actual class LinksBuilder : Links.Builder { - override fun create(): Links { - TODO("Not yet implemented") +class LibraryComponentsPlugin: Plugin { + override fun apply(target: Project) { + // no-op } } diff --git a/kaluga-library-components/src/main/kotlin/Publish.kt b/kaluga-library-components/src/main/kotlin/Publish.kt new file mode 100644 index 000000000..6aae56f54 --- /dev/null +++ b/kaluga-library-components/src/main/kotlin/Publish.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. + + */ + +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, + is ComponentType.DataBinding -> { + create("release", MavenPublication::class.java) { + from(components.getByName("release")) + + artifactId = project.name + groupId = Library.group + version = Library.version + } + create("debug", MavenPublication::class.java) { + from(components.getByName("debug")) + + artifactId = project.name + groupId = Library.group + version = Library.version + } + } + is ComponentType.Default -> { + getByName("kotlinMultiplatform") { + (this as MavenPublication).let { + artifactId = project.name + groupId = Library.group + version = Library.version + } + } + } + } + } + } + } +} diff --git a/links/src/jsTest/kotlin/com/splendo/kaluga/links/PlatformLinksManagerTest.kt b/kaluga-library-components/src/main/kotlin/PublishableComponent.kt similarity index 78% rename from links/src/jsTest/kotlin/com/splendo/kaluga/links/PlatformLinksManagerTest.kt rename to kaluga-library-components/src/main/kotlin/PublishableComponent.kt index 274c4ce6d..c713fd46c 100644 --- a/links/src/jsTest/kotlin/com/splendo/kaluga/links/PlatformLinksManagerTest.kt +++ b/kaluga-library-components/src/main/kotlin/PublishableComponent.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. @@ -15,6 +15,9 @@ */ -package com.splendo.kaluga.links +import org.gradle.api.Project -class PlatformLinksManagerTest +fun Project.publishableComponent() { + commonComponent() + publish() +} \ No newline at end of file diff --git a/keyboard-compose/README.md b/keyboard-compose/README.md new file mode 100644 index 000000000..c774163e7 --- /dev/null +++ b/keyboard-compose/README.md @@ -0,0 +1,32 @@ +# Keyboard Compose +This Android library contains composable functions to work with Kaluga keyboard. + +## Installing +This library is available on Maven Central. You can import Kaluga Keyboard Compose as follows: + +```kotlin +repositories { + // ... + mavenCentral() +} +// ... +dependencies { + // ... + implementation("com.splendo.kaluga.keyboard-compose:$kalugaVersion") +} +``` + +## Usage + +Create a `ComposeKeyboardManager.Builder` and add it to the `LifecycleSubscribables` of a `BaseLifecycleViewModel`. +Bind the ViewModel to your composable view using `ViewModelComposable`. + +To show the keyboard, add a `FocusRequester` to a view using +```kotlin +TextField( + modifier = Modifier + .focusRequester(focusRequester) +) +``` + +And focus on it using `ComposeFocusHandler(focusRequester)` diff --git a/keyboard-compose/api/keyboard-compose.api b/keyboard-compose/api/keyboard-compose.api index 1a894c34f..51135891c 100644 --- a/keyboard-compose/api/keyboard-compose.api +++ b/keyboard-compose/api/keyboard-compose.api @@ -1,12 +1,24 @@ -public final class com/splendo/kaluga/keyboard/compose/ComposeClearFocusHandler : com/splendo/kaluga/keyboard/ClearFocusHandler { +public final class com/splendo/kaluga/keyboard/compose/ComposeFocusHandler : com/splendo/kaluga/keyboard/FocusHandler { public static final field $stable I - public fun (Lkotlinx/coroutines/flow/StateFlow;)V - public fun clearFocus (Landroid/app/Activity;)V + public fun (Landroidx/compose/ui/focus/FocusRequester;)V + public final fun getFocusRequester ()Landroidx/compose/ui/focus/FocusRequester; } -public final class com/splendo/kaluga/keyboard/compose/ComposeFocusHandler : com/splendo/kaluga/keyboard/FocusHandler { +public final class com/splendo/kaluga/keyboard/compose/ComposeKeyboardManager : com/splendo/kaluga/keyboard/BaseKeyboardManager { public static final field $stable I - public fun (Landroidx/compose/ui/focus/FocusRequester;)V - public fun requestFocus (Landroid/app/Activity;)V + public fun ()V + public fun (Landroidx/compose/ui/focus/FocusManager;)V + public synthetic fun (Landroidx/compose/ui/focus/FocusManager;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun hide ()V + public synthetic fun show (Lcom/splendo/kaluga/keyboard/FocusHandler;)V + public fun show (Lcom/splendo/kaluga/keyboard/compose/ComposeFocusHandler;)V +} + +public final class com/splendo/kaluga/keyboard/compose/ComposeKeyboardManager$Builder : com/splendo/kaluga/architecture/compose/lifecycle/ComposableLifecycleSubscribable, com/splendo/kaluga/keyboard/BaseKeyboardManager$Builder { + public static final field $stable I + public fun ()V + public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/BaseKeyboardManager; + public synthetic fun getModifier ()Lkotlin/jvm/functions/Function2; + public fun getModifier ()Lkotlin/jvm/functions/Function4; } diff --git a/keyboard-compose/build.gradle.kts b/keyboard-compose/build.gradle.kts index 7b626d1cc..26d50d1a4 100644 --- a/keyboard-compose/build.gradle.kts +++ b/keyboard-compose/build.gradle.kts @@ -3,28 +3,14 @@ plugins { kotlin("android") id("jacoco") id("convention.publication") + id("org.jetbrains.dokka") 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"]}!!") + api(project(":architecture-compose")) } diff --git a/keyboard-compose/src/androidLibAndroidTest/AndroidManifest.xml b/keyboard-compose/src/androidTest/AndroidManifest.xml similarity index 100% rename from keyboard-compose/src/androidLibAndroidTest/AndroidManifest.xml rename to keyboard-compose/src/androidTest/AndroidManifest.xml diff --git a/keyboard-compose/src/androidLibAndroidTest/kotlin/TestActivity.kt b/keyboard-compose/src/androidTest/kotlin/TestActivity.kt similarity index 100% rename from keyboard-compose/src/androidLibAndroidTest/kotlin/TestActivity.kt rename to keyboard-compose/src/androidTest/kotlin/TestActivity.kt diff --git a/keyboard-compose/src/androidLibMain/AndroidManifest.xml b/keyboard-compose/src/main/AndroidManifest.xml similarity index 100% rename from keyboard-compose/src/androidLibMain/AndroidManifest.xml rename to keyboard-compose/src/main/AndroidManifest.xml diff --git a/keyboard-compose/src/androidLibMain/kotlin/ComposeFocusHandler.kt b/keyboard-compose/src/main/kotlin/ComposeFocusHandler.kt similarity index 78% rename from keyboard-compose/src/androidLibMain/kotlin/ComposeFocusHandler.kt rename to keyboard-compose/src/main/kotlin/ComposeFocusHandler.kt index 650bb9dcc..a1bfd032a 100644 --- a/keyboard-compose/src/androidLibMain/kotlin/ComposeFocusHandler.kt +++ b/keyboard-compose/src/main/kotlin/ComposeFocusHandler.kt @@ -17,12 +17,11 @@ 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() - } -} +/** + * A [FocusHandler] that focuses using a given [FocusRequester] + * @param focusRequester the [FocusRequester] to handle focusing. + */ +class ComposeFocusHandler(val focusRequester: FocusRequester) : FocusHandler diff --git a/keyboard-compose/src/main/kotlin/ComposeKeyboardManager.kt b/keyboard-compose/src/main/kotlin/ComposeKeyboardManager.kt new file mode 100644 index 000000000..de2f7482c --- /dev/null +++ b/keyboard-compose/src/main/kotlin/ComposeKeyboardManager.kt @@ -0,0 +1,62 @@ +/* + Copyright 2022 Splendo Consulting B.V. The Netherlands + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 + +/** + * A [BaseKeyboardManager] that takes a [ComposeFocusHandler]. Uses for managing the keyboard in Compose views. + * @param currentFocusManager The initial [FocusManager] to manage the focus. + */ +class ComposeKeyboardManager(internal var currentFocusManager: FocusManager? = null) : BaseKeyboardManager { + + /** + * A [BaseKeyboardManager.Builder] for creating a [ComposeKeyboardManager] + */ + 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/README.md b/keyboard/README.md index bc0e43d20..7d319fb0e 100644 --- a/keyboard/README.md +++ b/keyboard/README.md @@ -36,12 +36,12 @@ fun hideKeyboard(builder: KeyboardManager.Builder) { The `KeyboardManager.Builder` and `FocusHandler` are provided on the platform. ### Android -On Android the builder is a `LifecycleSubscribable` (see Architecture) that needs a `LifecycleSubscribable.LifecycleManager` object to provide the current context in which to display the keyboard. -For `BaseViewModel`, the builder should be made **publicly** visible and bound to a `KalugaViewModelLifecycleObserver`. +On Android the builder is an `ActivityLifecycleSubscribable` (see Architecture) that needs an `ActivityLifecycleSubscribable.LifecycleManager` object to provide the current context in which to display the keyboard. +For `BaseLifecycleViewModel`, the builder should be provided to `BaseLifecycleViewModel.activeLifecycleSubscribables` (using the constructor or `BaseLifecycleViewModel.addLifecycleSubscribables`) and bound to a `KalugaViewModelLifecycleObserver` or `ViewModelComposable`. The keyboardHostingView is any resource Id for a `View` attached to the `Activity` bound to the manager. ```kotlin -class KeyboardViewModel(val builder: KeyboardManager.Builder): BaseViewModel() { +class KeyboardViewModel(private val builder: KeyboardManager.Builder): BaseLifecycleViewModel(builder) { private val keyboardManager = builder.create(coroutineScope) @@ -72,7 +72,7 @@ class MyActivity: KalugaViewModelActivity() { } ``` -For other usages, make sure to call `LifecycleSubscriber.subscribe` and `LifecycleSubscriber.unsubscribe` to manage the lifecycle manually. +For other usages, make sure to call `ActivityLifecycleSubscribable.subscribe` and `ActivityLifecycleSubscribable.unsubscribe` to manage the lifecycle manually. ```kotlin // Android specific @@ -86,26 +86,7 @@ MainScope().launch { You can use the `AppCompatActivity.keyboardManagerBuilder` convenience method to get a builder that is valid during the lifespan of the Activity it belongs to. ### Android compose-ui - -Use compose's implementation for `FocusHandler`. -```kotlin - -@Composable -fun SomeLayout() { - val focusHandler = ComposeFocusHandler(FocusRequester.Default) - TextField ( - value = numberInput, - onValueChange = numberInputDelegate, - modifier = Modifier - .fillMaxWidth() - .focusRequester(FocusRequester.Default), // add this line to the TextField Modifier. - ) - - Button( { viewModel.show(focusHandler) } ) { - Text("Button") - } -} -``` +Use the [`keyboard-compose` module](../keyboard-compose) ### iOS In iOS the builder is attached to a `UIApplication`. By default this will be ` UIApplication.sharedApplication`, but it can be overwritten by a custom Application. The `KeyboardHostingView` can be any `UIView` that `canBecomeFirstResponder`. @@ -116,6 +97,43 @@ val customBuilder = KeyboardManagerBuilder(application) val keyboardHostingView = UITextField() ``` +For SwiftUI, the [Kaluga SwiftUI scripts](https://github.com/splendo/kaluga-swiftui) contain a `SwiftUIKeyboardManagerBuilder` if the `includeKeyboard` setting is set to true. +Use it as follows: + +```swift +struct SomeView: View { + + // Create a Hashable enum to identify fields to focus on + enum Field: String, Hashable { + case textField + } + + // Observe a SwiftUIKeyboardManagerBuilder + @ObservedObject var keyboardManagerBuilder: SwiftUIKeyboardManagerBuilder + + // Expose the current field with an @FocusState + @FocusState private var focusedField: Field? + + init { + keyboardManagerBuilder = SwiftUIKeyboardManagerBuilder() + // Provide keyboardManagerBuilder to shared code + } + + var body: some View { + // Create View and bind the keyboard manager + VStack{ + TextField("", text: _text.projectedValue) + .focused($focusedField, equals: Field.textField) + } + .bindKeyboardManager( + keyboardManagerBuilder: keyboardManagerBuilder, + focusState: _focusedField.projectedValue + ) + } +} +``` + +Then create a `ValueFocusHandler` for `Field` using `swiftUIFocusHandler(value: Field.textField)` + ## Testing Use the [`test-utils-keyboard` module](../test-utils-keyboard) to get a mockable `KeyboardManager`. - diff --git a/keyboard/api/androidLib/keyboard.api b/keyboard/api/androidLib/keyboard.api index 71e1c0ef0..dcbd65ce5 100644 --- a/keyboard/api/androidLib/keyboard.api +++ b/keyboard/api/androidLib/keyboard.api @@ -3,49 +3,35 @@ public abstract interface class com/splendo/kaluga/keyboard/BaseKeyboardManager public abstract fun show (Lcom/splendo/kaluga/keyboard/FocusHandler;)V } -public abstract interface class com/splendo/kaluga/keyboard/BaseKeyboardManager$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { +public abstract interface class com/splendo/kaluga/keyboard/BaseKeyboardManager$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { public abstract fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/BaseKeyboardManager; } -public abstract interface class com/splendo/kaluga/keyboard/ClearFocusHandler { - public abstract fun clearFocus (Landroid/app/Activity;)V +public abstract interface class com/splendo/kaluga/keyboard/FocusHandler { } -public abstract interface class com/splendo/kaluga/keyboard/FocusHandler { - public abstract fun requestFocus (Landroid/app/Activity;)V +public final class com/splendo/kaluga/keyboard/ViewFocusHandler : com/splendo/kaluga/keyboard/FocusHandler { + public fun (I)V + public final fun requestFocus (Landroid/app/Activity;)V } -public final class com/splendo/kaluga/keyboard/KeyboardManager : com/splendo/kaluga/keyboard/BaseKeyboardManager, kotlinx/coroutines/CoroutineScope { - public fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;Lcom/splendo/kaluga/keyboard/ClearFocusHandler;Lkotlinx/coroutines/CoroutineScope;)V - public synthetic fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;Lcom/splendo/kaluga/keyboard/ClearFocusHandler;Lkotlinx/coroutines/CoroutineScope;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class com/splendo/kaluga/keyboard/ViewKeyboardManager : com/splendo/kaluga/keyboard/BaseKeyboardManager, kotlinx/coroutines/CoroutineScope { + public fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;Lkotlinx/coroutines/CoroutineScope;)V + public synthetic fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;Lkotlinx/coroutines/CoroutineScope;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun hide ()V - public fun show (Lcom/splendo/kaluga/keyboard/FocusHandler;)V + public synthetic fun show (Lcom/splendo/kaluga/keyboard/FocusHandler;)V + public fun show (Lcom/splendo/kaluga/keyboard/ViewFocusHandler;)V } -public final class com/splendo/kaluga/keyboard/KeyboardManager$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable, com/splendo/kaluga/keyboard/BaseKeyboardManager$Builder { +public final class com/splendo/kaluga/keyboard/ViewKeyboardManager$Builder : com/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable, com/splendo/kaluga/keyboard/BaseKeyboardManager$Builder { public fun ()V - public fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;Lcom/splendo/kaluga/keyboard/ClearFocusHandler;)V - public synthetic fun (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleManagerObserver;Lcom/splendo/kaluga/keyboard/ClearFocusHandler;ILkotlin/jvm/internal/DefaultConstructorMarker;)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 synthetic fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/BaseKeyboardManager; - public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/KeyboardManager; - public fun getManager ()Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager; - public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable$LifecycleManager;)V + public fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/ViewKeyboardManager; + public fun getManager ()Lcom/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable$LifecycleManager; + public fun subscribe (Lcom/splendo/kaluga/architecture/lifecycle/ActivityLifecycleSubscribable$LifecycleManager;)V public fun unsubscribe ()V } -public final class com/splendo/kaluga/keyboard/KeyboardManagerKt { - public static final fun keyboardManagerBuilder (Landroidx/appcompat/app/AppCompatActivity;Lcom/splendo/kaluga/keyboard/ClearFocusHandler;)Lcom/splendo/kaluga/keyboard/KeyboardManager$Builder; - public static synthetic fun keyboardManagerBuilder$default (Landroidx/appcompat/app/AppCompatActivity;Lcom/splendo/kaluga/keyboard/ClearFocusHandler;ILjava/lang/Object;)Lcom/splendo/kaluga/keyboard/KeyboardManager$Builder; -} - -public final class com/splendo/kaluga/keyboard/ViewClearFocusHandler : com/splendo/kaluga/keyboard/ClearFocusHandler { - public fun ()V - public fun clearFocus (Landroid/app/Activity;)V -} - -public final class com/splendo/kaluga/keyboard/ViewFocusHandler : com/splendo/kaluga/keyboard/FocusHandler { - public fun (I)V - public fun requestFocus (Landroid/app/Activity;)V -} - diff --git a/keyboard/api/jvm/keyboard.api b/keyboard/api/jvm/keyboard.api index 67ae10b57..8644026bf 100644 --- a/keyboard/api/jvm/keyboard.api +++ b/keyboard/api/jvm/keyboard.api @@ -3,7 +3,7 @@ public abstract interface class com/splendo/kaluga/keyboard/BaseKeyboardManager public abstract fun show (Lcom/splendo/kaluga/keyboard/FocusHandler;)V } -public abstract interface class com/splendo/kaluga/keyboard/BaseKeyboardManager$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribableMarker { +public abstract interface class com/splendo/kaluga/keyboard/BaseKeyboardManager$Builder : com/splendo/kaluga/architecture/lifecycle/LifecycleSubscribable { public abstract fun create (Lkotlinx/coroutines/CoroutineScope;)Lcom/splendo/kaluga/keyboard/BaseKeyboardManager; } diff --git a/keyboard/build.gradle.kts b/keyboard/build.gradle.kts index 69ac0ba34..928327aed 100644 --- a/keyboard/build.gradle.kts +++ b/keyboard/build.gradle.kts @@ -3,15 +3,11 @@ plugins { id("jacoco") id("com.android.library") id("convention.publication") + id("org.jetbrains.dokka") 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"]!! +publishableComponent() kotlin { sourceSets { @@ -24,7 +20,7 @@ kotlin { getByName("commonTest") { dependencies { - api(project(":test-utils-base", "")) + api(project(":test-utils-keyboard", "")) } } } diff --git a/keyboard/src/androidLibAndroidTest/AndroidManifest.xml b/keyboard/src/androidLibInstrumentedTest/AndroidManifest.xml similarity index 100% rename from keyboard/src/androidLibAndroidTest/AndroidManifest.xml rename to keyboard/src/androidLibInstrumentedTest/AndroidManifest.xml diff --git a/keyboard/src/androidLibAndroidTest/kotlin/ActivityKeyboardManagerBuilderTest.kt b/keyboard/src/androidLibInstrumentedTest/kotlin/ActivityKeyboardManagerBuilderTest.kt similarity index 96% rename from keyboard/src/androidLibAndroidTest/kotlin/ActivityKeyboardManagerBuilderTest.kt rename to keyboard/src/androidLibInstrumentedTest/kotlin/ActivityKeyboardManagerBuilderTest.kt index a7e809fca..05ecb98bc 100644 --- a/keyboard/src/androidLibAndroidTest/kotlin/ActivityKeyboardManagerBuilderTest.kt +++ b/keyboard/src/androidLibInstrumentedTest/kotlin/ActivityKeyboardManagerBuilderTest.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/keyboard/src/androidLibAndroidTest/kotlin/TestActivity.kt b/keyboard/src/androidLibInstrumentedTest/kotlin/TestActivity.kt similarity index 90% rename from keyboard/src/androidLibAndroidTest/kotlin/TestActivity.kt rename to keyboard/src/androidLibInstrumentedTest/kotlin/TestActivity.kt index 2d940cb49..4c949c0e8 100644 --- a/keyboard/src/androidLibAndroidTest/kotlin/TestActivity.kt +++ b/keyboard/src/androidLibInstrumentedTest/kotlin/TestActivity.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -21,6 +21,7 @@ import android.os.Bundle import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope +import com.splendo.kaluga.test.keyboard.keyboardManagerBuilder class TestActivity : AppCompatActivity() { diff --git a/keyboard/src/androidLibMain/kotlin/FocusHandler.kt b/keyboard/src/androidLibMain/kotlin/FocusHandler.kt index 4d0bd0da6..435051d49 100644 --- a/keyboard/src/androidLibMain/kotlin/FocusHandler.kt +++ b/keyboard/src/androidLibMain/kotlin/FocusHandler.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -24,16 +24,15 @@ import android.view.inputmethod.InputMethod.SHOW_EXPLICIT import android.view.inputmethod.InputMethodManager import androidx.annotation.IdRes -actual interface FocusHandler { - fun requestFocus(activity: Activity?) -} - +/** + * A [FocusHandler] that focuses on a view with a given ID. + * @param id the ID of the View to focus on + */ class ViewFocusHandler( @IdRes private val id: Int ) : FocusHandler { - override fun requestFocus(activity: Activity?) { - if (activity == null) - return + fun requestFocus(activity: Activity?) { + if (activity == null) return val view = activity.findViewById(id) ?: return view.requestFocus() val inputManager = activity.getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager diff --git a/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt b/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt index fb3b072d0..6bb5c0468 100644 --- a/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/androidLibMain/kotlin/KeyboardManager.kt @@ -1,5 +1,5 @@ /* -Copyright 2019 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. @@ -19,35 +19,38 @@ package com.splendo.kaluga.keyboard import android.content.Context.INPUT_METHOD_SERVICE import android.view.inputmethod.InputMethodManager -import androidx.appcompat.app.AppCompatActivity import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver -import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable -import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache -import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver +import com.splendo.kaluga.architecture.lifecycle.ActivityLifecycleSubscribable import kotlinx.coroutines.CoroutineScope -actual class KeyboardManager( +/** + * A [BaseKeyboardManager] that takes a [ViewFocusHandler]. Used managing the keyboard in XML Views. + * @param lifecycleManagerObserver The [LifecycleManagerObserver] to observe lifecycle changes. + * @param coroutineScope The [CoroutineScope] managing the keyboard lifecycle. + */ +class ViewKeyboardManager( private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver(), - private val clearFocusHandler: ClearFocusHandler, coroutineScope: 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) +) : BaseKeyboardManager, CoroutineScope by coroutineScope { + + /** + * A [BaseKeyboardManager.Builder] for creating a [ViewKeyboardManager] + * @param lifecycleManagerObserver The [LifecycleManagerObserver] to observe lifecycle changes. + */ + class Builder( + private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver() + ) : BaseKeyboardManager.Builder, ActivityLifecycleSubscribable by lifecycleManagerObserver { + override fun create(coroutineScope: CoroutineScope) = ViewKeyboardManager(lifecycleManagerObserver, coroutineScope) } - override fun show(focusHandler: FocusHandler) { - lifecycleManagerObserver.manager?.activity?.let { - focusHandler.requestFocus(it) - } + override fun show(focusHandler: ViewFocusHandler) { + focusHandler.requestFocus(lifecycleManagerObserver.manager?.activity) } override fun hide() { - lifecycleManagerObserver.manager?.activity?.let { activity -> - clearFocusHandler.clearFocus(activity) + val managedActivity = lifecycleManagerObserver.manager?.activity + managedActivity?.let { activity -> + activity.currentFocus?.clearFocus() val inputMethodManager = activity.getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager inputMethodManager?.let { if (it.isAcceptingText) { @@ -57,16 +60,3 @@ actual class KeyboardManager( } } } - -/** - * @return A [KeyboardManager.Builder] which can be used to manipulate the soft keyboard while this Activity is active. - * Will be created if need but only one instance will exist. - * - * Warning: Do not attempt to use this builder outside of the lifespan of the Activity. - * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.LifecycleViewModel], - * which can automatically track which Activity is active for it. - * - */ -fun AppCompatActivity.keyboardManagerBuilder(clearFocusHandler: ClearFocusHandler = ViewClearFocusHandler()): KeyboardManager.Builder = getOrPutAndRemoveOnDestroyFromCache { - KeyboardManager.Builder(lifecycleManagerObserver(), clearFocusHandler) -} diff --git a/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.kt b/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.kt index a366ab959..af8555758 100644 --- a/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.kt +++ b/keyboard/src/androidLibUnitTest/kotlin/AndroidKeyboardManagerTests.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. You may obtain a copy of the License at @@ -21,24 +21,24 @@ import android.view.inputmethod.InputMethod.SHOW_EXPLICIT import android.view.inputmethod.InputMethodManager import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner -import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable +import com.splendo.kaluga.architecture.lifecycle.ActivityLifecycleSubscribable 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,10 +70,10 @@ 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] + * @param FH the type of [FocusHandler] to handle focus requests. */ - interface Builder : LifecycleSubscribableMarker { + interface Builder : LifecycleSubscribable { /** * Creates KeyboardManager object * - * @return The KeyboardManager object + * @param coroutineScope The [CoroutineScope] managing the keyboard lifecycle. + * @return The created [BaseKeyboardManager] */ - fun create(coroutineScope: CoroutineScope): BaseKeyboardManager + fun create(coroutineScope: CoroutineScope): BaseKeyboardManager } /** - * Shows the keyboard for a given [KeyboardHostingView] + * Shows the keyboard for a given [FH] * - * @param keyboardHostingView The view for which the keyboard will be shown + * @param focusHandler The [FH] 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 9d98b9a7f..80159c7fa 100644 --- a/keyboard/src/commonTest/kotlin/KeyboardManagerTests.kt +++ b/keyboard/src/commonTest/kotlin/KeyboardManagerTests.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. You may obtain a copy of the License at @@ -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 e624f63fc..fa6b90765 100644 --- a/keyboard/src/iosMain/kotlin/FocusHandler.kt +++ b/keyboard/src/iosMain/kotlin/FocusHandler.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 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. @@ -19,14 +19,21 @@ package com.splendo.kaluga.keyboard import platform.UIKit.UIView -actual interface FocusHandler { - fun requestFocus() -} - +/** + * A [FocusHandler] that focuses on a given [UIView] + * @param view the [UIView] to focus on + */ class UIKitFocusHandler(val view: UIView) : FocusHandler { - override fun requestFocus() { + fun requestFocus() { if (view.canBecomeFirstResponder) { view.becomeFirstResponder() } } } + +/** + * A [FocusHandler] that stores a given [Value] to focus on. + * This generic implementation allows for usage from SwiftUI. + * @param Value the type of Value to focus on. + */ +class ValueFocusHandler(val value: Value) : FocusHandler diff --git a/keyboard/src/iosMain/kotlin/KeyboardManager.kt b/keyboard/src/iosMain/kotlin/KeyboardManager.kt index 1a21c19da..e41265d82 100644 --- a/keyboard/src/iosMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/iosMain/kotlin/KeyboardManager.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -22,13 +22,21 @@ import kotlinx.coroutines.CoroutineScope import platform.UIKit.UIApplication import platform.darwin.sel_registerName -actual class KeyboardManager(private val application: UIApplication) : BaseKeyboardManager { - - actual class Builder(private val application: UIApplication = UIApplication.sharedApplication) : BaseKeyboardManager.Builder { - actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager(application) +/** + * A [BaseKeyboardManager] that takes a [UIKitFocusHandler]. Used for managing the keyboard in UIKit views. + * @param application The [UIApplication] that the keyboard is running in. + */ +class UIKitKeyboardManager(private val application: UIApplication) : BaseKeyboardManager { + + /** + * Builder for a [UIKitKeyboardManager] + * @param application The [UIApplication] that the keyboard is running in. + */ + 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() } @@ -36,3 +44,18 @@ actual class KeyboardManager(private val application: UIApplication) : BaseKeybo application.sendAction(sel_registerName("resignFirstResponder"), null, null, null) } } + +/** + * A [BaseKeyboardManager] that takes a [ValueFocusHandler]. Uses for managing keyboard in a generic way so that it allows for usage from SwiftUI. + * @param Value the type of value to expect + * @param onFocusOnValue callback method to indicate how to handle a focus change to a [Value]. When `null` is provided, they keyboard should be dismissed. + */ +open class ValueKeyboardManager(private val onFocusOnValue: (Value?) -> Unit) : BaseKeyboardManager> { + override fun show(focusHandler: ValueFocusHandler) { + onFocusOnValue(focusHandler.value) + } + + override fun hide() { + onFocusOnValue(null) + } +} 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/KeyboardManager.kt b/keyboard/src/jsMain/kotlin/KeyboardManager.kt index 79bbe6e33..764d5d6f9 100644 --- a/keyboard/src/jsMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/jsMain/kotlin/KeyboardManager.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -20,10 +20,16 @@ package com.splendo.kaluga.keyboard import kotlinx.coroutines.CoroutineScope -actual class KeyboardManager : BaseKeyboardManager { +/** + * A [BaseKeyboardManager] that takes any [FocusHandler] + */ +class KeyboardManager : BaseKeyboardManager { - actual class Builder : BaseKeyboardManager.Builder { - actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager() + /** + * A [BaseKeyboardManager.Builder] to create a [KeyboardManager] + */ + class Builder : BaseKeyboardManager.Builder { + override fun create(coroutineScope: CoroutineScope) = KeyboardManager() } override fun show(focusHandler: FocusHandler) { diff --git a/keyboard/src/jvmMain/kotlin/KeyboardManager.kt b/keyboard/src/jvmMain/kotlin/KeyboardManager.kt index 79bbe6e33..764d5d6f9 100644 --- a/keyboard/src/jvmMain/kotlin/KeyboardManager.kt +++ b/keyboard/src/jvmMain/kotlin/KeyboardManager.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -20,10 +20,16 @@ package com.splendo.kaluga.keyboard import kotlinx.coroutines.CoroutineScope -actual class KeyboardManager : BaseKeyboardManager { +/** + * A [BaseKeyboardManager] that takes any [FocusHandler] + */ +class KeyboardManager : BaseKeyboardManager { - actual class Builder : BaseKeyboardManager.Builder { - actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager() + /** + * A [BaseKeyboardManager.Builder] to create a [KeyboardManager] + */ + class Builder : BaseKeyboardManager.Builder { + override fun create(coroutineScope: CoroutineScope) = KeyboardManager() } override fun show(focusHandler: FocusHandler) { diff --git a/links/README.md b/links/README.md index 8b50c33e1..65e57d679 100644 --- a/links/README.md +++ b/links/README.md @@ -1,83 +1,46 @@ # Links -Module used to decode an object from either an App Link, Universal Link or Deep Link's query. It also uses [kaluga-architecture](https://github.com/splendo/kaluga/tree/master/architecture) to open links in the Browser. +Module used to decode an object from either an App Link, Universal Link or Deep Link's query. -## Deserializer -`LinksDecoder` is used to convert query values into an object and it takes a list of values and a serializer. **It is important that the values passed to `LinksDecoder` are ordered in the same way they are declared in the data class**. -When `decodeFromList` is called, a `LinksDecoder` is created and it goes through the passed `List` one per one referring to the passed `serializer` in order to know the parameter's type and finally convert it. -### Special usages -- Query contains an array of values: In this case the query will have to contain a parameter (put just before the list of values) that identifies the size of that array. -``` kotlin -@Serializable -data class Aliment(val name: String) - -@Serializable -data class Recipe(val name: String, val ingredients: List) +## Installing +This library is available on Maven Central. You can import Kaluga Links as follows: -// Somewhere in the code -val query = "name=Carbonara&size=3&ingredients=Spaghetti&ingredients=Bacon&ingredients=Egg" +```kotlin +repositories { + // ... + mavenCentral() +} +// ... +dependencies { + // ... + implementation("com.splendo.kaluga.links:$kalugaVersion") +} ``` -The list size's parameter name is not important and is not included in the data class parameters, but it is requested by the decoder. - ## Usage +This library can be used to process an incoming URL into an object. This is useful when handling an App Link, Universal Link or Deep Link -The entry point for universal links or dynamic links in Android or iOS are `MainActivity.kt` and `AppDelegate.swift`. -Said so `MainActivity.kt` will have to override `onNewIntent` and call `handleIncomingLink`, while on iOS you should override the `application` method that receives a `NSUserActivity`. - - -``` kotlin -// MainActivity.kt - -override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - val appLinkData: Uri? = intent?.data - appLinkData?.let { - val url = URL(it.path) - sharedViewModel.handleIncomingData(url, Person.serializer()) - } +```kotlin +val linksManager = DefaultLinksManager.Builder().create() +val someClassOrNull = linksManager.validateLink(url)?.let { + linksManager.handleIncomingLink(it, SomeClass.serializer) } ``` -``` swift -// AppDelegate.swift -func application(_ application: NSApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([NSUserActivityRestoring]) -> Void) -> Bool { - // Get URL components from the incoming user activity. - guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, - let incomingURL = userActivity.webpageURL else { - return false - } - - viewController.viewModel.handleIncomingData(incomingUrl, Person.Companion.serializer()) -} -``` +The library parses the query parameters of a URL into a list of objects and decodes them into an object using a serializer. +This means query parameters should be in the order at which the Serializer specifies them. +When the object contains nested properties, they should be preceded by a numeric indicator of the amount of elements to expect: +```kotlin +@Serializable +data class Aliment(val name: String) +@Serializable +data class Recipe(val name: String, val ingredients: List) -``` kotlin -// common data class -@Serializable -data class Person(val name: String, val surname: String) +// Somewhere in the code +val url = "https://kaluga.splendo.com/?name=Carbonara&size=3&ingredients=Spaghetti&ingredients=Bacon&ingredients=Egg" -// ExampleSharedViewModel -class SharedViewModel( - linksBuilder: LinksBuilder, - navigator: Navigator> -) : NavigatingViewModel>(navigator) { - private val links = linksBuilder.create() - - fun handleIncomingData(url: String, serializer: KSerializer) { - links.handleIncomingLink(url, serializer) - } - - fun handleOutgoingLink(url: String) { - links.validateLink(url) - } -} ``` -### Android -Android `PlatformLinksHandler` implementation uses `UrlQuerySanitizer` in order to extract the full query from a url. Keep in mind that `UrlQuerySanitizer` -will convert space characters into underscore character. Instead use `+` character between 2 words if you want to represent a space. - -Follow [navigation](https://github.com/splendo/kaluga/tree/master/architecture#navigation) in order to create a `Navigator`. \ No newline at end of file +The names of the parameters are ignored when decoding. diff --git a/links/api/androidLib/links.api b/links/api/androidLib/links.api index c8df5d58f..942f2f6bf 100644 --- a/links/api/androidLib/links.api +++ b/links/api/androidLib/links.api @@ -1,72 +1,39 @@ -public final class com/splendo/kaluga/links/Links { - public fun (Lcom/splendo/kaluga/links/manager/LinksManager$Builder;)V - public final fun handleIncomingLink (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; - public final fun validateLink (Ljava/lang/String;)Ljava/lang/String; -} - -public abstract interface class com/splendo/kaluga/links/Links$Builder { - public abstract fun create ()Lcom/splendo/kaluga/links/Links; -} - -public final class com/splendo/kaluga/links/LinksBuilder : com/splendo/kaluga/links/Links$Builder { - public fun ()V - public fun create ()Lcom/splendo/kaluga/links/Links; -} - -public final class com/splendo/kaluga/links/manager/DefaultLinksManager : com/splendo/kaluga/links/manager/LinksManager { - public fun (Lcom/splendo/kaluga/links/manager/LinksHandler;)V +public final class com/splendo/kaluga/links/DefaultLinksManager : com/splendo/kaluga/links/LinksManager { + public fun (Lcom/splendo/kaluga/links/handler/LinksHandler;)V public fun handleIncomingLink (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; public fun validateLink (Ljava/lang/String;)Ljava/lang/String; } -public abstract interface class com/splendo/kaluga/links/manager/LinksHandler { - public abstract fun extractQueryAsList (Ljava/lang/String;)Ljava/util/List; - public abstract fun isValid (Ljava/lang/String;)Z +public final class com/splendo/kaluga/links/DefaultLinksManager$Builder : com/splendo/kaluga/links/LinksManager$Builder { + public fun ()V + public fun (Lcom/splendo/kaluga/links/handler/LinksHandler;)V + public fun create ()Lcom/splendo/kaluga/links/LinksManager; } -public abstract interface class com/splendo/kaluga/links/manager/LinksManager { +public abstract interface class com/splendo/kaluga/links/LinksManager { public abstract fun handleIncomingLink (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; public abstract fun validateLink (Ljava/lang/String;)Ljava/lang/String; } -public abstract interface class com/splendo/kaluga/links/manager/LinksManager$Builder { - public abstract fun create ()Lcom/splendo/kaluga/links/manager/LinksManager; +public abstract interface class com/splendo/kaluga/links/LinksManager$Builder { + public abstract fun create ()Lcom/splendo/kaluga/links/LinksManager; } -public final class com/splendo/kaluga/links/manager/LinksManagerBuilder : com/splendo/kaluga/links/manager/LinksManager$Builder { - public fun ()V - public fun create ()Lcom/splendo/kaluga/links/manager/LinksManager; +public final class com/splendo/kaluga/links/LinksManagerKt { + public static final fun handleIncomingLink (Lcom/splendo/kaluga/links/LinksManager;Landroid/net/Uri;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; + public static final fun handleIncomingLink (Lcom/splendo/kaluga/links/LinksManager;Ljava/net/URL;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; + public static final fun validateLink (Lcom/splendo/kaluga/links/LinksManager;Landroid/net/Uri;)Landroid/net/Uri; + public static final fun validateLink (Lcom/splendo/kaluga/links/LinksManager;Ljava/net/URL;)Ljava/net/URL; +} + +public abstract interface class com/splendo/kaluga/links/handler/LinksHandler { + public abstract fun extractQueryAsList (Ljava/lang/String;)Ljava/util/List; + public abstract fun isValid (Ljava/lang/String;)Z } -public final class com/splendo/kaluga/links/manager/PlatformLinksHandler : com/splendo/kaluga/links/manager/LinksHandler { +public final class com/splendo/kaluga/links/handler/PlatformLinksHandler : com/splendo/kaluga/links/handler/LinksHandler { public fun ()V public fun extractQueryAsList (Ljava/lang/String;)Ljava/util/List; public fun isValid (Ljava/lang/String;)Z } -public final class com/splendo/kaluga/links/utils/DecodersKt { - public static final fun decodeFromList (Ljava/util/List;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; -} - -public final class com/splendo/kaluga/links/utils/LinksDecoder : kotlinx/serialization/encoding/AbstractDecoder { - public fun (Lkotlin/collections/ArrayDeque;I)V - public synthetic fun (Lkotlin/collections/ArrayDeque;IILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeDecoder; - public fun decodeBoolean ()Z - public fun decodeByte ()B - public fun decodeChar ()C - public fun decodeCollectionSize (Lkotlinx/serialization/descriptors/SerialDescriptor;)I - public fun decodeDouble ()D - public fun decodeElementIndex (Lkotlinx/serialization/descriptors/SerialDescriptor;)I - public fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I - public fun decodeFloat ()F - public fun decodeInt ()I - public fun decodeLong ()J - public fun decodeNotNullMark ()Z - public fun decodeSequentially ()Z - public fun decodeValue ()Ljava/lang/Object; - public final fun getElementsCount ()I - public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; - public final fun setElementsCount (I)V -} - diff --git a/links/api/jvm/links.api b/links/api/jvm/links.api index c8df5d58f..55cac19ae 100644 --- a/links/api/jvm/links.api +++ b/links/api/jvm/links.api @@ -1,72 +1,32 @@ -public final class com/splendo/kaluga/links/Links { - public fun (Lcom/splendo/kaluga/links/manager/LinksManager$Builder;)V - public final fun handleIncomingLink (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; - public final fun validateLink (Ljava/lang/String;)Ljava/lang/String; -} - -public abstract interface class com/splendo/kaluga/links/Links$Builder { - public abstract fun create ()Lcom/splendo/kaluga/links/Links; -} - -public final class com/splendo/kaluga/links/LinksBuilder : com/splendo/kaluga/links/Links$Builder { - public fun ()V - public fun create ()Lcom/splendo/kaluga/links/Links; -} - -public final class com/splendo/kaluga/links/manager/DefaultLinksManager : com/splendo/kaluga/links/manager/LinksManager { - public fun (Lcom/splendo/kaluga/links/manager/LinksHandler;)V +public final class com/splendo/kaluga/links/DefaultLinksManager : com/splendo/kaluga/links/LinksManager { + public fun (Lcom/splendo/kaluga/links/handler/LinksHandler;)V public fun handleIncomingLink (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; public fun validateLink (Ljava/lang/String;)Ljava/lang/String; } -public abstract interface class com/splendo/kaluga/links/manager/LinksHandler { - public abstract fun extractQueryAsList (Ljava/lang/String;)Ljava/util/List; - public abstract fun isValid (Ljava/lang/String;)Z +public final class com/splendo/kaluga/links/DefaultLinksManager$Builder : com/splendo/kaluga/links/LinksManager$Builder { + public fun ()V + public fun (Lcom/splendo/kaluga/links/handler/LinksHandler;)V + public fun create ()Lcom/splendo/kaluga/links/LinksManager; } -public abstract interface class com/splendo/kaluga/links/manager/LinksManager { +public abstract interface class com/splendo/kaluga/links/LinksManager { public abstract fun handleIncomingLink (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Ljava/lang/Object; public abstract fun validateLink (Ljava/lang/String;)Ljava/lang/String; } -public abstract interface class com/splendo/kaluga/links/manager/LinksManager$Builder { - public abstract fun create ()Lcom/splendo/kaluga/links/manager/LinksManager; +public abstract interface class com/splendo/kaluga/links/LinksManager$Builder { + public abstract fun create ()Lcom/splendo/kaluga/links/LinksManager; } -public final class com/splendo/kaluga/links/manager/LinksManagerBuilder : com/splendo/kaluga/links/manager/LinksManager$Builder { - public fun ()V - public fun create ()Lcom/splendo/kaluga/links/manager/LinksManager; +public abstract interface class com/splendo/kaluga/links/handler/LinksHandler { + public abstract fun extractQueryAsList (Ljava/lang/String;)Ljava/util/List; + public abstract fun isValid (Ljava/lang/String;)Z } -public final class com/splendo/kaluga/links/manager/PlatformLinksHandler : com/splendo/kaluga/links/manager/LinksHandler { +public final class com/splendo/kaluga/links/handler/PlatformLinksHandler : com/splendo/kaluga/links/handler/LinksHandler { public fun ()V public fun extractQueryAsList (Ljava/lang/String;)Ljava/util/List; public fun isValid (Ljava/lang/String;)Z } -public final class com/splendo/kaluga/links/utils/DecodersKt { - public static final fun decodeFromList (Ljava/util/List;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; -} - -public final class com/splendo/kaluga/links/utils/LinksDecoder : kotlinx/serialization/encoding/AbstractDecoder { - public fun (Lkotlin/collections/ArrayDeque;I)V - public synthetic fun (Lkotlin/collections/ArrayDeque;IILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun beginStructure (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/CompositeDecoder; - public fun decodeBoolean ()Z - public fun decodeByte ()B - public fun decodeChar ()C - public fun decodeCollectionSize (Lkotlinx/serialization/descriptors/SerialDescriptor;)I - public fun decodeDouble ()D - public fun decodeElementIndex (Lkotlinx/serialization/descriptors/SerialDescriptor;)I - public fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I - public fun decodeFloat ()F - public fun decodeInt ()I - public fun decodeLong ()J - public fun decodeNotNullMark ()Z - public fun decodeSequentially ()Z - public fun decodeValue ()Ljava/lang/Object; - public final fun getElementsCount ()I - public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; - public final fun setElementsCount (I)V -} - diff --git a/links/build.gradle.kts b/links/build.gradle.kts index ab346a126..bffd8ee3a 100644 --- a/links/build.gradle.kts +++ b/links/build.gradle.kts @@ -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. @@ -21,25 +21,24 @@ plugins { id("jacoco") id("convention.publication") id("com.android.library") + id("org.jetbrains.dokka") 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"]!! +publishableComponent() kotlin { sourceSets { + sourceSets.all { + languageSettings { + optIn("kotlinx.serialization.ExperimentalSerializationApi") + } + } + 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"]}") + apiDependency(Dependencies.KotlinX.Serialization.Core) } } commonTest { diff --git a/links/src/androidLibAndroidTest/kotlin/com/splendo/kaluga/links/AndroidLinksHandlerTest.kt b/links/src/androidLibAndroidTest/kotlin/com/splendo/kaluga/links/AndroidLinksHandlerTest.kt deleted file mode 100644 index 9385c3acb..000000000 --- a/links/src/androidLibAndroidTest/kotlin/com/splendo/kaluga/links/AndroidLinksHandlerTest.kt +++ /dev/null @@ -1,59 +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. - - */ - -package com.splendo.kaluga.links - -import com.splendo.kaluga.links.manager.PlatformLinksHandler -import com.splendo.kaluga.links.manager.TestConstants.INVALID_URLS -import com.splendo.kaluga.links.manager.TestConstants.VALID_URLS -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class AndroidLinksHandlerTest { - - private val linksValidator = PlatformLinksHandler() - - @Test - fun testLinksValidatorSucceed() { - VALID_URLS.forEach { - assertTrue { linksValidator.isValid(it) } - } - } - - @Test - fun testLinksValidatorFailed() { - INVALID_URLS.forEach { - assertFalse { linksValidator.isValid(it) } - } - } - - @Test - fun testQueryExtractorSucceed() { - val url = "https://test.io?list_1=first&list_2=second&list_3=third" - - assertEquals(listOf("first", "second", "third"), linksValidator.extractQueryAsList(url)) - } - - @Test - fun testQueryExtractorEmptyQuery() { - val url = "https://test.io" - - assertEquals(emptyList(), linksValidator.extractQueryAsList(url)) - } -} diff --git a/links/src/androidLibAndroidTest/kotlin/com/splendo/kaluga/links/manager/LinksManagerTest.kt b/links/src/androidLibAndroidTest/kotlin/com/splendo/kaluga/links/manager/LinksManagerTest.kt deleted file mode 100644 index da10937f5..000000000 --- a/links/src/androidLibAndroidTest/kotlin/com/splendo/kaluga/links/manager/LinksManagerTest.kt +++ /dev/null @@ -1,57 +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.links.manager - -import org.junit.Test -import kotlin.test.assertEquals - -class LinksManagerTestAndroid { - - private val linksManager = LinksManagerBuilder().create() - - @Test - fun testHandleIncomingLinkSucceed() { - val result = linksManager.handleIncomingLink(Person.dummyUrl, Person.serializer()) - - assertEquals(Person.dummyPerson, result) - } - - @Test - fun testHandleIncomingLinkFailed() { - val query = "" - - val result = linksManager.handleIncomingLink(query, Person.serializer()) - assertEquals(null, result) - } - - @Test - fun testHandleOutgoingLinkSucceed() { - val url = "https://valid-link?parameter=1" - - val result = linksManager.validateLink(url) - assertEquals(url, result) - } - - @Test - fun testHandleOutgoingLinkFailed() { - val url = "not valid" - - val result = linksManager.validateLink(url) - assertEquals(null, result) - } -} diff --git a/links/src/androidLibInstrumentedTest/AndroidManifest.xml b/links/src/androidLibInstrumentedTest/AndroidManifest.xml new file mode 100644 index 000000000..f0a3ba6a3 --- /dev/null +++ b/links/src/androidLibInstrumentedTest/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/example/shared/src/commonMain/kotlin/HelloShared.kt b/links/src/androidLibInstrumentedTest/kotlin/TestActivity.kt similarity index 75% rename from example/shared/src/commonMain/kotlin/HelloShared.kt rename to links/src/androidLibInstrumentedTest/kotlin/TestActivity.kt index 03656d76a..a4ccd1ab8 100644 --- a/example/shared/src/commonMain/kotlin/HelloShared.kt +++ b/links/src/androidLibInstrumentedTest/kotlin/TestActivity.kt @@ -1,6 +1,6 @@ /* -Copyright 2019 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. @@ -16,8 +16,8 @@ Copyright 2019 Splendo Consulting B.V. The Netherlands */ -package com.splendo.kaluga.example.shared +package com.splendo.kaluga.links -fun helloCommon(): String { - return "Hello from the shared module common source" -} +import androidx.appcompat.app.AppCompatActivity + +class TestActivity : AppCompatActivity() diff --git a/keyboard/src/jsMain/kotlin/FocusHandler.kt b/links/src/androidLibInstrumentedTest/kotlin/handler/AndroidLinksHandlerTest.kt similarity index 76% rename from keyboard/src/jsMain/kotlin/FocusHandler.kt rename to links/src/androidLibInstrumentedTest/kotlin/handler/AndroidLinksHandlerTest.kt index 584599b79..624dbccc5 100644 --- a/keyboard/src/jsMain/kotlin/FocusHandler.kt +++ b/links/src/androidLibInstrumentedTest/kotlin/handler/AndroidLinksHandlerTest.kt @@ -1,5 +1,5 @@ /* - Copyright 2021 Splendo Consulting B.V. The Netherlands + Copyright 2023 Splendo Consulting B.V. The Netherlands Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,6 +15,6 @@ */ -package com.splendo.kaluga.keyboard +package com.splendo.kaluga.links.handler -actual interface FocusHandler +class AndroidLinksHandlerTest : PlatformLinksHandlerTest(PlatformLinksHandler()) diff --git a/links/src/androidLibMain/AndroidManifest.xml b/links/src/androidLibMain/AndroidManifest.xml index 68749ee8f..a49b23659 100644 --- a/links/src/androidLibMain/AndroidManifest.xml +++ b/links/src/androidLibMain/AndroidManifest.xml @@ -1,5 +1,5 @@