diff --git a/.circleci/config.yml b/.circleci/config.yml index 16b634c7..73ff6a5e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ jobs: build: working_directory: ~/code docker: - - image: cimg/android:2023.07 + - image: cimg/android:2025.04 environment: JVM_OPTS: -Xmx3200m GRADLE_OPTS: -Dorg.gradle.daemon=true @@ -12,7 +12,7 @@ jobs: - checkout - restore_cache: # bump the cache version number if you want to wipe the gradle cache - key: v6-gradle-dependencies-cache + key: v7-gradle-dependencies-cache # - run: # name: Chmod permissions #if permission for Gradlew Dependencies fail, use this. # command: sudo chmod +x ./gradlew @@ -22,116 +22,77 @@ jobs: - save_cache: paths: - ~/.gradle - key: v6-gradle-dependencies-cache + key: v7-gradle-dependencies-cache - run: - name: Run Tests fore-jv-android - command: ./gradlew fore-jv:fore-jv-android:lintRelease fore-jv:fore-jv-android:testRelease + name: Run Tests fore-compose + command: ./gradlew lib:fore-compose:lintRelease lib:fore-compose:testRelease - store_artifacts: - path: fore-jv/fore-jv-android/build/reports + path: lib/fore-compose/build/reports - store_test_results: - path: fore-jv/fore-jv-android/build/test-results + path: lib/fore-compose/build/test-results - run: - name: Run Tests fore-kt-android - command: ./gradlew fore-kt:fore-kt-android:lintRelease fore-kt:fore-kt-android:testRelease + name: Run Tests fore-core + command: ./gradlew lib:fore-core:lintRelease lib:fore-core:testRelease - store_artifacts: - path: fore-kt/fore-kt-android/build/reports + path: lib/fore-core/build/reports - store_test_results: - path: fore-kt/fore-kt-android/build/test-results + path: lib/fore-core/build/test-results - run: - name: Run Tests example-jv-01reactiveui - command: ./gradlew app-examples:example-jv-01reactiveui:lintRelease app-examples:example-jv-01reactiveui:testRelease + name: Run Tests fore-net + command: ./gradlew lib:fore-net:lintRelease lib:fore-net:testRelease - store_artifacts: - path: app-examples/example-jv-01reactiveui/build/reports + path: lib/fore-net/build/reports - store_test_results: - path: app-examples/example-jv-01reactiveui/build/test-results + path: lib/fore-net/build/test-results - run: - name: Run Tests example-jv-02threading - command: ./gradlew app-examples:example-jv-02threading:lintRelease app-examples:example-jv-02threading:testRelease + name: Run Tests example-01-reactiveui + command: ./gradlew app-examples:example-01-reactiveui:lintRelease app-examples:example-01-reactiveui:testRelease - store_artifacts: - path: app-examples/example-jv-02threading/build/reports + path: app-examples/example-01-reactiveui/build/reports - store_test_results: - path: app-examples/example-jv-02threading/build/test-results + path: app-examples/example-01-reactiveui/build/test-results - run: - name: Run Tests example-jv-03adapters - command: ./gradlew app-examples:example-jv-03adapters:lintRelease app-examples:example-jv-03adapters:testRelease + name: Run Tests example-02-coroutine + command: ./gradlew app-examples:example-02-coroutine:lintRelease app-examples:example-02-coroutine:testRelease - store_artifacts: - path: app-examples/example-jv-03adapters/build/reports + path: app-examples/example-02-coroutine/build/reports - store_test_results: - path: app-examples/example-jv-03adapters/build/test-results + path: app-examples/example-02-coroutine/build/test-results - run: - name: Run Tests example-jv-04retrofit - command: ./gradlew app-examples:example-jv-04retrofit:lintRelease app-examples:example-jv-04retrofit:testRelease + name: Run Tests example-03-ktor + command: ./gradlew app-examples:example-03-ktor:lintRelease app-examples:example-03-ktor:testRelease - store_artifacts: - path: app-examples/example-jv-04retrofit/build/reports + path: app-examples/example-03-ktor/build/reports - store_test_results: - path: app-examples/example-jv-04retrofit/build/test-results + path: app-examples/example-03-ktor/build/test-results - run: - name: Run Tests example-jv-06db - command: ./gradlew app-examples:example-jv-06db:lintRelease app-examples:example-jv-06db:testRelease + name: Run Tests example-04-apollo + command: ./gradlew app-examples:example-04-apollo:lintRelease app-examples:example-04-apollo:testRelease - store_artifacts: - path: app-examples/example-jv-06db/build/reports + path: app-examples/example-04-apollo/build/reports - store_test_results: - path: app-examples/example-jv-06db/build/test-results + path: app-examples/example-04-apollo/build/test-results - - run: - name: Run Tests example-kt-01reactiveui - command: ./gradlew app-examples:example-kt-01reactiveui:lintRelease app-examples:example-kt-01reactiveui:testRelease - - store_artifacts: - path: app-examples/example-kt-01reactiveui/build/reports - - store_test_results: - path: app-examples/example-kt-01reactiveui/build/test-results - - - run: - name: Run Tests example-kt-02coroutine - command: ./gradlew app-examples:example-kt-02coroutine:lintRelease app-examples:example-kt-02coroutine:testRelease - - store_artifacts: - path: app-examples/example-kt-02coroutine/build/reports - - store_test_results: - path: app-examples/example-kt-02coroutine/build/test-results - - - run: - name: Run Tests example-kt-03adapters - command: ./gradlew app-examples:example-kt-03adapters:lintRelease app-examples:example-kt-03adapters:testRelease - - store_artifacts: - path: app-examples/example-kt-03adapters/build/reports - - store_test_results: - path: app-examples/example-kt-03adapters/build/test-results - - - run: - name: Run Tests example-kt-04retrofit - command: ./gradlew app-examples:example-kt-04retrofit:lintRelease app-examples:example-kt-04retrofit:testRelease - - store_artifacts: - path: app-examples/example-kt-04retrofit/build/reports - - store_test_results: - path: app-examples/example-kt-04retrofit/build/test-results - - - run: - name: Run Tests example-kt-07apollo3 - command: ./gradlew app-examples:example-kt-07apollo3:lintRelease app-examples:example-kt-07apollo3:testRelease - - store_artifacts: - path: app-examples/example-kt-07apollo3/build/reports - - store_test_results: - path: app-examples/example-kt-07apollo3/build/test-results +# - run: +# name: Run Tests example-05-db +# command: ./gradlew app-examples:example-05-db:lintRelease app-examples:example-05-db:testRelease +# - store_artifacts: +# path: app-examples/example-05-db/build/reports +# - store_test_results: +# path: app-examples/example-05-db/build/test-results - run: - name: Run Tests example-kt-08ktor - command: ./gradlew app-examples:example-kt-08ktor:lintRelease app-examples:example-kt-08ktor:testRelease + name: Run Tests example-06-compose + command: ./gradlew app-examples:example-06-compose:lintRelease app-examples:example-06-compose:testRelease - store_artifacts: - path: app-examples/example-kt-08ktor/build/reports + path: app-examples/example-06-compose/build/reports - store_test_results: - path: app-examples/example-kt-08ktor/build/test-results + path: app-examples/example-06-compose/build/test-results - - run: - name: Run Tests example-kt-09compose - command: ./gradlew app-examples:example-kt-09compose:lintRelease app-examples:example-kt-09compose:testRelease - - store_artifacts: - path: app-examples/example-kt-09compose/build/reports - - store_test_results: - path: app-examples/example-kt-09compose/build/test-results \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3bf8bd1a..386bb539 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,8 @@ secret_key_ring.gpg # passwords secrets.properties + +# Ignore Kotlin/Native metadata and build artifacts +.kotlin/ + +**/iosApp/Frameworks/ diff --git a/app-examples/example-kt-01reactiveui/build.gradle.kts b/app-examples/example-01-reactiveui/build.gradle.kts similarity index 87% rename from app-examples/example-kt-01reactiveui/build.gradle.kts rename to app-examples/example-01-reactiveui/build.gradle.kts index 3b8db2e2..f92552bb 100644 --- a/app-examples/example-kt-01reactiveui/build.gradle.kts +++ b/app-examples/example-01-reactiveui/build.gradle.kts @@ -2,9 +2,9 @@ import co.early.fore.Shared import co.early.fore.Shared.BuildTypes plugins { - alias(libs.plugins.androidApplication) - alias(libs.plugins.kotlinAndroid) - kotlin("kapt") + alias(libs.plugins.androidAppPlugin) + alias(libs.plugins.kotlinAndroidPlugin) + alias(libs.plugins.kotlinKaptPlugin) } @@ -18,7 +18,7 @@ println("[$appId testBuildType:${getTestBuildType()}]") kotlin { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(Shared.Versions.jvm_toolchain)) + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvm.toolchain.get().toInt())) } } @@ -68,9 +68,11 @@ android { dependencies { if (Shared.Publish.use_published_version) { - implementation("co.early.fore:fore-kt-android-core:${Shared.Publish.published_fore_version_for_examples}") + implementation(libs.fore.core) + testImplementation(libs.fore.test.fixtures) } else { - implementation(project(":fore-kt:fore-kt-android-core")) + implementation(project(":lib:fore-core")) + testImplementation(project(":lib:fore-test-fixtures")) } implementation("androidx.appcompat:appcompat:${Shared.Versions.appcompat}") diff --git a/app-examples/example-jv-01reactiveui/screenshot.png b/app-examples/example-01-reactiveui/screenshot.png similarity index 100% rename from app-examples/example-jv-01reactiveui/screenshot.png rename to app-examples/example-01-reactiveui/screenshot.png diff --git a/app-examples/example-kt-01reactiveui/src/main/AndroidManifest.xml b/app-examples/example-01-reactiveui/src/main/AndroidManifest.xml similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/AndroidManifest.xml rename to app-examples/example-01-reactiveui/src/main/AndroidManifest.xml diff --git a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/App.kt b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/App.kt similarity index 82% rename from app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/App.kt rename to app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/App.kt index 2767c945..005dcf79 100644 --- a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/App.kt +++ b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/App.kt @@ -1,8 +1,8 @@ package foo.bar.example.forereactiveuikt import android.app.Application -import co.early.fore.kt.core.delegate.DebugDelegateDefault -import co.early.fore.kt.core.delegate.Fore +import co.early.fore.core.delegate.DebugDelegateDefault +import co.early.fore.core.delegate.Fore /** * Copyright © 2015-2020 early.co. All rights reserved. diff --git a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/OG.kt b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/OG.kt similarity index 94% rename from app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/OG.kt rename to app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/OG.kt index 0b214837..e87287a8 100644 --- a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/OG.kt +++ b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/OG.kt @@ -1,7 +1,8 @@ package foo.bar.example.forereactiveuikt import android.app.Application -import co.early.fore.kt.core.logging.AndroidLogger +import co.early.fore.core.logging.AndroidLogger +//import co.early.fore.core.logging.AndroidLogger import foo.bar.example.forereactiveuikt.feature.wallet.Wallet import java.util.HashMap diff --git a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/feature/wallet/Wallet.kt b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/feature/wallet/Wallet.kt similarity index 92% rename from app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/feature/wallet/Wallet.kt rename to app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/feature/wallet/Wallet.kt index 82272371..d2055384 100644 --- a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/feature/wallet/Wallet.kt +++ b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/feature/wallet/Wallet.kt @@ -1,8 +1,8 @@ package foo.bar.example.forereactiveuikt.feature.wallet -import co.early.fore.kt.core.logging.Logger +import co.early.fore.core.logging.Logger import co.early.fore.core.observer.Observable -import co.early.fore.kt.core.observer.ObservableImp +import co.early.fore.core.observer.ObservableImp /** * Copyright © 2015-2020 early.co. All rights reserved. diff --git a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/ui/wallet/WalletsActivity.kt b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/ui/wallet/WalletsActivity.kt similarity index 67% rename from app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/ui/wallet/WalletsActivity.kt rename to app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/ui/wallet/WalletsActivity.kt index c8c11661..fffe6ff5 100644 --- a/app-examples/example-kt-01reactiveui/src/main/java/foo/bar/example/forereactiveuikt/ui/wallet/WalletsActivity.kt +++ b/app-examples/example-01-reactiveui/src/main/java/foo/bar/example/forereactiveuikt/ui/wallet/WalletsActivity.kt @@ -2,8 +2,13 @@ package foo.bar.example.forereactiveuikt.ui.wallet import android.os.Bundle import androidx.fragment.app.FragmentActivity +import co.early.fore.core.delegate.Fore +/** + * example 1/3 without using the LifecycleObserver + */ +//import co.early.fore.core.observer.Observer import co.early.fore.core.ui.SyncableView -import co.early.fore.kt.core.ui.LifecycleObserver +import co.early.fore.core.ui.ForeLifecycleObserver import foo.bar.example.forereactiveuikt.OG import foo.bar.example.forereactiveuikt.databinding.ActivityWalletBinding import foo.bar.example.forereactiveuikt.feature.wallet.Wallet @@ -18,13 +23,20 @@ class WalletsActivity : FragmentActivity(), SyncableView { private lateinit var binding: ActivityWalletBinding + /** + * example 2/3 without using the LifecycleObserver + */ +// var observer: Observer = Observer { syncView() } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Fore.i("onCreate()") + binding = ActivityWalletBinding.inflate(layoutInflater) setContentView(binding.root) - lifecycle.addObserver(LifecycleObserver(this, wallet)) + lifecycle.addObserver(ForeLifecycleObserver(this, wallet)) setupButtonClickListeners() } @@ -39,9 +51,25 @@ class WalletsActivity : FragmentActivity(), SyncableView { } override fun syncView() { + + Fore.i("syncView()") + binding.walletIncreaseBtn.isEnabled = wallet.canIncrease() binding.walletDecreaseBtn.isEnabled = wallet.canDecrease() binding.walletMobileamountTxt.text = wallet.mobileWalletAmount.toString() binding.walletSavingsamountTxt.text = wallet.savingsWalletAmount.toString() } + + /** + * example 3/3 without using the LifecycleObserver + */ +// override fun onStart() { +// super.onStart() +// wallet.addObserver(observer) +// syncView() // <- don't forget this +// } +// override fun onStop() { +// super.onStop() +// wallet.removeObserver(observer) +// } } diff --git a/app-examples/example-jv-01reactiveui/src/main/res/drawable-xxhdpi/moneybag.png b/app-examples/example-01-reactiveui/src/main/res/drawable-xxhdpi/moneybag.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/drawable-xxhdpi/moneybag.png rename to app-examples/example-01-reactiveui/src/main/res/drawable-xxhdpi/moneybag.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/drawable-xxhdpi/phone.png b/app-examples/example-01-reactiveui/src/main/res/drawable-xxhdpi/phone.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/drawable-xxhdpi/phone.png rename to app-examples/example-01-reactiveui/src/main/res/drawable-xxhdpi/phone.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/layout/activity_wallet.xml b/app-examples/example-01-reactiveui/src/main/res/layout/activity_wallet.xml similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/layout/activity_wallet.xml rename to app-examples/example-01-reactiveui/src/main/res/layout/activity_wallet.xml diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app-examples/example-01-reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app-examples/example-01-reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app-examples/example-01-reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app-examples/example-01-reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-hdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_background.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-hdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-mdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_background.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-mdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_background.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_background.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app-examples/example-01-reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png rename to app-examples/example-01-reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-01reactiveui/src/main/res/values/colors.xml b/app-examples/example-01-reactiveui/src/main/res/values/colors.xml similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/values/colors.xml rename to app-examples/example-01-reactiveui/src/main/res/values/colors.xml diff --git a/app-examples/example-kt-01reactiveui/src/main/res/values/strings.xml b/app-examples/example-01-reactiveui/src/main/res/values/strings.xml similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/values/strings.xml rename to app-examples/example-01-reactiveui/src/main/res/values/strings.xml diff --git a/app-examples/example-jv-01reactiveui/src/main/res/values/styles.xml b/app-examples/example-01-reactiveui/src/main/res/values/styles.xml similarity index 100% rename from app-examples/example-jv-01reactiveui/src/main/res/values/styles.xml rename to app-examples/example-01-reactiveui/src/main/res/values/styles.xml diff --git a/app-examples/example-kt-01reactiveui/src/test/java/foo/bar/example/forereactiveuikt/feature/wallet/WalletTest.kt b/app-examples/example-01-reactiveui/src/test/java/foo/bar/example/forereactiveuikt/feature/wallet/WalletTest.kt similarity index 95% rename from app-examples/example-kt-01reactiveui/src/test/java/foo/bar/example/forereactiveuikt/feature/wallet/WalletTest.kt rename to app-examples/example-01-reactiveui/src/test/java/foo/bar/example/forereactiveuikt/feature/wallet/WalletTest.kt index fd717435..581811d3 100644 --- a/app-examples/example-kt-01reactiveui/src/test/java/foo/bar/example/forereactiveuikt/feature/wallet/WalletTest.kt +++ b/app-examples/example-01-reactiveui/src/test/java/foo/bar/example/forereactiveuikt/feature/wallet/WalletTest.kt @@ -1,10 +1,10 @@ package foo.bar.example.forereactiveuikt.feature.wallet -import co.early.fore.kt.core.logging.Logger -import co.early.fore.kt.core.logging.SystemLogger +import co.early.fore.core.logging.Logger +import co.early.fore.core.logging.SystemLogger import co.early.fore.core.observer.Observer -import co.early.fore.kt.core.delegate.TestDelegateDefault -import co.early.fore.kt.core.delegate.Fore +import co.early.fore.core.delegate.TestDelegateDefault +import co.early.fore.core.delegate.Fore import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK import io.mockk.verify diff --git a/app-examples/example-kt-02coroutine/build.gradle.kts b/app-examples/example-02-coroutine/build.gradle.kts similarity index 85% rename from app-examples/example-kt-02coroutine/build.gradle.kts rename to app-examples/example-02-coroutine/build.gradle.kts index 89203dff..ed0e50ef 100644 --- a/app-examples/example-kt-02coroutine/build.gradle.kts +++ b/app-examples/example-02-coroutine/build.gradle.kts @@ -2,9 +2,9 @@ import co.early.fore.Shared import co.early.fore.Shared.BuildTypes plugins { - alias(libs.plugins.androidApplication) - alias(libs.plugins.kotlinAndroid) - kotlin("kapt") + alias(libs.plugins.androidAppPlugin) + alias(libs.plugins.kotlinAndroidPlugin) + alias(libs.plugins.kotlinKaptPlugin) } val appId = "foo.bar.example.forecoroutine" @@ -17,7 +17,7 @@ println("[$appId testBuildType:${getTestBuildType()}]") kotlin { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(Shared.Versions.jvm_toolchain)) + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvm.toolchain.get().toInt())) } } @@ -67,13 +67,14 @@ android { dependencies { if (Shared.Publish.use_published_version) { - implementation("co.early.fore:fore-kt-android-core:${Shared.Publish.published_fore_version_for_examples}") + implementation(libs.fore.core) + testImplementation(libs.fore.test.fixtures) } else { - implementation(project(":fore-kt:fore-kt-android-core")) + implementation(project(":lib:fore-core")) + testImplementation(project(":lib:fore-test-fixtures")) } implementation("androidx.appcompat:appcompat:${Shared.Versions.appcompat}") - implementation("androidx.constraintlayout:constraintlayout:${Shared.Versions.constraintlayout}") testImplementation("junit:junit:${Shared.Versions.junit}") testImplementation("io.mockk:mockk:${Shared.Versions.mockk}") diff --git a/app-examples/example-jv-02threading/screenshot.png b/app-examples/example-02-coroutine/screenshot.png similarity index 100% rename from app-examples/example-jv-02threading/screenshot.png rename to app-examples/example-02-coroutine/screenshot.png diff --git a/app-examples/example-jv-02threading/src/main/AndroidManifest.xml b/app-examples/example-02-coroutine/src/main/AndroidManifest.xml similarity index 100% rename from app-examples/example-jv-02threading/src/main/AndroidManifest.xml rename to app-examples/example-02-coroutine/src/main/AndroidManifest.xml diff --git a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/App.kt b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/App.kt similarity index 100% rename from app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/App.kt rename to app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/App.kt diff --git a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/OG.kt b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/OG.kt similarity index 95% rename from app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/OG.kt rename to app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/OG.kt index bdcb64ae..9c2cb021 100644 --- a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/OG.kt +++ b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/OG.kt @@ -1,8 +1,7 @@ package foo.bar.example.forecoroutine import android.app.Application -import co.early.fore.core.WorkMode -import co.early.fore.kt.core.logging.AndroidLogger +import co.early.fore.core.logging.AndroidLogger import foo.bar.example.forecoroutine.feature.counter.Counter import foo.bar.example.forecoroutine.feature.counter.CounterWithProgress import java.util.HashMap diff --git a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/Counter.kt b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/Counter.kt similarity index 85% rename from app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/Counter.kt rename to app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/Counter.kt index d9a76a6a..cf9a9da6 100644 --- a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/Counter.kt +++ b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/Counter.kt @@ -2,12 +2,12 @@ package foo.bar.example.forecoroutine.feature.counter import co.early.fore.core.WorkMode -import co.early.fore.kt.core.logging.Logger +import co.early.fore.core.logging.Logger import co.early.fore.core.observer.Observable -import co.early.fore.kt.core.coroutine.awaitDefault -import co.early.fore.kt.core.coroutine.launchMain -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.core.observer.ObservableImp +import co.early.fore.core.coroutine.awaitDefault +import co.early.fore.core.coroutine.launchMain +import co.early.fore.core.delegate.Fore +import co.early.fore.core.observer.ObservableImp import kotlinx.coroutines.delay /** diff --git a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgress.kt b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgress.kt similarity index 90% rename from app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgress.kt rename to app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgress.kt index 7b963f32..667f9c41 100644 --- a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgress.kt +++ b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgress.kt @@ -2,11 +2,11 @@ package foo.bar.example.forecoroutine.feature.counter import co.early.fore.core.WorkMode -import co.early.fore.kt.core.logging.Logger +import co.early.fore.core.logging.Logger import co.early.fore.core.observer.Observable -import co.early.fore.kt.core.coroutine.* -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.core.observer.ObservableImp +import co.early.fore.core.coroutine.* +import co.early.fore.core.delegate.Fore +import co.early.fore.core.observer.ObservableImp import kotlinx.coroutines.delay /** diff --git a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/ui/CounterActivity.kt b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/ui/CounterActivity.kt similarity index 97% rename from app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/ui/CounterActivity.kt rename to app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/ui/CounterActivity.kt index be118217..a0247551 100644 --- a/app-examples/example-kt-02coroutine/src/main/java/foo/bar/example/forecoroutine/ui/CounterActivity.kt +++ b/app-examples/example-02-coroutine/src/main/java/foo/bar/example/forecoroutine/ui/CounterActivity.kt @@ -4,7 +4,7 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.FragmentActivity import co.early.fore.core.ui.SyncableView -import co.early.fore.kt.core.ui.LifecycleObserver +import co.early.fore.core.ui.LifecycleObserver import foo.bar.example.forecoroutine.OG import foo.bar.example.forecoroutine.databinding.ActivityCounterBinding import foo.bar.example.forecoroutine.feature.counter.Counter diff --git a/app-examples/example-kt-02coroutine/src/main/res/layout/activity_counter.xml b/app-examples/example-02-coroutine/src/main/res/layout/activity_counter.xml similarity index 100% rename from app-examples/example-kt-02coroutine/src/main/res/layout/activity_counter.xml rename to app-examples/example-02-coroutine/src/main/res/layout/activity_counter.xml diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app-examples/example-02-coroutine/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app-examples/example-02-coroutine/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app-examples/example-02-coroutine/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app-examples/example-02-coroutine/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-hdpi/ic_launcher.png b/app-examples/example-02-coroutine/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-hdpi/ic_launcher.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app-examples/example-02-coroutine/src/main/res/mipmap-hdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-hdpi/ic_launcher_background.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-hdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app-examples/example-02-coroutine/src/main/res/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-hdpi/ic_launcher_foreground.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-hdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-mdpi/ic_launcher.png b/app-examples/example-02-coroutine/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-mdpi/ic_launcher.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app-examples/example-02-coroutine/src/main/res/mipmap-mdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-mdpi/ic_launcher_background.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-mdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app-examples/example-02-coroutine/src/main/res/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-mdpi/ic_launcher_foreground.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-mdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xhdpi/ic_launcher.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xhdpi/ic_launcher.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xhdpi/ic_launcher_background.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xxhdpi/ic_launcher_background.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-02threading/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app-examples/example-02-coroutine/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png rename to app-examples/example-02-coroutine/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-02threading/src/main/res/values/colors.xml b/app-examples/example-02-coroutine/src/main/res/values/colors.xml similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/values/colors.xml rename to app-examples/example-02-coroutine/src/main/res/values/colors.xml diff --git a/app-examples/example-kt-02coroutine/src/main/res/values/strings.xml b/app-examples/example-02-coroutine/src/main/res/values/strings.xml similarity index 100% rename from app-examples/example-kt-02coroutine/src/main/res/values/strings.xml rename to app-examples/example-02-coroutine/src/main/res/values/strings.xml diff --git a/app-examples/example-jv-02threading/src/main/res/values/styles.xml b/app-examples/example-02-coroutine/src/main/res/values/styles.xml similarity index 100% rename from app-examples/example-jv-02threading/src/main/res/values/styles.xml rename to app-examples/example-02-coroutine/src/main/res/values/styles.xml diff --git a/app-examples/example-kt-02coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterTest.kt b/app-examples/example-02-coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterTest.kt similarity index 93% rename from app-examples/example-kt-02coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterTest.kt rename to app-examples/example-02-coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterTest.kt index e32c54fb..f48c26df 100644 --- a/app-examples/example-kt-02coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterTest.kt +++ b/app-examples/example-02-coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterTest.kt @@ -1,9 +1,9 @@ package foo.bar.example.forecoroutine.feature.counter -import co.early.fore.kt.core.logging.SystemLogger +import co.early.fore.core.logging.SystemLogger import co.early.fore.core.observer.Observer -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.core.delegate.TestDelegateDefault +import co.early.fore.core.delegate.Fore +import co.early.fore.core.delegate.TestDelegateDefault import io.mockk.MockKAnnotations import io.mockk.mockk import io.mockk.verify diff --git a/app-examples/example-kt-02coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgressTest.kt b/app-examples/example-02-coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgressTest.kt similarity index 96% rename from app-examples/example-kt-02coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgressTest.kt rename to app-examples/example-02-coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgressTest.kt index b4025c7a..9ce40c81 100644 --- a/app-examples/example-kt-02coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgressTest.kt +++ b/app-examples/example-02-coroutine/src/test/java/foo/bar/example/forecoroutine/feature/counter/CounterWithProgressTest.kt @@ -1,10 +1,10 @@ package foo.bar.example.forecoroutine.feature.counter import co.early.fore.core.WorkMode -import co.early.fore.kt.core.logging.SystemLogger +import co.early.fore.core.logging.SystemLogger import co.early.fore.core.observer.Observer -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.core.delegate.TestDelegateDefault +import co.early.fore.core.delegate.Fore +import co.early.fore.core.delegate.TestDelegateDefault import io.mockk.mockk import io.mockk.verify import org.junit.Assert diff --git a/app-examples/example-kt-08ktor/build.gradle.kts b/app-examples/example-03-ktor/build.gradle.kts similarity index 77% rename from app-examples/example-kt-08ktor/build.gradle.kts rename to app-examples/example-03-ktor/build.gradle.kts index 600de1be..941ee6cc 100644 --- a/app-examples/example-kt-08ktor/build.gradle.kts +++ b/app-examples/example-03-ktor/build.gradle.kts @@ -2,10 +2,10 @@ import co.early.fore.Shared import co.early.fore.Shared.BuildTypes plugins { - alias(libs.plugins.androidApplication) - alias(libs.plugins.kotlinAndroid) - alias(libs.plugins.kotlinSerialization) - kotlin("kapt") + alias(libs.plugins.androidAppPlugin) + alias(libs.plugins.kotlinAndroidPlugin) + alias(libs.plugins.kotlinSerializationPlugin) + alias(libs.plugins.kotlinKaptPlugin) } @@ -19,7 +19,7 @@ println("[$appId testBuildType:${getTestBuildType()}]") kotlin { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(Shared.Versions.jvm_toolchain)) + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvm.toolchain.get().toInt())) } } @@ -70,11 +70,18 @@ android { dependencies { if (Shared.Publish.use_published_version) { - implementation("co.early.fore:fore-kt-android:${Shared.Publish.published_fore_version_for_examples}") + implementation(libs.fore.core) + implementation(libs.fore.net) + testImplementation(libs.fore.test.fixtures) } else { - implementation(project(":fore-kt:fore-kt-android")) + implementation(project(":lib:fore-core")) + implementation(project(":lib:fore-net")) + testImplementation(project(":lib:fore-test-fixtures")) } + implementation("io.ktor:ktor-client-mock:${Shared.Versions.ktor_client}") + implementation("io.ktor:ktor-client-cio:${Shared.Versions.ktor_client}") + implementation("io.ktor:ktor-client-logging:${Shared.Versions.ktor_client}") implementation("io.ktor:ktor-client-okhttp:${Shared.Versions.ktor_client}") implementation("io.ktor:ktor-serialization-kotlinx-json:${Shared.Versions.ktor_client}") implementation("io.ktor:ktor-client-content-negotiation:${Shared.Versions.ktor_client}") @@ -82,6 +89,8 @@ dependencies { implementation("androidx.appcompat:appcompat:${Shared.Versions.appcompat}") implementation("androidx.constraintlayout:constraintlayout:${Shared.Versions.constraintlayout}") + implementation("org.slf4j:slf4j-nop:2.0.7") /// to get rid of the slf4 warning that comes from ktor + testImplementation("junit:junit:${Shared.Versions.junit}") testImplementation("io.mockk:mockk:${Shared.Versions.mockk}") diff --git a/app-examples/example-kt-08ktor/proguard-example-kt-08ktor.pro b/app-examples/example-03-ktor/proguard-example-kt-08ktor.pro similarity index 100% rename from app-examples/example-kt-08ktor/proguard-example-kt-08ktor.pro rename to app-examples/example-03-ktor/proguard-example-kt-08ktor.pro diff --git a/app-examples/example-kt-08ktor/screenshot.png b/app-examples/example-03-ktor/screenshot.png similarity index 100% rename from app-examples/example-kt-08ktor/screenshot.png rename to app-examples/example-03-ktor/screenshot.png diff --git a/app-examples/example-kt-08ktor/src/main/AndroidManifest.xml b/app-examples/example-03-ktor/src/main/AndroidManifest.xml similarity index 100% rename from app-examples/example-kt-08ktor/src/main/AndroidManifest.xml rename to app-examples/example-03-ktor/src/main/AndroidManifest.xml diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/App.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/App.kt similarity index 100% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/App.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/App.kt diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/OG.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/OG.kt similarity index 72% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/OG.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/OG.kt index b3afd249..01f16fc1 100644 --- a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/OG.kt +++ b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/OG.kt @@ -1,16 +1,13 @@ package foo.bar.example.forektorkt import android.app.Application -import co.early.fore.kt.core.delegate.DebugDelegateDefault -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.net.InterceptorLogging -import co.early.fore.kt.net.ktor.CallWrapperKtor -import foo.bar.example.forektorkt.api.CustomGlobalErrorHandler -import foo.bar.example.forektorkt.api.CustomGlobalRequestInterceptor -import foo.bar.example.forektorkt.api.CustomKtorBuilder +import co.early.fore.core.delegate.DebugDelegateDefault +import co.early.fore.core.delegate.Fore +import co.early.fore.net.wrap.CallWrapper +import foo.bar.example.forektorkt.api.GlobalErrorHandler +import foo.bar.example.forektorkt.api.KtorClientBuilder import foo.bar.example.forektorkt.api.fruits.FruitService import foo.bar.example.forektorkt.feature.fruit.FruitFetcher -import java.util.* import kotlin.collections.set /** @@ -29,17 +26,15 @@ object OG { // create dependency graph - if (BuildConfig.DEBUG) { Fore.setDelegate(DebugDelegateDefault("fore_")) } + if (BuildConfig.DEBUG) { + Fore.setDelegate(DebugDelegateDefault("fore_")) + } val logger = Fore.getLogger() // networking classes common to all models - val httpClient = CustomKtorBuilder.create( - CustomGlobalRequestInterceptor(logger), - InterceptorLogging(logger) - )//logging interceptor should be the last one - - val callWrapper = CallWrapperKtor( - errorHandler = CustomGlobalErrorHandler(logger), + val httpClient = KtorClientBuilder.create(logger) + val callWrapper = CallWrapper( + errorHandler = GlobalErrorHandler(logger), logger = logger ) diff --git a/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/GlobalErrorHandler.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/GlobalErrorHandler.kt new file mode 100644 index 00000000..1955867a --- /dev/null +++ b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/GlobalErrorHandler.kt @@ -0,0 +1,173 @@ +package foo.bar.example.forektorkt.api + +import co.early.fore.core.delegate.Fore +import co.early.fore.core.logging.Logger +import co.early.fore.net.MessageProvider +import co.early.fore.net.wrap.ErrorHandler +import foo.bar.example.forektorkt.message.ErrorMessage +import foo.bar.example.forektorkt.message.ErrorMessage.* +import io.ktor.client.call.* +import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.plugins.RedirectResponseException +import io.ktor.client.plugins.ResponseException +import io.ktor.client.plugins.ServerResponseException +import io.ktor.client.statement.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json + +/** + * KMP-compatible error handler for REST API calls. + * + * Something like this will be suitable for most HTTP services, + * consider customizing for the particularities of your own server side + * + * Retry logic is handled in KtorBuilder + */ +class GlobalErrorHandler(private val logWrapper: Logger? = null) : ErrorHandler { + + override suspend fun > handleError( + t: Throwable, + kSerializer: KSerializer?, + ): ErrorMessage { + + Fore.getLogger(logWrapper).e("handling error in global error handler", t) + + val errorMessage = when (t) { + + is ResponseException -> { + + //initial error type + var msg = when (t) { + is ClientRequestException -> { + ERROR_SERVER + } // in 400..499 + is RedirectResponseException -> { + ERROR_SERVER + } //in 300..399 + is ServerResponseException -> { + ERROR_SERVER + } //in 500..599 + else -> { + ERROR_NETWORK + } //something else + } + + val response = t.response + + Fore.getLogger(logWrapper).e("handleError() HTTP:" + response.status) + + //get more specific with the error type + msg = when (response.status.value) { + 401 -> ERROR_SESSION_TIMED_OUT + 400, 405 -> ERROR_CLIENT + 429 -> ERROR_RATE_LIMITED + 404 -> ERROR_SERVER //if this happens in prod, it's usually a server config issue + else -> null + } ?: msg + + //let's get even more specifics about the error + kSerializer?.let { serializer -> + msg = parseCustomError(msg, response, serializer) + } + + msg + } + + is NoTransformationFoundException -> ERROR_SERVER // content type is probably wrong, check response from server in app logs + is SerializationException -> ERROR_SERVER //parsing issue, maybe response is not json, or does not match expected type, or is empty + is TimeoutCancellationException -> ERROR_NETWORK // network timeout + is CancellationException -> ERROR_CLIENT // user cancellation, lifecycle takedown + + else -> { + when { + isProbablyNetworkError(t) -> ERROR_NETWORK + isProbablySecurityError(t) -> ERROR_SECURITY_UNKNOWN + else -> ERROR_NETWORK + } + } + } + + Fore.getLogger(logWrapper).w("replyWithFailure() returning:$errorMessage") + return errorMessage + } + + // this works a little better in KMP where we don't have unified exceptions + // because of platform differences + private fun isProbablyNetworkError(throwable: Throwable): Boolean { + val message = throwable.message?.lowercase() ?: "" + val className = throwable::class.simpleName?.lowercase() ?: "" + + return message.contains("network") || + message.contains("connection") || + message.contains("timeout") || + message.contains("unreachable") || + className.contains("network") || + className.contains("connection") || + className.contains("timeout") || + className.contains("io") + } + + // this works a little better in KMP where we don't have unified exceptions + // because of platform differences + private fun isProbablySecurityError(throwable: Throwable): Boolean { + val message = throwable.message?.lowercase() ?: "" + val className = throwable::class.simpleName?.lowercase() ?: "" + + return message.contains("ssl") || + message.contains("tls") || + message.contains("certificate") || + message.contains("security") || + className.contains("ssl") || + className.contains("tls") || + className.contains("security") + } + + + @Suppress("UNCHECKED_CAST") + private suspend fun > parseCustomError( + provisionalErrorMessage: ErrorMessage, + errorResponse: HttpResponse, + customErrorSerializer: KSerializer + ): ErrorMessage { + + var customError: ErrorMessage = provisionalErrorMessage + + try { + + val bodyContent = errorResponse.bodyAsText() + Fore.getLogger(logWrapper) + .w("parseCustomError() attempting to parse this content:\n $bodyContent") + + val errorClass = Json.decodeFromString(customErrorSerializer, bodyContent) + customError = errorClass.message + + } catch (t: Throwable) { + + Fore.getLogger(logWrapper).e("parseCustomError() unexpected issue $t") + + when (t) { + is IllegalStateException -> { + Fore.getLogger(logWrapper).e("01") // problem reading body text + } + is SerializationException -> { + Fore.getLogger(logWrapper).e("02") // parsing error, @Serializable missing, wrong error class specified etc + } + is IllegalArgumentException -> { + Fore.getLogger(logWrapper).e("03") // encoding or argument issues + } + is NullPointerException -> { + Fore.getLogger(logWrapper).e("04") + } + else -> { + Fore.getLogger(logWrapper).e("05") + } + } + } + + Fore.getLogger(logWrapper).w("parseCustomError() returning:$customError") + return customError + } +} diff --git a/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/KtorClientBuilder.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/KtorClientBuilder.kt new file mode 100644 index 00000000..39b2f47d --- /dev/null +++ b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/KtorClientBuilder.kt @@ -0,0 +1,77 @@ +package foo.bar.example.forektorkt.api + +import co.early.fore.core.delegate.Fore +import co.early.fore.core.logging.Logger +import co.early.fore.net.NetworkingLogSanitizer +import co.early.fore.net.ForeNetworkLogs +import io.ktor.client.* +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.logging.Logging +import io.ktor.serialization.kotlinx.json.* + +/** + * Most of this will all be specific to your application, when customising for your own case + * bare in mind that you should also be able to use this class in your tests to mock the server + * by passing offline data interceptor plugins in (see unit tests for an example) + */ +object KtorClientBuilder { + + /** + * + * @param configurePluginsBefore ktor plugin configuration block to be run first + * NB the logging plugin is usually the last one assuming you want to log what is actually + * sent from the device after all the other plugins have run + * @return ktor HttpClient object suitable for instantiating service interfaces + */ + fun create( + lgr: Logger = Fore.getLogger(), + configurePluginsBefore: HttpClientConfig<*>.() -> Unit = {}, + ): HttpClient { + + return HttpClient(CIO) { // CIO or Darwin etc + expectSuccess = true + + configurePluginsBefore(this) + + install(ContentNegotiation) { + json() + } + install(PluginGlobalInterceptor) { + logger = lgr + } + // install(Logging) + // alternatively + install(ForeNetworkLogs) { + // all these are optional, default will suit most requirements + logger = lgr + curlStyleRequestLogs = true + prettifyResponseLogs = true + networkingLogSanitizer = object : NetworkingLogSanitizer { + override fun sanitizeHeaders(headers: Set>>): Set>> { + return headers + .filterNot { header -> + setOf( + "Authorization", + "X-Auth-Token", + "X-Session-Token" + ).any { it.equals(header.key, ignoreCase = true) } + } + .toSet() + } + + override fun sanitizeBody(text: String): String { + // highly implementation specific, e.g. you might want + // to recursively search through json keys for "id" or whatever + // and redact all the corresponding values etc + return text.replace("secret", "XXXX") + } + } + filters = listOf { request -> + // don't log any hosts containing notinteresting.com + !request.url.host.contains("notinteresting.com") + } + } + } + } +} diff --git a/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/PluginGlobalInterceptor.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/PluginGlobalInterceptor.kt new file mode 100644 index 00000000..7a51cb4e --- /dev/null +++ b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/PluginGlobalInterceptor.kt @@ -0,0 +1,44 @@ +package foo.bar.example.forektorkt.api + +import co.early.fore.core.logging.Logger +import foo.bar.example.forektorkt.BuildConfig +import io.ktor.client.plugins.* +import io.ktor.client.request.* +import io.ktor.util.* +import io.ktor.client.HttpClient + +/** + * Before creating your own custom ktor plugin check what's available first, + * for instance: install(DefaultRequest) { headers.append("User-Agent", "my ua") } + */ +class PluginGlobalInterceptor private constructor( + private val logger: Logger, + // private val session: Session, +) { + + class Config { + lateinit var logger: Logger + // var session: Session + } + + companion object Plugin : HttpClientPlugin { + + override val key = AttributeKey("PluginGlobalInterceptor") + + override fun prepare(block: Config.() -> Unit): PluginGlobalInterceptor { + val config = Config().apply(block) + return PluginGlobalInterceptor( + logger = config.logger + // session = config.session + ) + } + + override fun install(plugin: PluginGlobalInterceptor, scope: HttpClient) { + scope.requestPipeline.intercept(HttpRequestPipeline.State) { + context.headers.append("Content-Type", "application/json") + // context.headers.append("X-MyApp-Auth-Token", plugin.session?.getSessionToken() ?: "expired") + context.headers.append("User-Agent", "fore-example-user-agent-${BuildConfig.VERSION_NAME}") + } + } + } +} diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/api/fruits/FruitService.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/fruits/FruitService.kt similarity index 79% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/api/fruits/FruitService.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/fruits/FruitService.kt index 4a8f349e..e50dc248 100644 --- a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/api/fruits/FruitService.kt +++ b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/fruits/FruitService.kt @@ -8,19 +8,21 @@ import io.ktor.client.request.* * These stubs are hosted at https://www.mocky.io/ * * success example response: - * http://www.mocky.io/v2/59efa0132e0000ef331c5f9b + * https://run.mocky.io/v3/57760938-6a45-49de-8f34-2cc061d26bbd * * fail example response: * http://www.mocky.io/v2/59ef2a6c2e00002a1a1c5dea */ class FruitService(private val httpClient: HttpClient) { - private val baseUrl = "http://www.mocky.io/v2/" + private val baseUrl = "https://run.mocky.io/v3/" private val mediumDelay = 3 private val smallDelay = 1 suspend fun getFruitsSimulateOk(): List { - return httpClient.get("${baseUrl}59efa0132e0000ef331c5f9b/?mocky-delay=${mediumDelay}s").body() + return httpClient.get("${baseUrl}978bfa2b-cb05-45fa-b079-1b49a87d1832/?mocky-delay=${mediumDelay}s").body() + //return httpClient.get("${baseUrl}95fc2cb1-d8bf-43ef-a5cb-7db61736f40f/?mocky-delay=${mediumDelay}s").body() + // return httpClient.get("${baseUrl}57760938-6a45-49de-8f34-2cc061d26bbd/?mocky-delay=${mediumDelay}s").body() } suspend fun getFruitsSimulateNotAuthorised(): List { diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/api/fruits/FruitsCustomError.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/fruits/FruitsCustomError.kt similarity index 100% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/api/fruits/FruitsCustomError.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/fruits/FruitsCustomError.kt diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/api/fruits/Pojos.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/fruits/Pojos.kt similarity index 100% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/api/fruits/Pojos.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/api/fruits/Pojos.kt diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/feature/fruit/FruitFetcher.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/feature/fruit/FruitFetcher.kt similarity index 85% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/feature/fruit/FruitFetcher.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/feature/fruit/FruitFetcher.kt index e13f1c9b..becdaab9 100644 --- a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/feature/fruit/FruitFetcher.kt +++ b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/feature/fruit/FruitFetcher.kt @@ -1,18 +1,14 @@ package foo.bar.example.forektorkt.feature.fruit -import co.early.fore.kt.core.logging.Logger +import co.early.fore.core.logging.Logger import co.early.fore.core.observer.Observable -import co.early.fore.kt.core.carryOn -import co.early.fore.kt.core.coroutine.launchMain -import co.early.fore.kt.core.eitherError -import co.early.fore.kt.core.eitherSuccess -import co.early.fore.kt.core.observer.ObservableImp -import co.early.fore.kt.core.type.Either.Companion.fail -import co.early.fore.kt.core.type.Either.Companion.success -import co.early.fore.kt.core.type.Either.Fail -import co.early.fore.kt.core.type.Either.Success -import co.early.fore.kt.core.type.carryOn -import co.early.fore.kt.net.ktor.CallWrapperKtor +import co.early.fore.core.coroutine.launchMain +import co.early.fore.core.observer.ObservableImp +import co.early.fore.core.type.Either.Fail +import co.early.fore.core.type.Either.Success +import co.early.fore.core.type.carryOn +import co.early.fore.net.wrap.CallWrapper +import co.early.fore.net.wrap.Wrapper import foo.bar.example.forektorkt.api.fruits.FruitPojo import foo.bar.example.forektorkt.api.fruits.FruitService import foo.bar.example.forektorkt.api.fruits.FruitsCustomError @@ -27,7 +23,7 @@ typealias FailureCallback = (T) -> Unit */ class FruitFetcher( private val fruitService: FruitService, - private val callWrapperKtor: CallWrapperKtor, + private val callWrapper: Wrapper, private val logger: Logger ) : Observable by ObservableImp(logger = logger) { @@ -56,7 +52,7 @@ class FruitFetcher( logger.i("about to use CallWrapper t:" + Thread.currentThread()) - val deferredResult = callWrapperKtor.processCallAsync { + val deferredResult = callWrapper.processCallAsync { logger.i("processing call t:" + Thread.currentThread()) @@ -93,7 +89,7 @@ class FruitFetcher( launchMain { - val result = callWrapperKtor.processCallAwait { + val result = callWrapper.processCallAwait { fruitService.getFruitsSimulateNotAuthorised() } @@ -126,7 +122,7 @@ class FruitFetcher( launchMain { - val result = callWrapperKtor.processCallAwait(FruitsCustomError::class) { + val result = callWrapper.processCallAwait(FruitsCustomError.serializer()) { fruitService.getFruitsSimulateNotAuthorised() } @@ -171,35 +167,35 @@ class FruitFetcher( * errors at each step - Internet search for "railway oriented programming" * or "andThen" functions) */ - val response = callWrapperKtor.processCallAwait { + val response = callWrapper.processCallAwait { logger.i("...create user...") fruitService.createUser() }.carryOn { logger.i("...create user ticket...") - callWrapperKtor.processCallAwait { + callWrapper.processCallAwait { fruitService.createUserTicket(it.userId) } }.carryOn { ticketRef = it.ticketRef logger.i("...get waiting time...") - callWrapperKtor.processCallAwait { + callWrapper.processCallAwait { fruitService.getEstimatedWaitingTime(it.ticketRef) } }.carryOn { if (it.minutesWait > 10) { logger.i("...cancel ticket...") - callWrapperKtor.processCallAwait { + callWrapper.processCallAwait { fruitService.cancelTicket(ticketRef) } } else { logger.i("...confirm ticket...") - callWrapperKtor.processCallAwait { + callWrapper.processCallAwait { fruitService.confirmTicket(ticketRef) } } }.carryOn { logger.i("...claim free fruit!...") - callWrapperKtor.processCallAwait { + callWrapper.processCallAwait { fruitService.claimFreeFruit(it.ticketRef) } } diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/message/ErrorMessage.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/message/ErrorMessage.kt similarity index 100% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/message/ErrorMessage.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/message/ErrorMessage.kt diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/ui/fruit/FruitActivity.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/ui/fruit/FruitActivity.kt similarity index 95% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/ui/fruit/FruitActivity.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/ui/fruit/FruitActivity.kt index fd7cbf18..1b8a1e02 100644 --- a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/ui/fruit/FruitActivity.kt +++ b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/ui/fruit/FruitActivity.kt @@ -5,10 +5,10 @@ import android.os.Bundle import android.widget.Toast import androidx.core.content.ContentProviderCompat.requireContext import androidx.fragment.app.FragmentActivity +import co.early.fore.core.ui.LifecycleObserver import co.early.fore.core.ui.SyncableView -import co.early.fore.kt.core.ui.LifecycleObserver -import co.early.fore.kt.core.ui.showOrGone -import co.early.fore.kt.core.ui.showOrInvisible +import co.early.fore.core.ui.showOrGone +import co.early.fore.core.ui.showOrInvisible import foo.bar.example.forektorkt.OG import foo.bar.example.forektorkt.R import foo.bar.example.forektorkt.databinding.ActivityFruitBinding diff --git a/app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/ui/widgets/AnimatedTastyRatingBar.kt b/app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/ui/widgets/AnimatedTastyRatingBar.kt similarity index 100% rename from app-examples/example-kt-08ktor/src/main/java/foo/bar/example/forektorkt/ui/widgets/AnimatedTastyRatingBar.kt rename to app-examples/example-03-ktor/src/main/kotlin/foo/bar/example/forektorkt/ui/widgets/AnimatedTastyRatingBar.kt diff --git a/app-examples/example-jv-04retrofit/src/main/res/drawable-xxhdpi/lemon_negative.png b/app-examples/example-03-ktor/src/main/res/drawable-xxhdpi/lemon_negative.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/drawable-xxhdpi/lemon_negative.png rename to app-examples/example-03-ktor/src/main/res/drawable-xxhdpi/lemon_negative.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/drawable-xxhdpi/lemon_positive.png b/app-examples/example-03-ktor/src/main/res/drawable-xxhdpi/lemon_positive.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/drawable-xxhdpi/lemon_positive.png rename to app-examples/example-03-ktor/src/main/res/drawable-xxhdpi/lemon_positive.png diff --git a/app-examples/example-kt-08ktor/src/main/res/layout/activity_fruit.xml b/app-examples/example-03-ktor/src/main/res/layout/activity_fruit.xml similarity index 100% rename from app-examples/example-kt-08ktor/src/main/res/layout/activity_fruit.xml rename to app-examples/example-03-ktor/src/main/res/layout/activity_fruit.xml diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app-examples/example-03-ktor/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app-examples/example-03-ktor/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app-examples/example-03-ktor/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app-examples/example-03-ktor/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-hdpi/ic_launcher.png b/app-examples/example-03-ktor/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-hdpi/ic_launcher.png rename to app-examples/example-03-ktor/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app-examples/example-03-ktor/src/main/res/mipmap-hdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-hdpi/ic_launcher_background.png rename to app-examples/example-03-ktor/src/main/res/mipmap-hdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app-examples/example-03-ktor/src/main/res/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-hdpi/ic_launcher_foreground.png rename to app-examples/example-03-ktor/src/main/res/mipmap-hdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-mdpi/ic_launcher.png b/app-examples/example-03-ktor/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-mdpi/ic_launcher.png rename to app-examples/example-03-ktor/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app-examples/example-03-ktor/src/main/res/mipmap-mdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-mdpi/ic_launcher_background.png rename to app-examples/example-03-ktor/src/main/res/mipmap-mdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app-examples/example-03-ktor/src/main/res/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-mdpi/ic_launcher_foreground.png rename to app-examples/example-03-ktor/src/main/res/mipmap-mdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xhdpi/ic_launcher.png b/app-examples/example-03-ktor/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xhdpi/ic_launcher.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app-examples/example-03-ktor/src/main/res/mipmap-xhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xhdpi/ic_launcher_background.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app-examples/example-03-ktor/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app-examples/example-03-ktor/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app-examples/example-03-ktor/src/main/res/mipmap-xxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xxhdpi/ic_launcher_background.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app-examples/example-03-ktor/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app-examples/example-03-ktor/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app-examples/example-03-ktor/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-03adapters/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app-examples/example-03-ktor/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-03adapters/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png rename to app-examples/example-03-ktor/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/values/colors.xml b/app-examples/example-03-ktor/src/main/res/values/colors.xml similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/values/colors.xml rename to app-examples/example-03-ktor/src/main/res/values/colors.xml diff --git a/app-examples/example-kt-08ktor/src/main/res/values/strings.xml b/app-examples/example-03-ktor/src/main/res/values/strings.xml similarity index 100% rename from app-examples/example-kt-08ktor/src/main/res/values/strings.xml rename to app-examples/example-03-ktor/src/main/res/values/strings.xml diff --git a/app-examples/example-jv-04retrofit/src/main/res/values/styles.xml b/app-examples/example-03-ktor/src/main/res/values/styles.xml similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/values/styles.xml rename to app-examples/example-03-ktor/src/main/res/values/styles.xml diff --git a/app-examples/example-kt-08ktor/src/test/java/foo/bar/example/forektorkt/feature/fruit/FruitFetcherUnitTest.kt b/app-examples/example-03-ktor/src/test/kotlin/foo/bar/example/forektorkt/feature/fruit/FruitFetcherUnitTest.kt similarity index 86% rename from app-examples/example-kt-08ktor/src/test/java/foo/bar/example/forektorkt/feature/fruit/FruitFetcherUnitTest.kt rename to app-examples/example-03-ktor/src/test/kotlin/foo/bar/example/forektorkt/feature/fruit/FruitFetcherUnitTest.kt index fc79125c..0ea27da9 100644 --- a/app-examples/example-kt-08ktor/src/test/java/foo/bar/example/forektorkt/feature/fruit/FruitFetcherUnitTest.kt +++ b/app-examples/example-03-ktor/src/test/kotlin/foo/bar/example/forektorkt/feature/fruit/FruitFetcherUnitTest.kt @@ -1,10 +1,10 @@ package foo.bar.example.forektorkt.feature.fruit -import co.early.fore.kt.core.logging.SystemLogger +import co.early.fore.core.logging.SystemLogger import co.early.fore.core.observer.Observer -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.core.delegate.TestDelegateDefault -import co.early.fore.kt.net.ktor.CallWrapperKtor +import co.early.fore.core.delegate.Fore +import co.early.fore.core.delegate.TestDelegateDefault +import co.early.fore.net.wrap.CallWrapper import foo.bar.example.forektorkt.api.fruits.FruitPojo import foo.bar.example.forektorkt.api.fruits.FruitService import foo.bar.example.forektorkt.message.ErrorMessage @@ -34,7 +34,7 @@ class FruitFetcherUnitTest { private lateinit var mockFailureWithPayload: FailureCallback @MockK - private lateinit var mockCallWrapperKtor: CallWrapperKtor + private lateinit var mockCallWrapper: CallWrapper @MockK private lateinit var mockFruitService: FruitService @@ -61,7 +61,7 @@ class FruitFetcherUnitTest { //arrange val fruitFetcher = FruitFetcher( mockFruitService, - mockCallWrapperKtor, + mockCallWrapper, logger ) @@ -79,10 +79,10 @@ class FruitFetcherUnitTest { fun fetchFruit_MockSuccess() { //arrange - StateBuilder(mockCallWrapperKtor).getFruitSuccess(fruitPojo) + val fakeCallWrapper = StateBuilder().getFruitSuccess(fruitPojo).fakeCallWrapper val fruitFetcher = FruitFetcher( mockFruitService, - mockCallWrapperKtor, + fakeCallWrapper, logger ) @@ -110,10 +110,10 @@ class FruitFetcherUnitTest { fun fetchFruit_MockFailure() { //arrange - StateBuilder(mockCallWrapperKtor).getFruitFail(ErrorMessage.ERROR_FRUIT_USER_LOGIN_CREDENTIALS_INCORRECT) + val fakeCallWrapper = StateBuilder().getFruitFail(ErrorMessage.ERROR_FRUIT_USER_LOGIN_CREDENTIALS_INCORRECT).fakeCallWrapper val fruitFetcher = FruitFetcher( mockFruitService, - mockCallWrapperKtor, + fakeCallWrapper, logger ) @@ -136,7 +136,6 @@ class FruitFetcherUnitTest { /** - * * NB all we are checking here is that observers are called AT LEAST once * * We don't really want tie our tests (OR any observers in production code) @@ -147,7 +146,7 @@ class FruitFetcherUnitTest { * only that they will be called if something changes ("something" is not defined * and can change between implementations). * - * See the databinding docs for more information about this + * See the fore docs for more information about this * * @throws Exception */ @@ -156,10 +155,10 @@ class FruitFetcherUnitTest { fun observersNotifiedAtLeastOnce() { //arrange - StateBuilder(mockCallWrapperKtor).getFruitSuccess(fruitPojo) + val fakeCallWrapper = StateBuilder().getFruitSuccess(fruitPojo).fakeCallWrapper val fruitFetcher = FruitFetcher( mockFruitService, - mockCallWrapperKtor, + fakeCallWrapper, logger ) fruitFetcher.addObserver(mockObserver) diff --git a/app-examples/example-03-ktor/src/test/kotlin/foo/bar/example/forektorkt/feature/fruit/StateBuilder.kt b/app-examples/example-03-ktor/src/test/kotlin/foo/bar/example/forektorkt/feature/fruit/StateBuilder.kt new file mode 100644 index 00000000..3216843c --- /dev/null +++ b/app-examples/example-03-ktor/src/test/kotlin/foo/bar/example/forektorkt/feature/fruit/StateBuilder.kt @@ -0,0 +1,30 @@ +package foo.bar.example.forektorkt.feature.fruit + +import co.early.fore.net.wrap.FakeCallWrapper +import co.early.fore.net.wrap.toFakeFail +import co.early.fore.net.wrap.toFakeSuccess +import foo.bar.example.forektorkt.api.fruits.FruitPojo +import foo.bar.example.forektorkt.message.ErrorMessage + +class StateBuilder { + + lateinit var fakeCallWrapper: FakeCallWrapper + + internal fun getFruitSuccess(fruitPojo: FruitPojo): StateBuilder { + + fakeCallWrapper = FakeCallWrapper( + listOf(fruitPojo).toFakeSuccess() + ) + + return this + } + + internal fun getFruitFail(errorMessage: ErrorMessage): StateBuilder { + + fakeCallWrapper = FakeCallWrapper( + errorMessage.toFakeFail(), + ) + + return this + } +} diff --git a/app-examples/example-kt-07apollo3/build.gradle.kts b/app-examples/example-04-apollo/build.gradle.kts similarity index 70% rename from app-examples/example-kt-07apollo3/build.gradle.kts rename to app-examples/example-04-apollo/build.gradle.kts index 4ac9598a..33b89a82 100644 --- a/app-examples/example-kt-07apollo3/build.gradle.kts +++ b/app-examples/example-04-apollo/build.gradle.kts @@ -2,14 +2,14 @@ import co.early.fore.Shared import co.early.fore.Shared.BuildTypes plugins { - alias(libs.plugins.androidApplication) - alias(libs.plugins.kotlinAndroid) - id("com.apollographql.apollo3").version("3.8.3") - kotlin("kapt") + alias(libs.plugins.androidAppPlugin) + alias(libs.plugins.kotlinAndroidPlugin) + alias(libs.plugins.kotlinSerializationPlugin) + alias(libs.plugins.kotlinKaptPlugin) + alias(libs.plugins.apolloPlugin) } - -val appId = "foo.bar.example.foreapollo3" +val appId = "foo.bar.example.foreapollo" fun getTestBuildType(): String { return project.properties["testBuildType"] as String? ?: BuildTypes.DEFAULT @@ -19,7 +19,7 @@ println("[$appId testBuildType:${getTestBuildType()}]") kotlin { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(Shared.Versions.jvm_toolchain)) + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvm.toolchain.get().toInt())) } } @@ -57,7 +57,10 @@ android { } getByName(BuildTypes.RELEASE) { isMinifyEnabled = true - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "../proguard-example-app.pro") + proguardFiles( + getDefaultProguardFile("proguard-android.txt"), + "proguard-example-kt-08ktor.pro" + ) signingConfig = signingConfigs.getByName(BuildTypes.RELEASE) } } @@ -85,19 +88,32 @@ apollo { dependencies { if (Shared.Publish.use_published_version) { - implementation("co.early.fore:fore-kt-android-core:${Shared.Publish.published_fore_version_for_examples}") - implementation("co.early.fore:fore-kt-network:${Shared.Publish.published_fore_version_for_examples}") + implementation(libs.fore.core) + implementation(libs.fore.net) + implementation(libs.fore.net.apollo) + testImplementation(libs.fore.test.fixtures) + testImplementation(libs.fore.net.apollo.test.fixtures) } else { - implementation(project(":fore-kt:fore-kt-android-core")) - implementation(project(":fore-kt:fore-kt-network")) + implementation(project(":lib:fore-core")) + implementation(project(":lib:fore-net")) + implementation(project(":lib:fore-net-apollo")) + testImplementation(project(":lib:fore-test-fixtures")) + testImplementation(project(":lib:fore-net-apollo-test-fixtures")) } - implementation("com.apollographql.apollo3:apollo-runtime:${Shared.Versions.apollo3}") + implementation(libs.apollo.runtime) + implementation(libs.apollo.engine.ktor) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.serialization.json) + implementation(libs.ktor.content.negotiation) implementation("io.coil-kt:coil:${Shared.Versions.coil}") implementation("androidx.appcompat:appcompat:${Shared.Versions.appcompat}") implementation("androidx.constraintlayout:constraintlayout:${Shared.Versions.constraintlayout}") + implementation("org.slf4j:slf4j-nop:2.0.7") // to get rid of the slf4 warning that comes from ktor + testImplementation("junit:junit:${Shared.Versions.junit}") testImplementation("io.mockk:mockk:${Shared.Versions.mockk}") diff --git a/app-examples/example-kt-07apollo3/screenshot.png b/app-examples/example-04-apollo/screenshot.png similarity index 100% rename from app-examples/example-kt-07apollo3/screenshot.png rename to app-examples/example-04-apollo/screenshot.png diff --git a/app-examples/example-kt-04retrofit/src/main/AndroidManifest.xml b/app-examples/example-04-apollo/src/main/AndroidManifest.xml similarity index 82% rename from app-examples/example-kt-04retrofit/src/main/AndroidManifest.xml rename to app-examples/example-04-apollo/src/main/AndroidManifest.xml index 14caf367..d224fb77 100644 --- a/app-examples/example-kt-04retrofit/src/main/AndroidManifest.xml +++ b/app-examples/example-04-apollo/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ - + diff --git a/app-examples/example-kt-07apollo3/src/main/graphql/BookTrip.graphql b/app-examples/example-04-apollo/src/main/graphql/BookTrip.graphql similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/graphql/BookTrip.graphql rename to app-examples/example-04-apollo/src/main/graphql/BookTrip.graphql diff --git a/app-examples/example-kt-07apollo3/src/main/graphql/CancelTrip.graphql b/app-examples/example-04-apollo/src/main/graphql/CancelTrip.graphql similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/graphql/CancelTrip.graphql rename to app-examples/example-04-apollo/src/main/graphql/CancelTrip.graphql diff --git a/app-examples/example-kt-07apollo3/src/main/graphql/LaunchDetails.graphql b/app-examples/example-04-apollo/src/main/graphql/LaunchDetails.graphql similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/graphql/LaunchDetails.graphql rename to app-examples/example-04-apollo/src/main/graphql/LaunchDetails.graphql diff --git a/app-examples/example-kt-07apollo3/src/main/graphql/LaunchList.graphql b/app-examples/example-04-apollo/src/main/graphql/LaunchList.graphql similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/graphql/LaunchList.graphql rename to app-examples/example-04-apollo/src/main/graphql/LaunchList.graphql diff --git a/app-examples/example-kt-07apollo3/src/main/graphql/Login.graphql b/app-examples/example-04-apollo/src/main/graphql/Login.graphql similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/graphql/Login.graphql rename to app-examples/example-04-apollo/src/main/graphql/Login.graphql diff --git a/app-examples/example-kt-07apollo3/src/main/graphql/schema.json b/app-examples/example-04-apollo/src/main/graphql/schema.json similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/graphql/schema.json rename to app-examples/example-04-apollo/src/main/graphql/schema.json diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/App.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/App.kt similarity index 90% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/App.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/App.kt index 9793e7f3..a50182dd 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/App.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/App.kt @@ -1,4 +1,4 @@ -package foo.bar.example.foreapollo3 +package foo.bar.example.foreapollo import android.app.Application diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/OG.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/OG.kt similarity index 71% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/OG.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/OG.kt index 4f108809..79ff4c5a 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/OG.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/OG.kt @@ -1,20 +1,20 @@ -package foo.bar.example.foreapollo3 +package foo.bar.example.foreapollo + +import co.early.fore.net.wrap.apollo.CallWrapperApollo -import android.app.Application -import co.early.fore.kt.core.logging.AndroidLogger -import co.early.fore.kt.core.logging.SilentLogger -import co.early.fore.kt.net.InterceptorLogging -import co.early.fore.kt.net.apollo3.CallWrapperApollo3 -import com.apollographql.apollo3.api.Optional -import foo.bar.example.foreapollo3.api.CustomApolloBuilder -import foo.bar.example.foreapollo3.api.CustomGlobalErrorHandler -import foo.bar.example.foreapollo3.api.CustomGlobalRequestInterceptor -import foo.bar.example.foreapollo3.feature.authentication.AuthService -import foo.bar.example.foreapollo3.feature.authentication.Authenticator -import foo.bar.example.foreapollo3.feature.launch.LaunchService -import foo.bar.example.foreapollo3.feature.launch.LaunchesModel -import java.util.* +import android.app.Application +import co.early.fore.core.delegate.DebugDelegateDefault +import co.early.fore.core.delegate.Fore +import co.early.fore.core.logging.AndroidLogger +import co.early.fore.core.logging.SilentLogger +import foo.bar.example.foreapollo.api.ApolloClientBuilder +import foo.bar.example.foreapollo.api.CustomGlobalErrorHandler +import foo.bar.example.foreapollo.feature.authentication.AuthService +import foo.bar.example.foreapollo.feature.authentication.Authenticator +import foo.bar.example.foreapollo.feature.launch.LaunchService +import foo.bar.example.foreapollo.feature.launch.LaunchesModel +import com.apollographql.apollo.api.Optional /** * @@ -33,15 +33,13 @@ object OG { // create dependency graph val logger = if (BuildConfig.DEBUG) AndroidLogger("fore_") else SilentLogger() + Fore.setDelegate(DebugDelegateDefault(logger = logger)) // networking classes common to all models - val globalRequestInterceptor = CustomGlobalRequestInterceptor(logger) - val apolloClient = CustomApolloBuilder.create( - globalRequestInterceptor, - InterceptorLogging(logger) - )//logging interceptor should be the last one - val callWrapper = CallWrapperApollo3( + val apolloClient by lazy { ApolloClientBuilder.create(OG[Authenticator::class.java]) } + + val callWrapper = CallWrapperApollo( CustomGlobalErrorHandler(logger), logger ) @@ -54,7 +52,7 @@ object OG { callWrapper, logger ) - globalRequestInterceptor.setAuthenticator(authenticator) + val launchesModel = LaunchesModel( launchService = LaunchService( getLaunchList = { apolloClient.query(LaunchListQuery()).execute() }, diff --git a/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/ApolloClientBuilder.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/ApolloClientBuilder.kt new file mode 100644 index 00000000..7bb50b3e --- /dev/null +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/ApolloClientBuilder.kt @@ -0,0 +1,24 @@ +package foo.bar.example.foreapollo.api + +import com.apollographql.apollo.ApolloClient +import com.apollographql.ktor.ktorClient +import foo.bar.example.foreapollo.feature.authentication.Authenticator + +/** + * Most of this will all be specific to your application, when customising for your own case + * bare in mind that you should be able to use this class in your tests to mock the server + * by passing different interceptors in: + * + * see @[co.early.fore.net.testhelpers.InterceptorStubbedService] + * + */ +object ApolloClientBuilder { + fun create(authenticator: Authenticator): ApolloClient { + return ApolloClient.Builder() + .serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/graphql") + .ktorClient(KtorClientBuilder.create { + addSessionToken(authenticator = authenticator) + }) + .build() + } +} diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/api/CustomGlobalErrorHandler.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/CustomGlobalErrorHandler.kt similarity index 67% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/api/CustomGlobalErrorHandler.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/CustomGlobalErrorHandler.kt index 1df776ee..4caa58e4 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/api/CustomGlobalErrorHandler.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/CustomGlobalErrorHandler.kt @@ -1,16 +1,14 @@ -package foo.bar.example.foreapollo3.api +package foo.bar.example.foreapollo.api -import co.early.fore.kt.core.logging.Logger -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.net.apollo3.ErrorHandler -import com.apollographql.apollo3.api.ApolloResponse -import com.apollographql.apollo3.api.Error -import com.apollographql.apollo3.exception.ApolloHttpException -import com.apollographql.apollo3.exception.ApolloNetworkException -import com.apollographql.apollo3.exception.ApolloParseException -import com.apollographql.apollo3.exception.JsonEncodingException -import foo.bar.example.foreapollo3.message.ErrorMessage -import foo.bar.example.foreapollo3.message.ErrorMessage.* +import co.early.fore.core.logging.Logger +import co.early.fore.core.delegate.Fore +import co.early.fore.net.wrap.apollo.ErrorHandler +import com.apollographql.apollo.api.ApolloResponse +import com.apollographql.apollo.exception.ApolloHttpException +import com.apollographql.apollo.exception.ApolloNetworkException +import com.apollographql.apollo.exception.JsonEncodingException +import foo.bar.example.foreapollo.message.ErrorMessage +import foo.bar.example.foreapollo.message.ErrorMessage.* /** * You can probably use this class almost as it is for your own app, but you might want to @@ -27,24 +25,25 @@ class CustomGlobalErrorHandler(private val logger: Logger?) : Fore.getLogger(logger).e("handleError() t:$t errorResponse:$errorResponse") - val message = parseSpecificError(errorResponse?.errors?.first()) ?: parseGeneralErrors(t) + val message = errorResponse?.errors?.firstNotNullOf { it }?.let{ + parseSpecificError(it) + } ?: parseGeneralErrors(t) Fore.getLogger(logger).e("handleError() returning:$message") return message } - override fun handlePartialErrors(errors: List?): List { - return errors?.mapNotNull { + override fun handlePartialErrors(errors: List): List { + return errors.mapNotNull { parseSpecificError(it) - } ?: emptyList() + } } private fun parseGeneralErrors(t: Throwable?): ErrorMessage { return t?.let { when (it) { is java.lang.IllegalStateException -> ERROR_ALREADY_EXECUTED - is ApolloParseException -> ERROR_SERVER is JsonEncodingException -> ERROR_SERVER is ApolloNetworkException -> ERROR_NETWORK is java.net.UnknownServiceException -> ERROR_SECURITY_UNKNOWN @@ -66,14 +65,14 @@ class CustomGlobalErrorHandler(private val logger: Logger?) : } ?: ERROR_MISC } - private fun parseSpecificError(error: Error?): ErrorMessage? { + private fun parseSpecificError(error: com.apollographql.apollo.api.Error): ErrorMessage { // amazingly GraphQL never had an error code in its standard error // block so it usually gets put under the extensions block like this: // https://spec.graphql.org/draft/#example-8b658 - return error?.extensions?.let { extensions -> + return error.extensions?.let { extensions -> (extensions as? Map<*, *>)?.get("code")?.let { code -> ErrorMessage.createFromName(code as? String) } - } + } ?: ERROR_MISC } } diff --git a/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/KtorClientBuilder.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/KtorClientBuilder.kt new file mode 100644 index 00000000..d507b59d --- /dev/null +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/KtorClientBuilder.kt @@ -0,0 +1,78 @@ +package foo.bar.example.foreapollo.api + +import co.early.fore.core.delegate.Fore +import co.early.fore.core.logging.Logger +import co.early.fore.net.NetworkingLogSanitizer +import co.early.fore.net.ForeNetworkLogs +import foo.bar.example.foreapollo.BuildConfig +import io.ktor.client.* +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.DefaultRequest +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import kotlinx.serialization.json.Json + +/** + * Most of this will all be specific to your application, when customising for your own case + * bare in mind that you should also be able to use this class in your tests to mock the server + * by passing offline data interceptor plugins in (see unit tests for an example) + */ +object KtorClientBuilder { + + /** + * + * @param configurePluginsBefore ktor plugin configuration block to be run first + * NB the logging plugin is usually the last one assuming you want to log what is actually + * sent from the device after all the other plugins have run + * @return ktor HttpClient object suitable for instantiating service interfaces + */ + fun create( + lgr: Logger = Fore.getLogger(), + configurePluginsBefore: HttpClientConfig<*>.() -> Unit = {}, + ): HttpClient { + + return HttpClient(CIO) { // CIO or Darwin etc + expectSuccess = true + + configurePluginsBefore(this) + + install(DefaultRequest) { + headers.append("User-Agent", "fore-example-user-agent-${BuildConfig.VERSION_NAME}") + } + install(ContentNegotiation) { + Json { + ignoreUnknownKeys = true + } + } + install(ForeNetworkLogs) { + // all these are optional, default will suit most requirements + logger = lgr + curlStyleRequestLogs = true + prettifyResponseLogs = true + networkingLogSanitizer = object : NetworkingLogSanitizer { + override fun sanitizeHeaders(headers: Set>>): Set>> { + return headers + .filterNot { header -> + setOf( + "Authorization", + "X-Auth-Token", + "X-Session-Token" + ).any { it.equals(header.key, ignoreCase = true) } + } + .toSet() + } + + override fun sanitizeBody(text: String): String { + // highly implementation specific, e.g. you might want + // to recursively search through json keys for "id" or whatever + // and redact all the corresponding values etc + return text.replace("secret", "XXXX") + } + } + filters = listOf { request -> + // don't log any hosts containing notinteresting.com + !request.url.host.contains("notinteresting.com") + } + } + } + } +} diff --git a/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/PluginSession.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/PluginSession.kt new file mode 100644 index 00000000..41711b78 --- /dev/null +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/api/PluginSession.kt @@ -0,0 +1,17 @@ +package foo.bar.example.foreapollo.api + +import foo.bar.example.foreapollo.feature.authentication.Authenticator +import io.ktor.client.HttpClientConfig +import io.ktor.client.plugins.api.createClientPlugin + +fun HttpClientConfig<*>.addSessionToken(authenticator: Authenticator? = null) { + install(createClientPlugin("PluginSessionToken") { + onRequest { request, _ -> + if (authenticator?.hasSessionToken() == true) { + request.headers.append("Authorization", authenticator.sessionToken) + } else { + request.headers.append("Authorization", "expired") + } + } + }) +} diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/Callbacks.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/Callbacks.kt similarity index 65% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/Callbacks.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/Callbacks.kt index 89ef7f36..e3491c69 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/Callbacks.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/Callbacks.kt @@ -1,4 +1,4 @@ -package foo.bar.example.foreapollo3.feature +package foo.bar.example.foreapollo.feature typealias SuccessCallback = () -> Unit typealias FailureCallback = (T) -> Unit \ No newline at end of file diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/authentication/Authenticator.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/authentication/Authenticator.kt similarity index 77% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/authentication/Authenticator.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/authentication/Authenticator.kt index 844cd284..a7db22f5 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/authentication/Authenticator.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/authentication/Authenticator.kt @@ -1,18 +1,18 @@ -package foo.bar.example.foreapollo3.feature.authentication +package foo.bar.example.foreapollo.feature.authentication import co.early.fore.core.observer.Observable -import co.early.fore.kt.core.coroutine.awaitMain -import co.early.fore.kt.core.coroutine.launchIO -import co.early.fore.kt.core.logging.Logger -import co.early.fore.kt.core.observer.ObservableImp -import co.early.fore.kt.core.type.Either.Fail -import co.early.fore.kt.core.type.Either.Success -import co.early.fore.kt.net.apollo3.CallWrapperApollo3 -import com.apollographql.apollo3.api.ApolloResponse -import foo.bar.example.foreapollo3.LoginMutation -import foo.bar.example.foreapollo3.feature.FailureCallback -import foo.bar.example.foreapollo3.feature.SuccessCallback -import foo.bar.example.foreapollo3.message.ErrorMessage +import co.early.fore.core.coroutine.awaitMain +import co.early.fore.core.coroutine.launchIO +import co.early.fore.core.logging.Logger +import co.early.fore.core.observer.ObservableImp +import co.early.fore.core.type.Either.Fail +import co.early.fore.core.type.Either.Success +import co.early.fore.net.wrap.apollo.CallWrapperApollo +import com.apollographql.apollo.api.ApolloResponse +import foo.bar.example.foreapollo.LoginMutation +import foo.bar.example.foreapollo.feature.FailureCallback +import foo.bar.example.foreapollo.feature.SuccessCallback +import foo.bar.example.foreapollo.message.ErrorMessage data class AuthService( val login: suspend (email: String) -> ApolloResponse @@ -23,7 +23,7 @@ data class AuthService( */ class Authenticator( private val authService: AuthService, - private val callWrapperApollo3: CallWrapperApollo3, + private val callWrapperApollo: CallWrapperApollo, private val logger: Logger ) : Observable by ObservableImp(logger = logger) { @@ -58,7 +58,7 @@ class Authenticator( launchIO { - val deferredResult = callWrapperApollo3.processCallAsync { + val deferredResult = callWrapperApollo.processCallAsync { authService.login(email) } diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/launch/DataMapping.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/launch/DataMapping.kt similarity index 82% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/launch/DataMapping.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/launch/DataMapping.kt index fe2f9ece..658f2c45 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/launch/DataMapping.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/launch/DataMapping.kt @@ -1,7 +1,7 @@ -package foo.bar.example.foreapollo3.feature.launch +package foo.bar.example.foreapollo.feature.launch -import foo.bar.example.foreapollo3.LaunchDetailsQuery -import foo.bar.example.foreapollo3.LaunchListQuery +import foo.bar.example.foreapollo.LaunchDetailsQuery +import foo.bar.example.foreapollo.LaunchListQuery //we don't want the API / graphQL pojo abstractions leaking in to the rest of the app diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/launch/LaunchesModel.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/launch/LaunchesModel.kt similarity index 86% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/launch/LaunchesModel.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/launch/LaunchesModel.kt index 88877663..9d9042a8 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/feature/launch/LaunchesModel.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/feature/launch/LaunchesModel.kt @@ -1,20 +1,20 @@ -package foo.bar.example.foreapollo3.feature.launch +package foo.bar.example.foreapollo.feature.launch import co.early.fore.core.observer.Observable -import co.early.fore.kt.core.coroutine.awaitMain -import co.early.fore.kt.core.coroutine.launchIO -import co.early.fore.kt.core.logging.Logger -import co.early.fore.kt.core.observer.ObservableImp -import co.early.fore.kt.core.type.Either.Fail -import co.early.fore.kt.core.type.Either.Success -import co.early.fore.kt.core.type.carryOn -import co.early.fore.kt.net.apollo3.CallWrapperApollo3 -import com.apollographql.apollo3.api.ApolloResponse -import foo.bar.example.foreapollo3.* -import foo.bar.example.foreapollo3.feature.FailureCallback -import foo.bar.example.foreapollo3.feature.SuccessCallback -import foo.bar.example.foreapollo3.feature.authentication.Authenticator -import foo.bar.example.foreapollo3.message.ErrorMessage +import co.early.fore.core.coroutine.awaitMain +import co.early.fore.core.coroutine.launchIO +import co.early.fore.core.logging.Logger +import co.early.fore.core.observer.ObservableImp +import co.early.fore.core.type.Either.Fail +import co.early.fore.core.type.Either.Success +import co.early.fore.core.type.carryOn +import co.early.fore.net.wrap.apollo.WrapperApollo +import com.apollographql.apollo.api.ApolloResponse +import foo.bar.example.foreapollo.* +import foo.bar.example.foreapollo.feature.FailureCallback +import foo.bar.example.foreapollo.feature.SuccessCallback +import foo.bar.example.foreapollo.feature.authentication.Authenticator +import foo.bar.example.foreapollo.message.ErrorMessage import java.util.* data class LaunchService( @@ -28,7 +28,7 @@ data class LaunchService( class LaunchesModel( private val launchService: LaunchService, - private val callWrapper: CallWrapperApollo3, + private val callWrapper: WrapperApollo, private val authenticator: Authenticator, private val logger: Logger ) : Observable by ObservableImp(logger = logger) { diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/message/ErrorMessage.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/message/ErrorMessage.kt similarity index 94% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/message/ErrorMessage.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/message/ErrorMessage.kt index 3d72a3b4..73cf1121 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/message/ErrorMessage.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/message/ErrorMessage.kt @@ -1,9 +1,9 @@ -package foo.bar.example.foreapollo3.message +package foo.bar.example.foreapollo.message import android.os.Parcel import android.os.Parcelable -import foo.bar.example.foreapollo3.App -import foo.bar.example.foreapollo3.R +import foo.bar.example.foreapollo.App +import foo.bar.example.foreapollo.R /** * As an enum, this value can be passed around the app to indicate various error states. diff --git a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/ui/launch/LaunchActivity.kt b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/ui/launch/LaunchActivity.kt similarity index 81% rename from app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/ui/launch/LaunchActivity.kt rename to app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/ui/launch/LaunchActivity.kt index 268d2b95..d557f34e 100644 --- a/app-examples/example-kt-07apollo3/src/main/java/foo/bar/example/foreapollo3/ui/launch/LaunchActivity.kt +++ b/app-examples/example-04-apollo/src/main/java/foo/bar/example/foreapollo/ui/launch/LaunchActivity.kt @@ -1,22 +1,24 @@ -package foo.bar.example.foreapollo3.ui.launch +package foo.bar.example.foreapollo.ui.launch +import android.annotation.SuppressLint import android.content.Context import android.os.Bundle import android.widget.Toast import androidx.fragment.app.FragmentActivity import co.early.fore.core.ui.SyncableView -import co.early.fore.kt.core.ui.LifecycleObserver -import co.early.fore.kt.core.ui.showOrGone -import co.early.fore.kt.core.ui.showOrInvisible +import co.early.fore.core.ui.LifecycleObserver +import co.early.fore.core.ui.showOrGone +import co.early.fore.core.ui.showOrInvisible import coil.load -import foo.bar.example.foreapollo3.OG -import foo.bar.example.foreapollo3.databinding.ActivityLaunchesBinding -import foo.bar.example.foreapollo3.feature.FailureCallback -import foo.bar.example.foreapollo3.feature.SuccessCallback -import foo.bar.example.foreapollo3.feature.authentication.Authenticator -import foo.bar.example.foreapollo3.feature.launch.LaunchesModel -import foo.bar.example.foreapollo3.message.ErrorMessage +import foo.bar.example.foreapollo.OG +import foo.bar.example.foreapollo.databinding.ActivityLaunchesBinding +import foo.bar.example.foreapollo.feature.FailureCallback +import foo.bar.example.foreapollo.feature.SuccessCallback +import foo.bar.example.foreapollo.feature.authentication.Authenticator +import foo.bar.example.foreapollo.feature.launch.LaunchesModel +import foo.bar.example.foreapollo.message.ErrorMessage +@SuppressLint("CustomSplashScreen") class LaunchActivity : FragmentActivity(), SyncableView { //models that we need to sync with diff --git a/app-examples/example-kt-07apollo3/src/main/res/layout/activity_launches.xml b/app-examples/example-04-apollo/src/main/res/layout/activity_launches.xml similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/res/layout/activity_launches.xml rename to app-examples/example-04-apollo/src/main/res/layout/activity_launches.xml diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app-examples/example-04-apollo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app-examples/example-04-apollo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app-examples/example-04-apollo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app-examples/example-04-apollo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-hdpi/ic_launcher.png b/app-examples/example-04-apollo/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-hdpi/ic_launcher.png rename to app-examples/example-04-apollo/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app-examples/example-04-apollo/src/main/res/mipmap-hdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-hdpi/ic_launcher_background.png rename to app-examples/example-04-apollo/src/main/res/mipmap-hdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app-examples/example-04-apollo/src/main/res/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-hdpi/ic_launcher_foreground.png rename to app-examples/example-04-apollo/src/main/res/mipmap-hdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-mdpi/ic_launcher.png b/app-examples/example-04-apollo/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-mdpi/ic_launcher.png rename to app-examples/example-04-apollo/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app-examples/example-04-apollo/src/main/res/mipmap-mdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-mdpi/ic_launcher_background.png rename to app-examples/example-04-apollo/src/main/res/mipmap-mdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app-examples/example-04-apollo/src/main/res/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-mdpi/ic_launcher_foreground.png rename to app-examples/example-04-apollo/src/main/res/mipmap-mdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xhdpi/ic_launcher.png b/app-examples/example-04-apollo/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xhdpi/ic_launcher.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app-examples/example-04-apollo/src/main/res/mipmap-xhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xhdpi/ic_launcher_background.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app-examples/example-04-apollo/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app-examples/example-04-apollo/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app-examples/example-04-apollo/src/main/res/mipmap-xxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xxhdpi/ic_launcher_background.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app-examples/example-04-apollo/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app-examples/example-04-apollo/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app-examples/example-04-apollo/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-04retrofit/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app-examples/example-04-apollo/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-04retrofit/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png rename to app-examples/example-04-apollo/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/values/colors.xml b/app-examples/example-04-apollo/src/main/res/values/colors.xml similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/values/colors.xml rename to app-examples/example-04-apollo/src/main/res/values/colors.xml diff --git a/app-examples/example-kt-07apollo3/src/main/res/values/strings.xml b/app-examples/example-04-apollo/src/main/res/values/strings.xml similarity index 100% rename from app-examples/example-kt-07apollo3/src/main/res/values/strings.xml rename to app-examples/example-04-apollo/src/main/res/values/strings.xml diff --git a/app-examples/example-kt-04retrofit/src/main/res/values/styles.xml b/app-examples/example-04-apollo/src/main/res/values/styles.xml similarity index 100% rename from app-examples/example-kt-04retrofit/src/main/res/values/styles.xml rename to app-examples/example-04-apollo/src/main/res/values/styles.xml diff --git a/app-examples/example-kt-07apollo3/src/test/java/foo/bar/example/foreapollo3/feature/launch/LaunchFetcherUnitTest.kt b/app-examples/example-04-apollo/src/test/java/foo/bar/example/foreapollo/feature/launch/LaunchFetcherUnitTest.kt similarity index 88% rename from app-examples/example-kt-07apollo3/src/test/java/foo/bar/example/foreapollo3/feature/launch/LaunchFetcherUnitTest.kt rename to app-examples/example-04-apollo/src/test/java/foo/bar/example/foreapollo/feature/launch/LaunchFetcherUnitTest.kt index c4480ca5..d04f8907 100644 --- a/app-examples/example-kt-07apollo3/src/test/java/foo/bar/example/foreapollo3/feature/launch/LaunchFetcherUnitTest.kt +++ b/app-examples/example-04-apollo/src/test/java/foo/bar/example/foreapollo/feature/launch/LaunchFetcherUnitTest.kt @@ -1,15 +1,15 @@ -package foo.bar.example.foreapollo3.feature.launch +package foo.bar.example.foreapollo.feature.launch import co.early.fore.core.observer.Observer -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.kt.core.delegate.TestDelegateDefault -import co.early.fore.kt.core.logging.SystemLogger -import co.early.fore.kt.net.apollo3.CallWrapperApollo3 -import foo.bar.example.foreapollo3.LaunchListQuery -import foo.bar.example.foreapollo3.feature.FailureCallback -import foo.bar.example.foreapollo3.feature.SuccessCallback -import foo.bar.example.foreapollo3.feature.authentication.Authenticator -import foo.bar.example.foreapollo3.message.ErrorMessage +import co.early.fore.core.delegate.Fore +import co.early.fore.core.delegate.TestDelegateDefault +import co.early.fore.core.logging.SystemLogger +import co.early.fore.net.wrap.apollo.CallWrapperApollo +import foo.bar.example.foreapollo.LaunchListQuery +import foo.bar.example.foreapollo.feature.FailureCallback +import foo.bar.example.foreapollo.feature.SuccessCallback +import foo.bar.example.foreapollo.feature.authentication.Authenticator +import foo.bar.example.foreapollo.message.ErrorMessage import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK @@ -38,7 +38,7 @@ class LaunchesModelUnitTest { private lateinit var mockFailureWithPayload: FailureCallback @MockK - private lateinit var mockCallWrapperApollo: CallWrapperApollo3 + private lateinit var mockCallWrapperApollo: CallWrapperApollo @MockK private lateinit var mockLaunchService: LaunchService @@ -89,7 +89,7 @@ class LaunchesModelUnitTest { //arrange val callWrapper = - StateBuilder().getLaunchSuccess(mockLaunchesData).mockCallWrapperApollo + StateBuilder().getLaunchSuccess(mockLaunchesData).fakeCallWrapperApollo val launchesModel = LaunchesModel( mockLaunchService, callWrapper, @@ -121,7 +121,7 @@ class LaunchesModelUnitTest { //arrange val callWrapper = - StateBuilder().getLaunchFail(ErrorMessage.INTERNAL_SERVER_ERROR).mockCallWrapperApollo + StateBuilder().getLaunchFail(ErrorMessage.INTERNAL_SERVER_ERROR).fakeCallWrapperApollo val launchesModel = LaunchesModel( mockLaunchService, callWrapper, @@ -169,7 +169,7 @@ class LaunchesModelUnitTest { //arrange val callWrapper = - StateBuilder().getLaunchSuccess(mockLaunchesData).mockCallWrapperApollo + StateBuilder().getLaunchSuccess(mockLaunchesData).fakeCallWrapperApollo val launchesModel = LaunchesModel( mockLaunchService, callWrapper, diff --git a/app-examples/example-04-apollo/src/test/java/foo/bar/example/foreapollo/feature/launch/StateBuilder.kt b/app-examples/example-04-apollo/src/test/java/foo/bar/example/foreapollo/feature/launch/StateBuilder.kt new file mode 100644 index 00000000..8684d239 --- /dev/null +++ b/app-examples/example-04-apollo/src/test/java/foo/bar/example/foreapollo/feature/launch/StateBuilder.kt @@ -0,0 +1,31 @@ +package foo.bar.example.foreapollo.feature.launch + +import co.early.fore.net.wrap.apollo.CallWrapperApollo.SuccessResult +import co.early.fore.net.wrap.apollo.FakeCallWrapperApollo +import co.early.fore.net.wrap.toFakeFail +import co.early.fore.net.wrap.toFakeSuccess +import foo.bar.example.foreapollo.LaunchListQuery +import foo.bar.example.foreapollo.message.ErrorMessage + +class StateBuilder { + + lateinit var fakeCallWrapperApollo: FakeCallWrapperApollo + + internal fun getLaunchSuccess(launches: LaunchListQuery.Data): StateBuilder { + + fakeCallWrapperApollo = FakeCallWrapperApollo( + SuccessResult(launches).toFakeSuccess() + ) + + return this + } + + internal fun getLaunchFail(errorMessage: ErrorMessage): StateBuilder { + + fakeCallWrapperApollo = FakeCallWrapperApollo( + errorMessage.toFakeFail() + ) + + return this + } +} diff --git a/app-examples/example-jv-06db/build.gradle.kts b/app-examples/example-05-db/build.gradle.kts similarity index 100% rename from app-examples/example-jv-06db/build.gradle.kts rename to app-examples/example-05-db/build.gradle.kts diff --git a/app-examples/example-jv-06db/screenshot.png b/app-examples/example-05-db/screenshot.png similarity index 100% rename from app-examples/example-jv-06db/screenshot.png rename to app-examples/example-05-db/screenshot.png diff --git a/app-examples/example-jv-06db/src/main/AndroidManifest.xml b/app-examples/example-05-db/src/main/AndroidManifest.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/AndroidManifest.xml rename to app-examples/example-05-db/src/main/AndroidManifest.xml diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/App.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/App.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/App.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/App.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/OG.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/OG.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/OG.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/OG.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/CustomGlobalErrorHandler.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/CustomGlobalErrorHandler.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/CustomGlobalErrorHandler.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/CustomGlobalErrorHandler.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/CustomGlobalRequestInterceptor.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/CustomGlobalRequestInterceptor.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/CustomGlobalRequestInterceptor.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/CustomGlobalRequestInterceptor.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/CustomRetrofitBuilder.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/CustomRetrofitBuilder.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/CustomRetrofitBuilder.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/CustomRetrofitBuilder.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemPojo.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemPojo.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemPojo.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemPojo.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemService.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemService.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemService.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/api/todoitems/TodoItemService.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/RandomTodoCreator.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/RandomTodoCreator.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/RandomTodoCreator.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/RandomTodoCreator.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDao.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDao.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDao.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDao.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDatabase.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDatabase.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDatabase.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemDatabase.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemEntity.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemEntity.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemEntity.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/db/todoitems/TodoItemEntity.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/bossmode/BossMode.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/bossmode/BossMode.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/bossmode/BossMode.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/bossmode/BossMode.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/remote/RemoteWorker.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/remote/RemoteWorker.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/remote/RemoteWorker.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/remote/RemoteWorker.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoItem.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoItem.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoItem.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoItem.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoListModel.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoListModel.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoListModel.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/feature/todoitems/TodoListModel.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/message/UserMessage.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/message/UserMessage.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/message/UserMessage.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/message/UserMessage.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/BaseActivity.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/BaseActivity.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/BaseActivity.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/BaseActivity.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/BaseActivityNavDrawer.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/BaseActivityNavDrawer.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/BaseActivityNavDrawer.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/BaseActivityNavDrawer.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/NavContainer.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/NavContainer.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/NavContainer.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/NavContainer.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/NavHeader.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/NavHeader.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/NavHeader.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/NavHeader.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/common/uiutils/SyncerTextWatcher.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/common/uiutils/SyncerTextWatcher.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/common/uiutils/SyncerTextWatcher.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/common/uiutils/SyncerTextWatcher.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/common/widgets/PercentBar.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/common/widgets/PercentBar.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/common/widgets/PercentBar.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/common/widgets/PercentBar.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListActivity.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListActivity.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListActivity.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListActivity.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListAdapter.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListAdapter.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListAdapter.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListAdapter.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListFragment.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListFragment.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListFragment.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListFragment.java diff --git a/app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListView.java b/app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListView.java similarity index 100% rename from app-examples/example-jv-06db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListView.java rename to app-examples/example-05-db/src/main/java/foo/bar/example/foredb/ui/todolist/TodoListView.java diff --git a/app-examples/example-jv-06db/src/main/res/drawable-hdpi/basket.png b/app-examples/example-05-db/src/main/res/drawable-hdpi/basket.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable-hdpi/basket.png rename to app-examples/example-05-db/src/main/res/drawable-hdpi/basket.png diff --git a/app-examples/example-jv-06db/src/main/res/drawable-hdpi/basket_menu_icon.png b/app-examples/example-05-db/src/main/res/drawable-hdpi/basket_menu_icon.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable-hdpi/basket_menu_icon.png rename to app-examples/example-05-db/src/main/res/drawable-hdpi/basket_menu_icon.png diff --git a/app-examples/example-jv-06db/src/main/res/drawable-hdpi/fruit_menu_icon.png b/app-examples/example-05-db/src/main/res/drawable-hdpi/fruit_menu_icon.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable-hdpi/fruit_menu_icon.png rename to app-examples/example-05-db/src/main/res/drawable-hdpi/fruit_menu_icon.png diff --git a/app-examples/example-jv-06db/src/main/res/drawable-hdpi/navigation_badge.xml b/app-examples/example-05-db/src/main/res/drawable-hdpi/navigation_badge.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable-hdpi/navigation_badge.xml rename to app-examples/example-05-db/src/main/res/drawable-hdpi/navigation_badge.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable-hdpi/navigation_selector.xml b/app-examples/example-05-db/src/main/res/drawable-hdpi/navigation_selector.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable-hdpi/navigation_selector.xml rename to app-examples/example-05-db/src/main/res/drawable-hdpi/navigation_selector.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable-hdpi/todo_menu_icon.png b/app-examples/example-05-db/src/main/res/drawable-hdpi/todo_menu_icon.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable-hdpi/todo_menu_icon.png rename to app-examples/example-05-db/src/main/res/drawable-hdpi/todo_menu_icon.png diff --git a/app-examples/example-jv-06db/src/main/res/drawable-xxhdpi/logo.png b/app-examples/example-05-db/src/main/res/drawable-xxhdpi/logo.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable-xxhdpi/logo.png rename to app-examples/example-05-db/src/main/res/drawable-xxhdpi/logo.png diff --git a/app-examples/example-jv-06db/src/main/res/drawable/button_negative_disabled.xml b/app-examples/example-05-db/src/main/res/drawable/button_negative_disabled.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/button_negative_disabled.xml rename to app-examples/example-05-db/src/main/res/drawable/button_negative_disabled.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable/button_negative_enabled.xml b/app-examples/example-05-db/src/main/res/drawable/button_negative_enabled.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/button_negative_enabled.xml rename to app-examples/example-05-db/src/main/res/drawable/button_negative_enabled.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable/button_negative_selector.xml b/app-examples/example-05-db/src/main/res/drawable/button_negative_selector.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/button_negative_selector.xml rename to app-examples/example-05-db/src/main/res/drawable/button_negative_selector.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable/button_positive_disabled.xml b/app-examples/example-05-db/src/main/res/drawable/button_positive_disabled.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/button_positive_disabled.xml rename to app-examples/example-05-db/src/main/res/drawable/button_positive_disabled.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable/button_positive_enabled.xml b/app-examples/example-05-db/src/main/res/drawable/button_positive_enabled.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/button_positive_enabled.xml rename to app-examples/example-05-db/src/main/res/drawable/button_positive_enabled.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable/button_positive_selector.xml b/app-examples/example-05-db/src/main/res/drawable/button_positive_selector.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/button_positive_selector.xml rename to app-examples/example-05-db/src/main/res/drawable/button_positive_selector.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable/nav_bg_bossmode_off.xml b/app-examples/example-05-db/src/main/res/drawable/nav_bg_bossmode_off.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/nav_bg_bossmode_off.xml rename to app-examples/example-05-db/src/main/res/drawable/nav_bg_bossmode_off.xml diff --git a/app-examples/example-jv-06db/src/main/res/drawable/nav_bg_bossmode_on.xml b/app-examples/example-05-db/src/main/res/drawable/nav_bg_bossmode_on.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/drawable/nav_bg_bossmode_on.xml rename to app-examples/example-05-db/src/main/res/drawable/nav_bg_bossmode_on.xml diff --git a/app-examples/example-jv-06db/src/main/res/layout/common_activity_base.xml b/app-examples/example-05-db/src/main/res/layout/common_activity_base.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/layout/common_activity_base.xml rename to app-examples/example-05-db/src/main/res/layout/common_activity_base.xml diff --git a/app-examples/example-jv-06db/src/main/res/layout/common_content_singlepane.xml b/app-examples/example-05-db/src/main/res/layout/common_content_singlepane.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/layout/common_content_singlepane.xml rename to app-examples/example-05-db/src/main/res/layout/common_content_singlepane.xml diff --git a/app-examples/example-jv-06db/src/main/res/layout/common_nav_header.xml b/app-examples/example-05-db/src/main/res/layout/common_nav_header.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/layout/common_nav_header.xml rename to app-examples/example-05-db/src/main/res/layout/common_nav_header.xml diff --git a/app-examples/example-jv-06db/src/main/res/layout/design_navigation_menu_item.xml b/app-examples/example-05-db/src/main/res/layout/design_navigation_menu_item.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/layout/design_navigation_menu_item.xml rename to app-examples/example-05-db/src/main/res/layout/design_navigation_menu_item.xml diff --git a/app-examples/example-jv-06db/src/main/res/layout/fragment_todolist.xml b/app-examples/example-05-db/src/main/res/layout/fragment_todolist.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/layout/fragment_todolist.xml rename to app-examples/example-05-db/src/main/res/layout/fragment_todolist.xml diff --git a/app-examples/example-jv-06db/src/main/res/layout/fragment_todolist_listitem.xml b/app-examples/example-05-db/src/main/res/layout/fragment_todolist_listitem.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/layout/fragment_todolist_listitem.xml rename to app-examples/example-05-db/src/main/res/layout/fragment_todolist_listitem.xml diff --git a/app-examples/example-jv-06db/src/main/res/menu/menu_bossmode_off.xml b/app-examples/example-05-db/src/main/res/menu/menu_bossmode_off.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/menu/menu_bossmode_off.xml rename to app-examples/example-05-db/src/main/res/menu/menu_bossmode_off.xml diff --git a/app-examples/example-jv-06db/src/main/res/menu/menu_bossmode_on.xml b/app-examples/example-05-db/src/main/res/menu/menu_bossmode_on.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/menu/menu_bossmode_on.xml rename to app-examples/example-05-db/src/main/res/menu/menu_bossmode_on.xml diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app-examples/example-05-db/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app-examples/example-05-db/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app-examples/example-05-db/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app-examples/example-05-db/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-hdpi/ic_launcher.png b/app-examples/example-05-db/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-hdpi/ic_launcher.png rename to app-examples/example-05-db/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app-examples/example-05-db/src/main/res/mipmap-hdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-hdpi/ic_launcher_background.png rename to app-examples/example-05-db/src/main/res/mipmap-hdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app-examples/example-05-db/src/main/res/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-hdpi/ic_launcher_foreground.png rename to app-examples/example-05-db/src/main/res/mipmap-hdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-mdpi/ic_launcher.png b/app-examples/example-05-db/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-mdpi/ic_launcher.png rename to app-examples/example-05-db/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app-examples/example-05-db/src/main/res/mipmap-mdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-mdpi/ic_launcher_background.png rename to app-examples/example-05-db/src/main/res/mipmap-mdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app-examples/example-05-db/src/main/res/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-mdpi/ic_launcher_foreground.png rename to app-examples/example-05-db/src/main/res/mipmap-mdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xhdpi/ic_launcher.png b/app-examples/example-05-db/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xhdpi/ic_launcher.png rename to app-examples/example-05-db/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app-examples/example-05-db/src/main/res/mipmap-xhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xhdpi/ic_launcher_background.png rename to app-examples/example-05-db/src/main/res/mipmap-xhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app-examples/example-05-db/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png rename to app-examples/example-05-db/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app-examples/example-05-db/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to app-examples/example-05-db/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app-examples/example-05-db/src/main/res/mipmap-xxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xxhdpi/ic_launcher_background.png rename to app-examples/example-05-db/src/main/res/mipmap-xxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app-examples/example-05-db/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png rename to app-examples/example-05-db/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app-examples/example-05-db/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to app-examples/example-05-db/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app-examples/example-05-db/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png rename to app-examples/example-05-db/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png diff --git a/app-examples/example-jv-06db/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app-examples/example-05-db/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-jv-06db/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png rename to app-examples/example-05-db/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-jv-06db/src/main/res/values/colors.xml b/app-examples/example-05-db/src/main/res/values/colors.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/values/colors.xml rename to app-examples/example-05-db/src/main/res/values/colors.xml diff --git a/app-examples/example-jv-06db/src/main/res/values/dimens.xml b/app-examples/example-05-db/src/main/res/values/dimens.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/values/dimens.xml rename to app-examples/example-05-db/src/main/res/values/dimens.xml diff --git a/app-examples/example-jv-06db/src/main/res/values/strings.xml b/app-examples/example-05-db/src/main/res/values/strings.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/values/strings.xml rename to app-examples/example-05-db/src/main/res/values/strings.xml diff --git a/app-examples/example-jv-06db/src/main/res/values/styles.xml b/app-examples/example-05-db/src/main/res/values/styles.xml similarity index 100% rename from app-examples/example-jv-06db/src/main/res/values/styles.xml rename to app-examples/example-05-db/src/main/res/values/styles.xml diff --git a/app-examples/example-jv-06db/src/test/java/foo/bar/example/foredb/feature/todoitems/TodoItemsModelTest.java b/app-examples/example-05-db/src/test/java/foo/bar/example/foredb/feature/todoitems/TodoItemsModelTest.java similarity index 100% rename from app-examples/example-jv-06db/src/test/java/foo/bar/example/foredb/feature/todoitems/TodoItemsModelTest.java rename to app-examples/example-05-db/src/test/java/foo/bar/example/foredb/feature/todoitems/TodoItemsModelTest.java diff --git a/app-examples/example-kt-09compose/build.gradle.kts b/app-examples/example-06-compose/build.gradle.kts similarity index 52% rename from app-examples/example-kt-09compose/build.gradle.kts rename to app-examples/example-06-compose/build.gradle.kts index ddbd190b..fd99649b 100644 --- a/app-examples/example-kt-09compose/build.gradle.kts +++ b/app-examples/example-06-compose/build.gradle.kts @@ -2,13 +2,13 @@ import co.early.fore.Shared import co.early.fore.Shared.BuildTypes plugins { - alias(libs.plugins.androidApplication) - alias(libs.plugins.kotlinAndroid) - alias(libs.plugins.kotlinSerialization) - kotlin("kapt") + alias(libs.plugins.androidAppPlugin) + alias(libs.plugins.kotlinAndroidPlugin) + alias(libs.plugins.composeCompilerPlugin) + alias(libs.plugins.kotlinSerializationPlugin) + alias(libs.plugins.kotlinKaptPlugin) } - val appId = "foo.bar.example.forecompose" fun getTestBuildType(): String { @@ -19,7 +19,7 @@ println("[$appId testBuildType:${getTestBuildType()}]") kotlin { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(Shared.Versions.jvm_toolchain)) + languageVersion.set(JavaLanguageVersion.of(libs.versions.jvm.toolchain.get().toInt())) } } @@ -32,9 +32,6 @@ android { compose = true buildConfig = true } - composeOptions { - kotlinCompilerExtensionVersion = Shared.Versions.composeCompiler - } defaultConfig { applicationId = appId @@ -74,29 +71,44 @@ android { dependencies { if (Shared.Publish.use_published_version) { - implementation("co.early.fore:fore-kt-android:${Shared.Publish.published_fore_version_for_examples}") - implementation("co.early.fore:fore-kt-android-compose:${Shared.Versions.composeCompiler}") + implementation(libs.fore.core) + implementation(libs.fore.net) + implementation(libs.fore.compose) + testImplementation(libs.fore.test.fixtures) } else { - implementation(project(":fore-kt:fore-kt-android")) - implementation(project(":fore-kt:fore-kt-android-compose")) + implementation(project(":lib:fore-core")) + implementation(project(":lib:fore-net")) + implementation(project(":lib:fore-compose")) + testImplementation(project(":lib:fore-test-fixtures")) } // persistence - implementation("co.early.persista:persista:${Shared.Versions.perSista}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Shared.Versions.kotlinxSerializationJson}") + implementation(libs.persista) + implementation(libs.kotlinx.serialization) // compose - implementation("androidx.activity:activity-compose:${Shared.Versions.activityCompose}") - implementation(platform("androidx.compose:compose-bom:${Shared.Versions.composeBom}")) - implementation("androidx.compose.material3:material3") - implementation("androidx.compose.ui:ui") - implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.ui:ui-tooling") - debugImplementation("androidx.compose.ui:ui-tooling-preview") - debugImplementation("androidx.compose.ui:ui-tooling") - debugImplementation("androidx.compose.ui:ui-test-manifest") + +// implementation("androidx.compose.ui:ui:1.6.0") // Update to latest +// implementation("androidx.compose.runtime:runtime:1.6.0") + + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.material3) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.ui.tooling) + implementation(libs.androidx.wear.compose.tooling) + implementation(libs.androidx.wear.tooling.preview) + + implementation(libs.slf4j.nop) // to get rid of the slf4 warning that comes from ktor + + debugImplementation(libs.androidx.ui.tooling.preview) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + debugImplementation(libs.androidx.wear.compose.tooling) + debugImplementation(libs.androidx.wear.tooling.preview) //testing - testImplementation("junit:junit:${Shared.Versions.junit}") - testImplementation("io.mockk:mockk:${Shared.Versions.mockk}") + testImplementation(libs.junit) + testImplementation(libs.mockk) } diff --git a/app-examples/example-kt-09compose/src/main/AndroidManifest.xml b/app-examples/example-06-compose/src/main/AndroidManifest.xml similarity index 100% rename from app-examples/example-kt-09compose/src/main/AndroidManifest.xml rename to app-examples/example-06-compose/src/main/AndroidManifest.xml diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/App.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/App.kt similarity index 100% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/App.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/App.kt diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/OG.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/OG.kt similarity index 91% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/OG.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/OG.kt index 6c32b5cf..e8f4b2d8 100644 --- a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/OG.kt +++ b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/OG.kt @@ -1,12 +1,11 @@ package foo.bar.example.forecompose import android.app.Application -import co.early.fore.kt.core.delegate.DebugDelegateDefault -import co.early.fore.kt.core.delegate.Fore -import co.early.fore.ui.size.* +import co.early.fore.core.delegate.DebugDelegateDefault +import co.early.fore.core.delegate.Fore import co.early.persista.PerSista import foo.bar.example.forecompose.feature.counter.CounterModel -import java.util.* +import okio.Path.Companion.toOkioPath /** * Copyright © 2015-2023 early.co. All rights reserved. @@ -26,7 +25,7 @@ object OG { val logger = Fore.getLogger() val perSista = PerSista( - dataDirectory = application.filesDir, + dataPath = application.filesDir.toOkioPath(), logger = logger, ) diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/feature/counter/CounterModel.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/feature/counter/CounterModel.kt similarity index 91% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/feature/counter/CounterModel.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/feature/counter/CounterModel.kt index 5cacd56f..4e521a5f 100644 --- a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/feature/counter/CounterModel.kt +++ b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/feature/counter/CounterModel.kt @@ -1,9 +1,9 @@ package foo.bar.example.forecompose.feature.counter -import co.early.fore.kt.core.logging.Logger +import co.early.fore.core.logging.Logger import co.early.fore.core.observer.Observable -import co.early.fore.kt.core.coroutine.launchIO -import co.early.fore.kt.core.observer.ObservableImp +import co.early.fore.core.coroutine.launchIO +import co.early.fore.core.observer.ObservableImp import co.early.persista.PerSista import kotlinx.coroutines.delay diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/feature/counter/CounterState.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/feature/counter/CounterState.kt similarity index 100% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/feature/counter/CounterState.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/feature/counter/CounterState.kt diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/MainActivity.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/MainActivity.kt similarity index 100% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/MainActivity.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/MainActivity.kt diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/common/LabelFormatting.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/common/LabelFormatting.kt similarity index 100% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/common/LabelFormatting.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/common/LabelFormatting.kt diff --git a/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/common/StateView.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/common/StateView.kt new file mode 100644 index 00000000..b14e2166 --- /dev/null +++ b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/common/StateView.kt @@ -0,0 +1,134 @@ +package foo.bar.example.forecompose.ui.screens.common + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.sp +import co.early.fore.ui.size.WidthBasedTextUnit +import co.early.fore.ui.size.WindowSize +import foo.bar.example.forecompose.ui.screens.home.DisplayToggleView + + +/** + * This is mainly to demonstrate that [ObservableGroup.observeAsState()] works as intended, + * as the app is backgrounded or the composable is hidden, the logs show the fore + * observer being added / removed as appropriate. See the observeAsState code comments for a + * full explanation + */ +@Composable +fun ShowHideWrapper(state: Any, size: WindowSize, content: @Composable () -> Unit) { + + val show = remember { mutableStateOf(true) } + val btnColor by animateColorAsState( + targetValue = if (show.value) Color.Red else Color.Green, + animationSpec = tween(durationMillis = 500), + ) + + AnimatedVisibility( + visible = show.value, + enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(), + exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(), + ) { + content() + } + + val stateFontSize = WidthBasedTextUnit( + xs = 12.sp, + m = 20.sp, + l = 35.sp + ) + + val stateAsString = state.prettyPrint() + + AnimatedVisibility( + visible = !show.value, + enter = fadeIn(), + exit = fadeOut(), + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stateAsString, + style = TextStyle(fontSize = stateFontSize(size)) + ) + } + } + + Box( + modifier = Modifier + .fillMaxSize() + ) { + DisplayToggleView( + size = size, + displayed = show.value, + btnColor = btnColor, + toggleDisplayCallback = { show.value = !show.value }, + ) + } +} + +// https://gist.github.com/Mayankmkh/92084bdf2b59288d3e74c3735cccbf9f +fun Any.prettyPrint(): String { + + var indentLevel = 0 + val indentWidth = 4 + + fun padding() = "".padStart(indentLevel * indentWidth) + + val toString = toString()//.replace("foo.bar.clean.domain.features.", "") + + val stringBuilder = StringBuilder(toString.length) + + var i = 0 + while (i < toString.length) { + when (val char = toString[i]) { + '(', '[', '{' -> { + indentLevel++ + stringBuilder.appendLine(char).append(padding()) + } + + ')', ']', '}' -> { + indentLevel-- + stringBuilder.appendLine().append(padding()).append(char) + } + + ',' -> { + stringBuilder.appendLine(char).append(padding()) + // ignore space after comma as we have added a newline + val nextChar = toString.getOrElse(i + 1) { char } + if (nextChar == ' ') i++ + } + + else -> { + stringBuilder.append(char) + } + } + i++ + } + + return stringBuilder.toString().replace("=", " = ") +} diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/home/HomeScreen.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/home/HomeScreen.kt similarity index 86% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/home/HomeScreen.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/home/HomeScreen.kt index a325873e..d1dc0152 100644 --- a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/home/HomeScreen.kt +++ b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/home/HomeScreen.kt @@ -22,12 +22,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.* import co.early.fore.compose.observeAsState -import co.early.fore.kt.core.delegate.Fore +import co.early.fore.core.delegate.Fore import co.early.fore.ui.size.* import foo.bar.example.forecompose.OG import foo.bar.example.forecompose.R import foo.bar.example.forecompose.feature.counter.CounterModel import foo.bar.example.forecompose.feature.counter.CounterState +import foo.bar.example.forecompose.ui.screens.common.ShowHideWrapper import foo.bar.example.forecompose.ui.screens.common.toLabel @Composable @@ -38,10 +39,9 @@ fun HomeScreen( Fore.getLogger().i("HomeScreen $size") - showHideWrapper(size) { - - val counterState by counterModel.observeAsState("FOO") { counterModel.state } + val counterState by counterModel.observeAsState("FOO") { counterModel.state } + ShowHideWrapper(counterState, size) { HomeView( size = size, counterState = counterState, @@ -268,39 +268,3 @@ fun BoxScope.MiniDiagnostics(size: WindowSize) { style = TextStyle(color = Color.Blue, fontSize = diagnosticsFontSize) ) } - -/** - * This is mainly to demonstrate that [ObservableGroup.observeAsState()] works as intended, - * as the app is backgrounded or the composable is hidden, the logs show the fore - * observer being added / removed as appropriate. See the observeAsState code comments for a - * full explanation - */ -@Composable -fun showHideWrapper(size: WindowSize, content: @Composable () -> Unit) { - - val show = remember { mutableStateOf(true) } - val btnColor by animateColorAsState( - targetValue = if (show.value) Color.Red else Color.Green, - animationSpec = tween(durationMillis = 500), - ) - - AnimatedVisibility( - visible = show.value, - enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(), - exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(), - ) { - content() - } - - Box( - modifier = Modifier - .fillMaxSize() - ) { - DisplayToggleView( - size = size, - displayed = show.value, - btnColor = btnColor, - toggleDisplayCallback = { show.value = !show.value }, - ) - } -} diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/home/Previews.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/home/Previews.kt similarity index 66% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/home/Previews.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/home/Previews.kt index 4c3d1f00..8ce634b6 100644 --- a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/screens/home/Previews.kt +++ b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/screens/home/Previews.kt @@ -1,17 +1,40 @@ package foo.bar.example.forecompose.ui.screens.home +import android.annotation.SuppressLint import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpSize -import co.early.fore.ui.size.* +import androidx.wear.tooling.preview.devices.WearDevices +import co.early.fore.ui.size.WindowSize +import co.early.fore.ui.size.hL_high +import co.early.fore.ui.size.hL_low +import co.early.fore.ui.size.hM_high +import co.early.fore.ui.size.hM_low +import co.early.fore.ui.size.hS_high +import co.early.fore.ui.size.hS_low +import co.early.fore.ui.size.hXL_high +import co.early.fore.ui.size.hXL_low +import co.early.fore.ui.size.hXS_high +import co.early.fore.ui.size.hXS_low +import co.early.fore.ui.size.toWindowSize +import co.early.fore.ui.size.wL_high +import co.early.fore.ui.size.wL_low +import co.early.fore.ui.size.wM_high +import co.early.fore.ui.size.wM_low +import co.early.fore.ui.size.wS_high +import co.early.fore.ui.size.wS_low +import co.early.fore.ui.size.wXL_high +import co.early.fore.ui.size.wXL_low +import co.early.fore.ui.size.wXS_high +import co.early.fore.ui.size.wXS_low import foo.bar.example.forecompose.feature.counter.CounterState import foo.bar.example.forecompose.ui.theme.ComposeTheme +@SuppressLint("UnusedBoxWithConstraintsScope") @Composable fun PreviewWithWindowSize(isRound: Boolean = false, content: @Composable (size: WindowSize) -> Unit) { ComposeTheme { @@ -44,7 +67,7 @@ fun MyPreviews() { } } -@Preview(showBackground = true, device = Devices.WEAR_OS_LARGE_ROUND) +@Preview(showBackground = true, device = WearDevices.LARGE_ROUND) @Composable fun MyRoundPreview() { PreviewWithWindowSize(isRound = true) { diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/theme/Color.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/theme/Color.kt similarity index 100% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/theme/Color.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/theme/Color.kt diff --git a/app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/theme/Theme.kt b/app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/theme/Theme.kt similarity index 100% rename from app-examples/example-kt-09compose/src/main/java/foo/bar/example/forecompose/ui/theme/Theme.kt rename to app-examples/example-06-compose/src/main/kotlin/foo/bar/example/forecompose/ui/theme/Theme.kt diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app-examples/example-06-compose/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app-examples/example-06-compose/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app-examples/example-06-compose/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app-examples/example-06-compose/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher.png b/app-examples/example-06-compose/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher.png rename to app-examples/example-06-compose/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app-examples/example-06-compose/src/main/res/mipmap-hdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_background.png rename to app-examples/example-06-compose/src/main/res/mipmap-hdpi/ic_launcher_background.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app-examples/example-06-compose/src/main/res/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png rename to app-examples/example-06-compose/src/main/res/mipmap-hdpi/ic_launcher_foreground.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher.png b/app-examples/example-06-compose/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher.png rename to app-examples/example-06-compose/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app-examples/example-06-compose/src/main/res/mipmap-mdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_background.png rename to app-examples/example-06-compose/src/main/res/mipmap-mdpi/ic_launcher_background.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app-examples/example-06-compose/src/main/res/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png rename to app-examples/example-06-compose/src/main/res/mipmap-mdpi/ic_launcher_foreground.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher.png b/app-examples/example-06-compose/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher.png rename to app-examples/example-06-compose/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app-examples/example-06-compose/src/main/res/mipmap-xhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_background.png rename to app-examples/example-06-compose/src/main/res/mipmap-xhdpi/ic_launcher_background.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app-examples/example-06-compose/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png rename to app-examples/example-06-compose/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app-examples/example-06-compose/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to app-examples/example-06-compose/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app-examples/example-06-compose/src/main/res/mipmap-xxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_background.png rename to app-examples/example-06-compose/src/main/res/mipmap-xxhdpi/ic_launcher_background.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app-examples/example-06-compose/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png rename to app-examples/example-06-compose/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app-examples/example-06-compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to app-examples/example-06-compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app-examples/example-06-compose/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png rename to app-examples/example-06-compose/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png diff --git a/app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app-examples/example-06-compose/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from app-examples/example-kt-01reactiveui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png rename to app-examples/example-06-compose/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/app-examples/example-kt-09compose/src/main/res/values/strings.xml b/app-examples/example-06-compose/src/main/res/values/strings.xml similarity index 100% rename from app-examples/example-kt-09compose/src/main/res/values/strings.xml rename to app-examples/example-06-compose/src/main/res/values/strings.xml diff --git a/app-examples/example-kt-09compose/src/main/res/values/themes.xml b/app-examples/example-06-compose/src/main/res/values/themes.xml similarity index 100% rename from app-examples/example-kt-09compose/src/main/res/values/themes.xml rename to app-examples/example-06-compose/src/main/res/values/themes.xml diff --git a/app-examples/example-kt-09compose/src/test/java/foo/bar/example/forecompose/feature/counter/CounterModelTest.kt b/app-examples/example-06-compose/src/test/kotlin/foo/bar/example/forecompose/feature/counter/CounterModelTest.kt similarity index 93% rename from app-examples/example-kt-09compose/src/test/java/foo/bar/example/forecompose/feature/counter/CounterModelTest.kt rename to app-examples/example-06-compose/src/test/kotlin/foo/bar/example/forecompose/feature/counter/CounterModelTest.kt index e292abd5..289955b7 100644 --- a/app-examples/example-kt-09compose/src/test/java/foo/bar/example/forecompose/feature/counter/CounterModelTest.kt +++ b/app-examples/example-06-compose/src/test/kotlin/foo/bar/example/forecompose/feature/counter/CounterModelTest.kt @@ -1,16 +1,17 @@ package foo.bar.example.forecompose.feature.counter -import co.early.fore.kt.core.logging.Logger -import co.early.fore.kt.core.logging.SystemLogger +import co.early.fore.core.logging.Logger +import co.early.fore.core.logging.SystemLogger import co.early.fore.core.observer.Observer -import co.early.fore.kt.core.delegate.TestDelegateDefault -import co.early.fore.kt.core.delegate.Fore +import co.early.fore.core.delegate.TestDelegateDefault +import co.early.fore.core.delegate.Fore import co.early.persista.PerSista import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK import io.mockk.verify import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher +import okio.Path.Companion.toOkioPath import org.junit.Assert import org.junit.Before import org.junit.Test @@ -159,10 +160,10 @@ class CounterModelTest { dataFolder.create() return CounterModel( PerSista( - dataDirectory = dataFolder.newFolder(), + dataPath = dataFolder.newFolder().toOkioPath(), mainDispatcher = testDispatcher, writeReadDispatcher = testDispatcher, - logger = Fore.getLogger() + logger = Fore.getLogger(), ), Fore.getLogger(), ) diff --git a/app-examples/example-07-kmp/.gitignore b/app-examples/example-07-kmp/.gitignore new file mode 100644 index 00000000..e510fa99 --- /dev/null +++ b/app-examples/example-07-kmp/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +.idea +.DS_Store +build +captures +.externalNativeBuild +.cxx +local.properties +xcuserdata \ No newline at end of file diff --git a/app-examples/example-07-kmp/androidApp/build.gradle.kts b/app-examples/example-07-kmp/androidApp/build.gradle.kts new file mode 100644 index 00000000..49805197 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlinKaptPlugin) + alias(libs.plugins.kotlinSerializationPlugin) +} + +android { + namespace = "com.kmpfoo.android" + compileSdk = 35 + defaultConfig { + applicationId = "com.kmpfoo.android" + minSdk = 21 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + } + buildFeatures { + compose = true + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.shared) + implementation(libs.compose.ui) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + implementation(libs.androidx.activity.compose) + debugImplementation(libs.compose.ui.tooling) + debugImplementation(libs.androidx.wear.compose.tooling) + debugImplementation(libs.androidx.wear.tooling.preview) +} \ No newline at end of file diff --git a/app-examples/example-kt-02coroutine/src/main/AndroidManifest.xml b/app-examples/example-07-kmp/androidApp/src/main/AndroidManifest.xml similarity index 63% rename from app-examples/example-kt-02coroutine/src/main/AndroidManifest.xml rename to app-examples/example-07-kmp/androidApp/src/main/AndroidManifest.xml index d3214061..e9e186f9 100644 --- a/app-examples/example-kt-02coroutine/src/main/AndroidManifest.xml +++ b/app-examples/example-07-kmp/androidApp/src/main/AndroidManifest.xml @@ -2,19 +2,17 @@ - + android:name=".App" + android:theme="@style/Theme.AppTheme"> + - - - + \ No newline at end of file diff --git a/app-examples/example-kt-04retrofit/src/main/java/foo/bar/example/foreretrofitkt/App.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/App.kt similarity index 75% rename from app-examples/example-kt-04retrofit/src/main/java/foo/bar/example/foreretrofitkt/App.kt rename to app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/App.kt index c4bd32c3..bdb940e0 100644 --- a/app-examples/example-kt-04retrofit/src/main/java/foo/bar/example/foreretrofitkt/App.kt +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/App.kt @@ -1,10 +1,9 @@ -package foo.bar.example.foreretrofitkt - +package com.kmpfoo.android import android.app.Application /** - * Copyright © 2019 early.co. All rights reserved. + * Copyright © 2015-2021 early.co. All rights reserved. */ class App : Application() { diff --git a/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/OG.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/OG.kt new file mode 100644 index 00000000..1caaa9f8 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/OG.kt @@ -0,0 +1,88 @@ +package com.kmpfoo.android + +import android.app.Application +import android.content.pm.ApplicationInfo +import co.early.fore.core.delegate.DebugDelegateDefault +import co.early.fore.core.delegate.Fore +import co.early.persista.PerSista +import com.kmpfoo.feature.counter.CounterModel +import okio.Path.Companion.toOkioPath +import kotlin.reflect.KClass + +/** + * Copyright © 2015-2023 early.co. All rights reserved. + */ +@Suppress("UNUSED_PARAMETER") +object OG { + + private var initialized = false + private val dependencies = HashMap, Any>() + + fun setApplication(application: Application) { + + val isDebug = application.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0 + + // create dependency graph + if (isDebug) { + Fore.setDelegate(DebugDelegateDefault("foo_")) + } + val logger = Fore.getLogger() + + val perSista = PerSista( + dataPath = application.filesDir.toOkioPath(), + logger = logger, + ) + + val counterModel = CounterModel( + perSista = perSista, + logger = logger, + ) + + // add models to the dependencies map if you will need them later + dependencies[CounterModel::class] = counterModel + } + + fun init() { + if (!initialized) { + initialized = true + + // run any necessary initialization code once object graph has been created here + +// // just in case you need to, but defaults are recommended +// ForeSize.overrideBreakPoints( +// ViewPortBreakPoints( +// widthSDpBelow = 400.dp, +// widthMDpBelow = 500.dp, +// ) +// ) + + } + } + + /** + * This is how dependencies get injected, typically an Activity/Fragment/View will call this + * during the onCreate()/onCreateView()/onFinishInflate() method respectively for each of the + * dependencies it needs. + * + * Can use a DI library for similar behaviour using annotations + * + * Will return mocks if they have been set previously in putMock() + * + * + * Call it like this: + * + * + * yourModel = OG[YourModel::class.java] + * + * + * If you want to more tightly scoped object, one way is to pass a factory class here and create + * an instance where you need it + * + */ + @Suppress("UNCHECKED_CAST") + operator fun get(model: KClass): T = dependencies[model] as T + + fun putMock(clazz: KClass, instance: T) { + dependencies[clazz] = instance as Any + } +} diff --git a/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/MainActivity.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/MainActivity.kt new file mode 100644 index 00000000..82e2f183 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/MainActivity.kt @@ -0,0 +1,25 @@ +package com.kmpfoo.android.ui + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Surface +import androidx.compose.ui.Modifier +import co.early.fore.ui.size.rememberWindowSize +import com.kmpfoo.android.ui.screens.home.HomeScreen +import com.kmpfoo.android.ui.theme.ComposeTheme + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + ComposeTheme { + Surface( modifier = Modifier.fillMaxSize()) { + HomeScreen(rememberWindowSize()) + } + } + } + } +} diff --git a/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/common/LabelFormatting.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/common/LabelFormatting.kt new file mode 100644 index 00000000..75a290fd --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/common/LabelFormatting.kt @@ -0,0 +1,51 @@ +package com.kmpfoo.android.ui.screens.common + +import co.early.fore.ui.size.* + +fun Width.toLabel(extended: Boolean = false): String { + return when(this){ + Width.XSmall -> if (extended) { "Width:XSmall" } else "W:XS" + Width.Small -> if (extended) { "Width:Small" } else "W:S" + Width.Medium -> if (extended) { "Width:Medium" } else "W:M" + Width.Large -> if (extended) { "Width:Large" } else "W:L" + Width.XLarge -> if (extended) { "Width:XLarge" } else "W:XL" + } +} + +fun Height.toLabel(extended: Boolean = false): String { + return when(this){ + Height.XSmall -> if (extended) { "Height:XSmall" } else "H:XS" + Height.Small -> if (extended) { "Height:Small" } else "H:S" + Height.Medium -> if (extended) { "Height:Medium" } else "H:M" + Height.Large -> if (extended) { "Height:Large" } else "H:L" + Height.XLarge -> if (extended) { "Height:XLarge" } else "H:XL" + } +} + +fun MinDim.toLabel(extended: Boolean = false): String { + return when(this){ + MinDim.XSmall -> if (extended) { "MinDim:XSmall" } else "M:XS" + MinDim.Small -> if (extended) { "MinDim:Small" } else "M:S" + MinDim.Medium -> if (extended) { "MinDim:Medium" } else "M:M" + MinDim.Large -> if (extended) { "MinDim:Large" } else "M:L" + MinDim.XLarge -> if (extended) { "MinDim:XLarge" } else "M:XL" + } +} + +fun Aspect.toLabel(extended: Boolean = false): String { + return when(this){ + Aspect.Port -> if (extended) { "Aspect:Port" } else "A:P" + Aspect.Land -> if (extended) { "Aspect:Land" } else "A:L" + Aspect.Squarish -> if (extended) { "Aspect:Squarish" } else "A:S" + } +} + +fun WindowSize.toLabel(extended: Boolean = false, multipleLines: Boolean = false): String { + return "${dpSize.width}:${dpSize.height}|" + + (if (multipleLines) "\n" else "") + + "${width.toLabel(extended)}|" + + "${height.toLabel(extended)}|" + + (if (multipleLines) "\n" else "") + + "${minDim.toLabel(extended)}|" + + aspect.toLabel(extended) +} diff --git a/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/home/HomeScreen.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/home/HomeScreen.kt new file mode 100644 index 00000000..e74b3587 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/home/HomeScreen.kt @@ -0,0 +1,375 @@ +package com.kmpfoo.android.ui.screens.home + +import androidx.annotation.StringRes +import androidx.compose.animation.* +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.* +import co.early.fore.compose.observeAsState +import co.early.fore.core.delegate.Fore +import co.early.fore.ui.size.* +import com.kmpfoo.android.OG +import com.kmpfoo.android.R +import com.kmpfoo.feature.counter.CounterModel +import com.kmpfoo.feature.counter.CounterState +import com.kmpfoo.android.ui.screens.common.toLabel + +@Composable +fun HomeScreen( + size: WindowSize = WindowSize(), + counterModel: CounterModel = OG[CounterModel::class], +) { + + Fore.getLogger().i("HomeScreen $size") + + val counterState by counterModel.observeAsState("FOO") { counterModel.state } + + ShowHideWrapper(counterState, size) { + + HomeView( + size = size, + counterState = counterState, + increaseCallback = { counterModel.increase() }, + decreaseCallback = { counterModel.decrease() }, + ) + } +} + +@Composable +fun HomeView( + size: WindowSize, + counterState: CounterState, + increaseCallback: () -> Unit = {}, + decreaseCallback: () -> Unit = {}, +) { + + Fore.getLogger().i("HomeView") + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CounterView( + size = size, + counterState = counterState, + increaseCallback = { increaseCallback() }, + decreaseCallback = { decreaseCallback() }, + ) + } + Box( + modifier = Modifier + .fillMaxSize() + ) { + DiagnosticInfo(size) + } +} + +@OptIn(ExperimentalAnimationApi::class) +@Composable +fun CounterView( + size: WindowSize, + counterState: CounterState, + increaseCallback: () -> Unit, + decreaseCallback: () -> Unit, +) { + + Fore.getLogger().i("CounterView") + + val minimumDimension = size.dpSize.minimumDimension() + val borderThickness = minimumDimension * 0.1f + val boxHeight = minimumDimension * 0.5f + val numberFontSize = (minimumDimension / 5f).value.sp + val buttonFontSize = (minimumDimension / 8f).value.sp + val buttonSize = max(borderThickness * 3, 50.dp) + val color = WidthBasedValue( + xs = Color.Red, + s = Color.Green, + m = Color.Blue, + l = Color.Magenta, + xl = Color.Gray + ) + val shape = AspectBasedValue( + port = CircleShape, + land = CircleShape, + squarish = RectangleShape + ) + + Box( + modifier = Modifier + .fillMaxWidth(0.9f) + .height(boxHeight) + ) { + + Box( + modifier = Modifier + .fillMaxSize() + .padding(start = borderThickness, end = borderThickness) + .border(width = borderThickness, color = color(size), shape = shape(size)), + ) + + Box(modifier = Modifier.fillMaxSize()) { + + CustomButton( + Modifier.align(Alignment.CenterStart), + R.string.decrease, + counterState.canDecrease(), + buttonSize, + buttonFontSize, + decreaseCallback, + ) + + if (counterState.loading) { + CircularProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .size(borderThickness), + ) + } else { + Text( + modifier = Modifier + .align(Alignment.Center) + .padding(borderThickness / 2), + text = counterState.amount.toString(), + style = TextStyle(fontSize = numberFontSize) + ) + } + + CustomButton( + Modifier.align(Alignment.CenterEnd), + R.string.increase, + counterState.canIncrease(), + buttonSize, + buttonFontSize, + increaseCallback, + ) + } + } +} + +@Composable +fun CustomButton( + modifier: Modifier = Modifier, + @StringRes labelResId: Int, + enabled: Boolean, + buttonSize: Dp, + buttonFontSize: TextUnit, + callback: () -> Unit, +) { + + Fore.getLogger().i("CustomButton") + + Button( + modifier = modifier.size(buttonSize), + onClick = { callback() }, + enabled = enabled + ) { + Text( + text = stringResource(id = labelResId), + style = TextStyle(fontSize = buttonFontSize) + ) + } +} + +@Composable +fun BoxScope.DisplayToggleView( + size: WindowSize, + btnColor: Color, + displayed: Boolean, + toggleDisplayCallback: () -> Unit, +) { + + Fore.getLogger().i("DisplayToggleView") + + val minimumDimension = size.dpSize.minimumDimension() + val toggleBtnFontSize = (minimumDimension / 30f).value.sp + val label = stringResource(id = if (displayed) R.string.hide else R.string.show) + val alignment = if (size.isRound) Alignment.TopCenter else Alignment.TopEnd + + Button( + modifier = Modifier.align(alignment), + colors = ButtonDefaults.textButtonColors(contentColor = btnColor), + onClick = { toggleDisplayCallback() }, + shape = ButtonDefaults.textShape, + ) { + Text( + text = label, + style = TextStyle(fontSize = toggleBtnFontSize), + ) + } +} + +@Composable +fun BoxScope.DiagnosticInfo(size: WindowSize) { + // Note: you don't really need two different composables here + // as they are so similar, this code serves as the "adaptive" + // vs "responsive" example. In this case "adaptive" meaning to + // display a completely different UI based on the size class + // see: https://dev.to/erdo/jetpack-compose-and-windowsize-classes-gb4 + WidthBasedComposable( + xs = { sz -> MiniDiagnostics(sz) }, + m = { sz -> MiniDiagnostics(sz) }, + l = { sz -> RegularDiagnostics(sz) }, + )(size) +} + +@Composable +fun BoxScope.RegularDiagnostics(size: WindowSize) { + + Fore.getLogger().i("RegularDiagnostics ${size.toLabel()}") + + val diagnosticsFontSize = (size.dpSize.width / 60f).value.sp + val alignment = if (size.isRound) Alignment.BottomCenter else Alignment.BottomStart + val diagnosticText = size.toLabel(extended = true, multipleLines = size.isRound) + + Text( + modifier = Modifier + .align(alignment) + .padding(start = 10.dp, end = 10.dp) + .background(color = Color.Yellow), + text = diagnosticText, + style = TextStyle(color = Color.Red, fontSize = diagnosticsFontSize) + ) +} + +@Composable +fun BoxScope.MiniDiagnostics(size: WindowSize) { + + Fore.getLogger().i("MiniDiagnostics ${size.toLabel()}") + + val diagnosticsFontSize = (size.dpSize.width / 30f).value.sp + val alignment = if (size.isRound) Alignment.BottomCenter else Alignment.BottomStart + val diagnosticText = size.toLabel(multipleLines = size.isRound) + + Text( + modifier = Modifier + .align(alignment) + .padding(start = 10.dp, end = 10.dp) + .background(color = Color.Yellow), + text = diagnosticText, + style = TextStyle(color = Color.Blue, fontSize = diagnosticsFontSize) + ) +} + +/** + * This is mainly to demonstrate that [ObservableGroup.observeAsState()] works as intended, + * as the app is backgrounded or the composable is hidden, the logs show the fore + * observer being added / removed as appropriate. See the observeAsState code comments for a + * full explanation + */ +@Composable +fun ShowHideWrapper(state: Any, size: WindowSize, content: @Composable () -> Unit) { + + val show = remember { mutableStateOf(true) } + val btnColor by animateColorAsState( + targetValue = if (show.value) Color.Red else Color.Green, + animationSpec = tween(durationMillis = 500), + ) + + AnimatedVisibility( + visible = show.value, + enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(), + exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(), + ) { + content() + } + + val stateFontSize = WidthBasedTextUnit( + xs = 12.sp, + m = 20.sp, + l = 35.sp + ) + + val stateAsString = state.prettyPrint() + + AnimatedVisibility( + visible = !show.value, + enter = fadeIn(), + exit = fadeOut(), + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stateAsString, + style = TextStyle(fontSize = stateFontSize(size)) + ) + } + } + + Box( + modifier = Modifier + .fillMaxSize() + ) { + DisplayToggleView( + size = size, + displayed = show.value, + btnColor = btnColor, + toggleDisplayCallback = { show.value = !show.value }, + ) + } +} + +// https://gist.github.com/Mayankmkh/92084bdf2b59288d3e74c3735cccbf9f +fun Any.prettyPrint(): String { + + var indentLevel = 0 + val indentWidth = 4 + + fun padding() = "".padStart(indentLevel * indentWidth) + + val toString = toString()//.replace("foo.bar.clean.domain.features.", "") + + val stringBuilder = StringBuilder(toString.length) + + var i = 0 + while (i < toString.length) { + when (val char = toString[i]) { + '(', '[', '{' -> { + indentLevel++ + stringBuilder.appendLine(char).append(padding()) + } + + ')', ']', '}' -> { + indentLevel-- + stringBuilder.appendLine().append(padding()).append(char) + } + + ',' -> { + stringBuilder.appendLine(char).append(padding()) + // ignore space after comma as we have added a newline + val nextChar = toString.getOrElse(i + 1) { char } + if (nextChar == ' ') i++ + } + + else -> { + stringBuilder.append(char) + } + } + i++ + } + + return stringBuilder.toString().replace("=", " = ") +} \ No newline at end of file diff --git a/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/home/Previews.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/home/Previews.kt new file mode 100644 index 00000000..63e3ed02 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/screens/home/Previews.kt @@ -0,0 +1,76 @@ +package com.kmpfoo.android.ui.screens.home + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpSize +import androidx.wear.tooling.preview.devices.WearDevices +import co.early.fore.ui.size.WindowSize +import co.early.fore.ui.size.hL_high +import co.early.fore.ui.size.hL_low +import co.early.fore.ui.size.hM_high +import co.early.fore.ui.size.hM_low +import co.early.fore.ui.size.hS_high +import co.early.fore.ui.size.hS_low +import co.early.fore.ui.size.hXL_high +import co.early.fore.ui.size.hXL_low +import co.early.fore.ui.size.hXS_high +import co.early.fore.ui.size.hXS_low +import co.early.fore.ui.size.toWindowSize +import co.early.fore.ui.size.wL_high +import co.early.fore.ui.size.wL_low +import co.early.fore.ui.size.wM_high +import co.early.fore.ui.size.wM_low +import co.early.fore.ui.size.wS_high +import co.early.fore.ui.size.wS_low +import co.early.fore.ui.size.wXL_high +import co.early.fore.ui.size.wXL_low +import co.early.fore.ui.size.wXS_high +import co.early.fore.ui.size.wXS_low +import com.kmpfoo.feature.counter.CounterState +import com.kmpfoo.android.ui.theme.ComposeTheme + +@SuppressLint("UnusedBoxWithConstraintsScope") +@Composable +fun PreviewWithWindowSize(isRound: Boolean = false, content: @Composable (size: WindowSize) -> Unit) { + ComposeTheme { + BoxWithConstraints( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + content(DpSize(maxWidth, maxHeight).toWindowSize(isRound = isRound)) + } + } +} + +@Preview(showBackground = true, widthDp = wXS_low, heightDp = hXS_low) +@Preview(showBackground = true, widthDp = wXS_high, heightDp = hXS_high) +@Preview(showBackground = true, widthDp = wS_low, heightDp = hS_low) +@Preview(showBackground = true, widthDp = wS_high, heightDp = hS_high) +@Preview(showBackground = true, widthDp = wM_low, heightDp = hM_low) +@Preview(showBackground = true, widthDp = wM_high, heightDp = hM_high) +@Preview(showBackground = true, widthDp = wL_low, heightDp = hL_low) +@Preview(showBackground = true, widthDp = wL_high, heightDp = hL_high) +@Preview(showBackground = true, widthDp = wXL_low, heightDp = hXL_low) +@Preview(showBackground = true, widthDp = wXL_high, heightDp = hXL_high) +@Preview(showBackground = true, widthDp = wL_high, heightDp = hXS_low) +@Preview(showBackground = true, widthDp = wXS_low, heightDp = hM_high) +@Composable +fun MyPreviews() { + PreviewWithWindowSize { + HomeView(size = it, counterState = CounterState(3)) + } +} + +@Preview(showBackground = true, device = WearDevices.LARGE_ROUND) +@Composable +fun MyRoundPreview() { + PreviewWithWindowSize(isRound = true) { + HomeView(size = it, counterState = CounterState(3)) + } +} diff --git a/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/theme/Color.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/theme/Color.kt new file mode 100644 index 00000000..d92e7d01 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/theme/Color.kt @@ -0,0 +1,71 @@ +package com.kmpfoo.android.ui.theme + +import androidx.compose.ui.graphics.Color + +/** + * https://m3.material.io/theme-builder + */ + +val md_theme_light_primary = Color(0xFF5754A8) +val md_theme_light_onPrimary = Color(0xFFFFFFFF) +val md_theme_light_primaryContainer = Color(0xFFE2DFFF) +val md_theme_light_onPrimaryContainer = Color(0xFF110563) +val md_theme_light_secondary = Color(0xFF4F56A9) +val md_theme_light_onSecondary = Color(0xFFFFFFFF) +val md_theme_light_secondaryContainer = Color(0xFFE0E0FF) +val md_theme_light_onSecondaryContainer = Color(0xFF030865) +val md_theme_light_tertiary = Color(0xFF006590) +val md_theme_light_onTertiary = Color(0xFFFFFFFF) +val md_theme_light_tertiaryContainer = Color(0xFFC8E6FF) +val md_theme_light_onTertiaryContainer = Color(0xFF001E2F) +val md_theme_light_error = Color(0xFFBA1A1A) +val md_theme_light_errorContainer = Color(0xFFFFDAD6) +val md_theme_light_onError = Color(0xFFFFFFFF) +val md_theme_light_onErrorContainer = Color(0xFF410002) +val md_theme_light_background = Color(0xFFFFFBFF) +val md_theme_light_onBackground = Color(0xFF1C1B1F) +val md_theme_light_surface = Color(0xFFFFFBFF) +val md_theme_light_onSurface = Color(0xFF1C1B1F) +val md_theme_light_surfaceVariant = Color(0xFFE4E1EC) +val md_theme_light_onSurfaceVariant = Color(0xFF47464F) +val md_theme_light_outline = Color(0xFF787680) +val md_theme_light_inverseOnSurface = Color(0xFFF3EFF4) +val md_theme_light_inverseSurface = Color(0xFF313034) +val md_theme_light_inversePrimary = Color(0xFFC3C0FF) +val md_theme_light_shadow = Color(0xFF000000) +val md_theme_light_surfaceTint = Color(0xFF5754A8) +val md_theme_light_outlineVariant = Color(0xFFC8C5D0) +val md_theme_light_scrim = Color(0xFF000000) + +val md_theme_dark_primary = Color(0xFFC3C0FF) +val md_theme_dark_onPrimary = Color(0xFF282377) +val md_theme_dark_primaryContainer = Color(0xFF3F3C8F) +val md_theme_dark_onPrimaryContainer = Color(0xFFE2DFFF) +val md_theme_dark_secondary = Color(0xFFBEC2FF) +val md_theme_dark_onSecondary = Color(0xFF1E2578) +val md_theme_dark_secondaryContainer = Color(0xFF373E90) +val md_theme_dark_onSecondaryContainer = Color(0xFFE0E0FF) +val md_theme_dark_tertiary = Color(0xFF88CEFF) +val md_theme_dark_onTertiary = Color(0xFF00344D) +val md_theme_dark_tertiaryContainer = Color(0xFF004C6E) +val md_theme_dark_onTertiaryContainer = Color(0xFFC8E6FF) +val md_theme_dark_error = Color(0xFFFFB4AB) +val md_theme_dark_errorContainer = Color(0xFF93000A) +val md_theme_dark_onError = Color(0xFF690005) +val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) +val md_theme_dark_background = Color(0xFF1C1B1F) +val md_theme_dark_onBackground = Color(0xFFE5E1E6) +val md_theme_dark_surface = Color(0xFF1C1B1F) +val md_theme_dark_onSurface = Color(0xFFE5E1E6) +val md_theme_dark_surfaceVariant = Color(0xFF47464F) +val md_theme_dark_onSurfaceVariant = Color(0xFFC8C5D0) +val md_theme_dark_outline = Color(0xFF928F9A) +val md_theme_dark_inverseOnSurface = Color(0xFF1C1B1F) +val md_theme_dark_inverseSurface = Color(0xFFE5E1E6) +val md_theme_dark_inversePrimary = Color(0xFF5754A8) +val md_theme_dark_shadow = Color(0xFF000000) +val md_theme_dark_surfaceTint = Color(0xFFC3C0FF) +val md_theme_dark_outlineVariant = Color(0xFF47464F) +val md_theme_dark_scrim = Color(0xFF000000) + +val seed = Color(0xFF7C7ABA) diff --git a/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/theme/Theme.kt b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/theme/Theme.kt new file mode 100644 index 00000000..1d6535c3 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/kotlin/com/kmpfoo/android/ui/theme/Theme.kt @@ -0,0 +1,93 @@ +package com.kmpfoo.android.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable + +/** + * https://m3.material.io/theme-builder + */ + +private val LightColors = lightColorScheme( + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary, + surfaceTint = md_theme_light_surfaceTint, + outlineVariant = md_theme_light_outlineVariant, + scrim = md_theme_light_scrim, +) + + +private val DarkColors = darkColorScheme( + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary, + surfaceTint = md_theme_dark_surfaceTint, + outlineVariant = md_theme_dark_outlineVariant, + scrim = md_theme_dark_scrim, +) + +@Composable +fun ComposeTheme( + useDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (!useDarkTheme) { + LightColors + } else { + DarkColors + } + + MaterialTheme( + colorScheme = colors, + content = content + ) +} diff --git a/app-examples/example-07-kmp/androidApp/src/main/res/values/strings.xml b/app-examples/example-07-kmp/androidApp/src/main/res/values/strings.xml new file mode 100644 index 00000000..8ed3192d --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/res/values/strings.xml @@ -0,0 +1,17 @@ + + + kt 9 compose ex + + + + - + SHOW + HIDE + + + + Please retry later + Please check your network and try again + Please login and try again + Please check your account for funds + + diff --git a/app-examples/example-07-kmp/androidApp/src/main/res/values/themes.xml b/app-examples/example-07-kmp/androidApp/src/main/res/values/themes.xml new file mode 100644 index 00000000..37f8e608 --- /dev/null +++ b/app-examples/example-07-kmp/androidApp/src/main/res/values/themes.xml @@ -0,0 +1,3 @@ + + - - - - diff --git a/app-examples/example-jv-03adapters/src/test/java/foo/bar/example/foreadapters/feature/playlist/ImmutablePlaylistModelTest.java b/app-examples/example-jv-03adapters/src/test/java/foo/bar/example/foreadapters/feature/playlist/ImmutablePlaylistModelTest.java deleted file mode 100644 index b0d55784..00000000 --- a/app-examples/example-jv-03adapters/src/test/java/foo/bar/example/foreadapters/feature/playlist/ImmutablePlaylistModelTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package foo.bar.example.foreadapters.feature.playlist; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import co.early.fore.core.WorkMode; -import co.early.fore.core.logging.Logger; -import co.early.fore.core.logging.SystemLogger; -import co.early.fore.core.observer.Observer; -import co.early.fore.core.time.SystemTimeWrapper; - -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * - */ -public class ImmutablePlaylistModelTest { - - - private static Logger logger = new SystemLogger(); - private SystemTimeWrapper mockSystemTimeWrapper; - private ImmutablePlaylistModel immutablePlaylistModel; - - - @Before - public void setUp() throws Exception { - mockSystemTimeWrapper = mock(SystemTimeWrapper.class); - immutablePlaylistModel = new ImmutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - } - - - @Test - public void initialConditions() throws Exception { - - //arrange - - //act - - //assert - Assert.assertEquals(0, immutablePlaylistModel.getItemCount()); - Assert.assertEquals(false, immutablePlaylistModel.hasObservers()); - } - - - @Test - public void addNewTrack() throws Exception { - - //arrange - - //act - immutablePlaylistModel.addNewTrack(); - - //assert - Assert.assertEquals(1, immutablePlaylistModel.getItemCount()); - Assert.assertEquals(1, immutablePlaylistModel.getItem(0).getNumberOfPlaysRequested()); - } - - - @Test - public void removeTrack() throws Exception { - - //arrange - immutablePlaylistModel.addNewTrack(); - - //act - immutablePlaylistModel.removeTrack(0); - - //assert - Assert.assertEquals(0, immutablePlaylistModel.getItemCount()); - } - - - @Test - public void add5NewTracks() throws Exception { - - //arrange - - //act - immutablePlaylistModel.add5NewTracks(); - - //assert - Assert.assertEquals(5, immutablePlaylistModel.getItemCount()); - Assert.assertEquals(1, immutablePlaylistModel.getItem(4).getNumberOfPlaysRequested()); - } - - - @Test - public void remove5Tracks() throws Exception { - - //arrange - immutablePlaylistModel.add5NewTracks(); - - //act - immutablePlaylistModel.remove5Tracks(); - - //assert - Assert.assertEquals(0, immutablePlaylistModel.getItemCount()); - } - - - @Test - public void increasePlays() throws Exception { - - //arrange - immutablePlaylistModel.addNewTrack(); - - //act - immutablePlaylistModel.increasePlaysForTrack(0); - - //assert - Assert.assertEquals(2, immutablePlaylistModel.getItem(0).getNumberOfPlaysRequested()); - } - - - @Test - public void decreasePlays() throws Exception { - - //arrange - immutablePlaylistModel.addNewTrack(); - immutablePlaylistModel.increasePlaysForTrack(0); - - //act - immutablePlaylistModel.decreasePlaysForTrack(0); - - //assert - Assert.assertEquals(1, immutablePlaylistModel.getItem(0).getNumberOfPlaysRequested()); - } - - - @Test - public void removeAllTracks() throws Exception { - - //arrange - immutablePlaylistModel.add5NewTracks(); - immutablePlaylistModel.addNewTrack(); - immutablePlaylistModel.addNewTrack(); - - //act - immutablePlaylistModel.removeAllTracks(); - - //assert - Assert.assertEquals(0, immutablePlaylistModel.getItemCount()); - } - - - /** - * - * NB all we are checking here is that observers are called AT LEAST once - * - * We don't really want tie our tests (OR any observers in production code) - * to an expected number of times this method might be called. (This would be - * testing an implementation detail and make the tests unnecessarily brittle) - * - * The contract says nothing about how many times observers will get called, - * only that they will be called if something changes ("something" is not defined - * and can change between implementations). - * - * See the databinding readme for more information about this - * - * @throws Exception - */ - @Test - public void observersNotifiedAtLeastOnceForAddTrack() throws Exception { - - //arrange - Observer mockObserver = mock(Observer.class); - immutablePlaylistModel.addObserver(mockObserver); - - //act - immutablePlaylistModel.addNewTrack(); - - //assert - verify(mockObserver, atLeastOnce()).somethingChanged(); - } - - - @Test - public void observersNotifiedAtLeastOnceForIncreasePlays() throws Exception { - - //arrange - immutablePlaylistModel.addNewTrack(); - Observer mockObserver = mock(Observer.class); - immutablePlaylistModel.addObserver(mockObserver); - - //act - immutablePlaylistModel.increasePlaysForTrack(0); - - //assert - verify(mockObserver, atLeastOnce()).somethingChanged(); - } -} diff --git a/app-examples/example-jv-03adapters/src/test/java/foo/bar/example/foreadapters/feature/playlist/MutablePlaylistModelTest.java b/app-examples/example-jv-03adapters/src/test/java/foo/bar/example/foreadapters/feature/playlist/MutablePlaylistModelTest.java deleted file mode 100644 index 8430a379..00000000 --- a/app-examples/example-jv-03adapters/src/test/java/foo/bar/example/foreadapters/feature/playlist/MutablePlaylistModelTest.java +++ /dev/null @@ -1,309 +0,0 @@ -package foo.bar.example.foreadapters.feature.playlist; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import co.early.fore.adapters.mutable.UpdateSpec; -import co.early.fore.core.WorkMode; -import co.early.fore.core.logging.Logger; -import co.early.fore.core.logging.SystemLogger; -import co.early.fore.core.observer.Observer; -import co.early.fore.core.time.SystemTimeWrapper; - -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * - */ -public class MutablePlaylistModelTest { - - - private static Logger logger = new SystemLogger(); - private SystemTimeWrapper mockSystemTimeWrapper; - - - @Before - public void setUp() throws Exception { - mockSystemTimeWrapper = mock(SystemTimeWrapper.class); - } - - - @Test - public void initialConditions() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - - //act - - //assert - Assert.assertEquals(0, mutablePlaylistModel.getItemCount()); - Assert.assertEquals(false, mutablePlaylistModel.hasObservers()); - // 50ms won't have any effect here as the mocked systemTimeWrapper will always give the current - // time as 0 anyway, so we will never have the chance to be over maxAgeMs - Assert.assertEquals(UpdateSpec.UpdateType.FULL_UPDATE, mutablePlaylistModel.getAndClearLatestUpdateSpec(50).type); - } - - - @Test - public void addNewTrack() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - - //act - mutablePlaylistModel.addNewTrack(); - - //assert - Assert.assertEquals(1, mutablePlaylistModel.getItemCount()); - Assert.assertEquals(1, mutablePlaylistModel.getItem(0).getNumberOfPlaysRequested()); - } - - - @Test - public void removeTrack() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.addNewTrack(); - - //act - mutablePlaylistModel.removeTrack(0); - - //assert - Assert.assertEquals(0, mutablePlaylistModel.getItemCount()); - } - - - @Test - public void add5NewTracks() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - - //act - mutablePlaylistModel.add5NewTracks(); - - //assert - Assert.assertEquals(5, mutablePlaylistModel.getItemCount()); - Assert.assertEquals(1, mutablePlaylistModel.getItem(4).getNumberOfPlaysRequested()); - } - - - @Test - public void remove5Tracks() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.add5NewTracks(); - - //act - mutablePlaylistModel.remove5Tracks(); - - //assert - Assert.assertEquals(0, mutablePlaylistModel.getItemCount()); - } - - - @Test - public void increasePlays() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.addNewTrack(); - - //act - mutablePlaylistModel.increasePlaysForTrack(0); - - //assert - Assert.assertEquals(2, mutablePlaylistModel.getItem(0).getNumberOfPlaysRequested()); - } - - - @Test - public void decreasePlays() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.addNewTrack(); - mutablePlaylistModel.increasePlaysForTrack(0); - - //act - mutablePlaylistModel.decreasePlaysForTrack(0); - - //assert - Assert.assertEquals(1, mutablePlaylistModel.getItem(0).getNumberOfPlaysRequested()); - } - - - @Test - public void removeAllTracks() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.add5NewTracks(); - mutablePlaylistModel.addNewTrack(); - mutablePlaylistModel.addNewTrack(); - - //act - mutablePlaylistModel.removeAllTracks(); - - //assert - Assert.assertEquals(0, mutablePlaylistModel.getItemCount()); - } - - - /** - * - * NB all we are checking here is that observers are called AT LEAST once - * - * We don't really want tie our tests (OR any observers in production code) - * to an expected number of times this method might be called. (This would be - * testing an implementation detail and make the tests unnecessarily brittle) - * - * The contract says nothing about how many times observers will get called, - * only that they will be called if something changes ("something" is not defined - * and can change between implementations). - * - * See the databinding readme for more information about this - * - * @throws Exception - */ - @Test - public void observersNotifiedAtLeastOnceForAddTrack() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - Observer mockObserver = mock(Observer.class); - mutablePlaylistModel.addObserver(mockObserver); - - //act - mutablePlaylistModel.addNewTrack(); - - //assert - verify(mockObserver, atLeastOnce()).somethingChanged(); - } - - - @Test - public void observersNotifiedAtLeastOnceForIncreasePlays() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.addNewTrack(); - Observer mockObserver = mock(Observer.class); - mutablePlaylistModel.addObserver(mockObserver); - - //act - mutablePlaylistModel.increasePlaysForTrack(0); - - //assert - verify(mockObserver, atLeastOnce()).somethingChanged(); - } - - - /** - * - * Here we make some tests to verify the UpdateSpec returned is correct so that we - * know that we can correctly drive a ChangeAwareAdapter with this model - * - * @throws Exception - */ - @Test - public void updateSpecCorrectForAddTrack() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - - //act - mutablePlaylistModel.addNewTrack(); - - //assert - UpdateSpec updateSpec = mutablePlaylistModel.getAndClearLatestUpdateSpec(50); - Assert.assertEquals(UpdateSpec.UpdateType.ITEM_INSERTED, updateSpec.type); - Assert.assertEquals(0, updateSpec.rowPosition); - Assert.assertEquals(1, updateSpec.rowsEffected); - } - - - @Test - public void updateSpecCorrectForIncreasePlays() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.addNewTrack(); - mutablePlaylistModel.add5NewTracks(); - - //act - mutablePlaylistModel.increasePlaysForTrack(3); - - //assert - UpdateSpec updateSpec = mutablePlaylistModel.getAndClearLatestUpdateSpec(50); - Assert.assertEquals(UpdateSpec.UpdateType.ITEM_CHANGED, updateSpec.type); - Assert.assertEquals(3, updateSpec.rowPosition); - Assert.assertEquals(1, updateSpec.rowsEffected); - } - - - @Test - public void updateSpecCorrectForRemove5Tracks() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.add5NewTracks(); - mutablePlaylistModel.addNewTrack(); - - //act - mutablePlaylistModel.remove5Tracks(); - - //assert - UpdateSpec updateSpec = mutablePlaylistModel.getAndClearLatestUpdateSpec(50); - Assert.assertEquals(UpdateSpec.UpdateType.ITEM_REMOVED, updateSpec.type); - Assert.assertEquals(0, updateSpec.rowPosition); - Assert.assertEquals(5, updateSpec.rowsEffected); - } - - @Test - public void updateSpecCorrectForClearAllTracks() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.add5NewTracks(); - mutablePlaylistModel.addNewTrack(); - - //act - mutablePlaylistModel.removeAllTracks(); - - //assert - UpdateSpec updateSpec = mutablePlaylistModel.getAndClearLatestUpdateSpec(50); - Assert.assertEquals(UpdateSpec.UpdateType.ITEM_REMOVED, updateSpec.type); - Assert.assertEquals(0, updateSpec.rowPosition); - Assert.assertEquals(6, updateSpec.rowsEffected); - } - - - @Test - public void updateSpecCorrectPastMaxAge() throws Exception { - - //arrange - MutablePlaylistModel mutablePlaylistModel = new MutablePlaylistModel(mockSystemTimeWrapper, WorkMode.SYNCHRONOUS, logger); - mutablePlaylistModel.add5NewTracks(); - mutablePlaylistModel.addNewTrack(); - - //act - mutablePlaylistModel.increasePlaysForTrack(3); - when(mockSystemTimeWrapper.currentTimeMillis()).thenReturn((long)51); - - //assert - UpdateSpec updateSpec = mutablePlaylistModel.getAndClearLatestUpdateSpec(50); - Assert.assertEquals(UpdateSpec.UpdateType.FULL_UPDATE, updateSpec.type); - Assert.assertEquals(0, updateSpec.rowPosition); - Assert.assertEquals(0, updateSpec.rowsEffected); - } - - -} diff --git a/app-examples/example-jv-04retrofit/build.gradle.kts b/app-examples/example-jv-04retrofit/build.gradle.kts deleted file mode 100644 index 7f828bef..00000000 --- a/app-examples/example-jv-04retrofit/build.gradle.kts +++ /dev/null @@ -1,95 +0,0 @@ -import co.early.fore.Shared -import co.early.fore.Shared.BuildTypes - -plugins { - alias(libs.plugins.androidApplication) -} - - -val appId = "foo.bar.example.foreretrofit" - -fun getTestBuildType(): String { - return project.properties["testBuildType"] as String? ?: BuildTypes.DEFAULT -} - -println("[$appId testBuildType:${getTestBuildType()}]") - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(Shared.Versions.jvm_toolchain)) - } -} - -android { - - namespace = appId - compileSdk = Shared.Android.compileSdk - - defaultConfig { - applicationId = appId - minSdk = Shared.Android.minSdk - targetSdk = Shared.Android.targetSdk - versionCode = 1 - versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - testBuildType = getTestBuildType() - } - signingConfigs { - create(BuildTypes.RELEASE) { - // keytool -genkey -v -keystore debug.fake_keystore -storetype PKCS12 -alias android -storepass android -keypass android -keyalg RSA -keysize 2048 -validity 20000 -dname "cn=Unknown, ou=Unknown, o=Unknown, c=Unknown" - storeFile = file("../keystore/debug.fake_keystore") - storePassword = "android" - keyAlias = "android" - keyPassword = "android" - } - } - buildTypes { - getByName(BuildTypes.DEBUG) { - isMinifyEnabled = false - } - getByName(BuildTypes.RELEASE) { - isMinifyEnabled = true - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "../proguard-example-app.pro") - signingConfig = signingConfigs.getByName(BuildTypes.RELEASE) - } - } - lint { - abortOnError = true - lintConfig = File(project.rootDir, "lint-example-apps.xml") - } - buildFeatures { - buildConfig = true - } -} - -dependencies { - - if (Shared.Publish.use_published_version) { - implementation("co.early.fore:fore-jv-android-network:${Shared.Publish.published_fore_version_for_examples}") - } else { - implementation(project(":fore-jv:fore-jv-android-network")) - } - - annotationProcessor("com.jakewharton:butterknife-compiler:${Shared.Versions.butterknife}") - //noinspection AnnotationProcessorOnCompilePath - implementation("com.jakewharton:butterknife:${Shared.Versions.butterknife}") - - implementation("com.squareup.retrofit2:retrofit:${Shared.Versions.retrofit}") - implementation("com.squareup.retrofit2:converter-gson:${Shared.Versions.converter_gson}") - implementation("androidx.appcompat:appcompat:${Shared.Versions.appcompat}") - implementation("androidx.constraintlayout:constraintlayout:${Shared.Versions.constraintlayout}") - - testImplementation("junit:junit:${Shared.Versions.junit}") - testImplementation("org.mockito:mockito-core:${Shared.Versions.mockito_core}") - testImplementation("org.hamcrest:hamcrest-library:${Shared.Versions.hamcrest_library}") - - androidTestImplementation("org.mockito:mockito-core:${Shared.Versions.mockito_core}") - androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:${Shared.Versions.dexmaker_mockito}") - androidTestImplementation("androidx.test:runner:${Shared.Versions.androidxtest}") - androidTestImplementation("androidx.test:rules:${Shared.Versions.androidxtest}") - androidTestImplementation("androidx.annotation:annotation:${Shared.Versions.annotation}") - androidTestImplementation("androidx.core:core:${Shared.Versions.android_core}") - androidTestImplementation("androidx.test.espresso:espresso-core:${Shared.Versions.espresso_core}") { - exclude(group = "com.android.support", module = "support-annotations") - } -} diff --git a/app-examples/example-jv-04retrofit/screenshot.png b/app-examples/example-jv-04retrofit/screenshot.png deleted file mode 100644 index 55c10a44..00000000 Binary files a/app-examples/example-jv-04retrofit/screenshot.png and /dev/null differ diff --git a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/DrawableMatcher.java b/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/DrawableMatcher.java deleted file mode 100644 index 925a6c04..00000000 --- a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/DrawableMatcher.java +++ /dev/null @@ -1,74 +0,0 @@ -package foo.bar.example.foreretrofit; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.view.View; -import android.widget.ImageView; - -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -/** - * - * https://medium.com/@dbottillo/android-ui-test-espresso-matcher-for-imageview-1a28c832626f - * https://github.com/dbottillo/Blog/blob/espresso_match_imageview/app/src/androidTest/java/com/danielebottillo/blog/config/DrawableMatcher.java - * - */ -public class DrawableMatcher extends TypeSafeMatcher { - - private final int expectedId; - private String resourceName; - static final int EMPTY = -1; - static final int ANY = -2; - - DrawableMatcher(int expectedId) { - super(View.class); - this.expectedId = expectedId; - } - - @Override - protected boolean matchesSafely(View target) { - if (!(target instanceof ImageView)) { - return false; - } - ImageView imageView = (ImageView) target; - if (expectedId == EMPTY) { - return imageView.getDrawable() == null; - } - if (expectedId == ANY) { - return imageView.getDrawable() != null; - } - Resources resources = target.getContext().getResources(); - Drawable expectedDrawable = resources.getDrawable(expectedId); - resourceName = resources.getResourceEntryName(expectedId); - - if (expectedDrawable == null) { - return false; - } - - Bitmap bitmap = getBitmap(imageView.getDrawable()); - Bitmap otherBitmap = getBitmap(expectedDrawable); - return bitmap.sameAs(otherBitmap); - } - - private Bitmap getBitmap(Drawable drawable) { - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bitmap; - } - - @Override - public void describeTo(Description description) { - description.appendText("with drawable from resource id: "); - description.appendValue(expectedId); - if (resourceName != null) { - description.appendText("["); - description.appendText(resourceName); - description.appendText("]"); - } - } -} diff --git a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/EspressoTestMatchers.java b/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/EspressoTestMatchers.java deleted file mode 100644 index 83505191..00000000 --- a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/EspressoTestMatchers.java +++ /dev/null @@ -1,27 +0,0 @@ -package foo.bar.example.foreretrofit; - -import android.view.View; - -import org.hamcrest.Matcher; - -/** - * - * https://medium.com/@dbottillo/android-ui-test-espresso-matcher-for-imageview-1a28c832626f - * https://github.com/dbottillo/Blog/blob/espresso_match_imageview/app/src/androidTest/java/com/danielebottillo/blog/config/EspressoTestsMatchers.java - * - */ -public class EspressoTestMatchers { - - public static Matcher withDrawable(final int resourceId) { - return new DrawableMatcher(resourceId); - } - - public static Matcher noDrawable() { - return new DrawableMatcher(DrawableMatcher.EMPTY); - } - - public static Matcher hasDrawable() { - return new DrawableMatcher(DrawableMatcher.ANY); - } - -} diff --git a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ProgressBarIdler.java b/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ProgressBarIdler.java deleted file mode 100644 index a0a636e9..00000000 --- a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ProgressBarIdler.java +++ /dev/null @@ -1,104 +0,0 @@ -package foo.bar.example.foreretrofit; - -import android.app.Activity; -import android.app.Application; -import android.app.Instrumentation; -import android.os.Bundle; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; -import androidx.core.content.ContextCompat; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ProgressBar; - -import java.util.Collection; - -import static androidx.test.runner.lifecycle.Stage.RESUMED; - -/** - * If you have any spinning progress bars in your UI it will mess - * up espresso tests (espresso will time out waiting for all UI - * threads to be idle before proceeding). - *

- * This class will go through all your view elements views checking for - * indeterminate progress bars and replacing the indeterminate drawable - * with a static one so that espresso tests can continue - *

- * http://stackoverflow.com/a/37049916/3680389 - * https://stackoverflow.com/questions/33289152/progressbars-and-espresso#36201647 - */ -public class ProgressBarIdler implements Application.ActivityLifecycleCallbacks { - - private static void makeAllProgressBarsIdle(View view) { - if (view instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) view; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - makeAllProgressBarsIdle(viewGroup.getChildAt(i)); - } - } else if (view instanceof ProgressBar) { - ProgressBar progressBar = (ProgressBar) view; - if (progressBar.isIndeterminate()) { - progressBar.setIndeterminateDrawable(ContextCompat.getDrawable(InstrumentationRegistry.getTargetContext(), android.R.drawable.ic_lock_lock)); - } - } - } - - public static void makeAllProgressBarsIdle(Instrumentation instrumentation, final Activity activity) { - instrumentation.runOnMainSync(new Runnable() { - public void run() { - makeAllProgressBarsIdle(activity.findViewById(android.R.id.content).getRootView()); - } - }); - } - - public static void makeAllProgressBarsIdleForAllResumedActivities(Instrumentation instrumentation) { - instrumentation.runOnMainSync(new Runnable() { - public void run() { - Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED); - if (resumedActivities.isEmpty()) { - throw new RuntimeException("Could not change orientation"); - } - for (Activity activity : resumedActivities) { - makeAllProgressBarsIdle(activity.findViewById(android.R.id.content).getRootView()); - } - } - }); - } - - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - - } - - @Override - public void onActivityStarted(Activity activity) { - - } - - @Override - public void onActivityResumed(Activity activity) { - makeAllProgressBarsIdle(activity.findViewById(android.R.id.content).getRootView()); - } - - @Override - public void onActivityPaused(Activity activity) { - - } - - @Override - public void onActivityStopped(Activity activity) { - - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - - } - - @Override - public void onActivityDestroyed(Activity activity) { - - } - -} diff --git a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewRotationTest.java b/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewRotationTest.java deleted file mode 100644 index b117a56a..00000000 --- a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewRotationTest.java +++ /dev/null @@ -1,208 +0,0 @@ -package foo.bar.example.foreretrofit.ui.fruit; - -import android.app.Activity; -import android.content.pm.ActivityInfo; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; - -import co.early.fore.core.WorkMode; -import co.early.fore.core.callbacks.FailureCallbackWithPayload; -import co.early.fore.core.callbacks.SuccessCallback; -import co.early.fore.core.callbacks.SuccessCallbackWithPayload; -import co.early.fore.core.logging.Logger; -import co.early.fore.core.logging.SystemLogger; -import co.early.fore.core.observer.Observer; -import co.early.fore.net.retrofit2.CallProcessorRetrofit2; -import foo.bar.example.foreretrofit.R; -import foo.bar.example.foreretrofit.api.fruits.FruitPojo; -import foo.bar.example.foreretrofit.api.fruits.FruitService; -import foo.bar.example.foreretrofit.feature.fruit.FruitFetcher; -import foo.bar.example.foreretrofit.message.UserMessage; - -import static androidx.test.InstrumentationRegistry.getInstrumentation; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static foo.bar.example.foreretrofit.EspressoTestMatchers.withDrawable; -import static org.hamcrest.Matchers.not; -import static org.mockito.Mockito.mock; - -/** - * Here we make sure that when the view is rotated while a long running action is being - * performed by the model, all the view elements still accurately represent the model's - * state after rotation is complete, and those view elements are still updated as appropriate - * when the model completes the long running action. - */ -@RunWith(AndroidJUnit4.class) -public class FruitViewRotationTest { - - public static final String TAG = FruitViewRotationTest.class.getSimpleName(); - - /** - * Here we're testing with a real model, - * and just mocking its dependencies. - * This lets us realistically test the - * interactions between the view - * and the model during and after - * a rotation - */ - FruitFetcher fruitFetcher; - Logger logger = new SystemLogger(); - FruitPojo fruitPojo = new FruitPojo("testFruit1", true, 45); - - SuccessCallback mockSuccessCallback; - FailureCallbackWithPayload mockFailureCallbackWithPayload; - CallProcessorRetrofit2 mockCallProcessor; - FruitService mockFruitService; - Observer mockObserver; - - SuccessCallbackWithPayload cachedSuccessCallback; - private CountDownLatch countDownLatch = new CountDownLatch(1); - - @Before - public void setup(){ - - logger.i(TAG, "setup()"); - - //MockitoAnnotations.initMocks(LoginViewTest.this); - System.setProperty("dexmaker.dexcache", InstrumentationRegistry.getTargetContext().getCacheDir().getPath()); - - mockSuccessCallback = mock(SuccessCallback.class); - mockFailureCallbackWithPayload = mock(FailureCallbackWithPayload.class); - mockCallProcessor = mock(CallProcessorRetrofit2.class); - mockFruitService = mock(FruitService.class); - - //construct a real model with mock dependencies - fruitFetcher = new FruitFetcher( - mockFruitService, - mockCallProcessor, - logger, - WorkMode.ASYNCHRONOUS); - - } - - @Test - public void stateSurvivesRotation() throws Exception { - - logger.i(TAG, "stateSurvivesRotation()"); - - //arrange - Activity activity = new FruitViewRotationTestStateBuilder(this) - .withDelayedCallProcessor() - .createRule() - .launchActivity(null); - - checkUIBeforeClick(activity); - - //act - fruitFetcher.fetchFruits(mockSuccessCallback, mockFailureCallbackWithPayload); - - checkUIWhenFetching(activity); - - swapOrientation(activity); - - checkUIWhenFetching(activity); - - callCachedSuccessCallback(); - - try{ - countDownLatch.await(); - }catch(InterruptedException e){ - } - - checkUIOnceComplete(activity); - } - - - - public void callCachedSuccessCallback() { - - logger.i(TAG, "callSuccessOnCachedSuccessFailCallback()"); - - List fruitList = new ArrayList<>(); - fruitList.add(fruitPojo); - - //we need to be back on the UI thread for this - getInstrumentation().runOnMainSync(new Runnable() { - @Override - public void run() { - - logger.i(TAG, "about to call success, id:" + Thread.currentThread().getId()); - - cachedSuccessCallback.success(fruitList); - countDownLatch.countDown(); - } - }); - } - - public void setCachedSuccessCallback(SuccessCallbackWithPayload successCallBack) { - logger.i(TAG, "setCachedSuccessFailCallback()"); - this.cachedSuccessCallback = successCallBack; - } - - - private synchronized void checkUIBeforeClick(Activity activity) { - logger.i(TAG, "checkUIBeforeClick()"); - - //assert - onView(withId(R.id.fruit_busy_progbar)).check(matches(not(isDisplayed()))); - onView(withId(R.id.fruit_fetchsuccess_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailadvanced_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailbasic_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_name_textview)).check(matches(withText("(fruitless)"))); - onView(withId(R.id.fruit_tastyrating_textview)).check(matches(withText( - activity.getString(R.string.fruit_percent, 0) - ))); - onView(withId(R.id.fruit_citrus_img)).check(matches(withDrawable(R.drawable.lemon_negative))); - } - - private synchronized void checkUIWhenFetching(Activity activity) { - logger.i(TAG, "checkUIWhenFetching()"); - - //assert - onView(withId(R.id.fruit_busy_progbar)).check(matches(isDisplayed())); - onView(withId(R.id.fruit_fetchsuccess_btn)).check(matches(not(isEnabled()))); - onView(withId(R.id.fruit_fetchfailadvanced_btn)).check(matches(not(isEnabled()))); - onView(withId(R.id.fruit_fetchfailbasic_btn)).check(matches(not(isEnabled()))); - onView(withId(R.id.fruit_name_textview)).check(matches(not(isDisplayed()))); - onView(withId(R.id.fruit_tastyrating_textview)).check(matches(withText( - activity.getString(R.string.fruit_percent, 0) - ))); - onView(withId(R.id.fruit_citrus_img)).check(matches(withDrawable(R.drawable.lemon_negative))); - } - - private synchronized void checkUIOnceComplete(Activity activity) { - logger.i(TAG, "checkUIOnceComplete()"); - - //assert - onView(withId(R.id.fruit_busy_progbar)).check(matches(not(isDisplayed()))); - onView(withId(R.id.fruit_fetchsuccess_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailadvanced_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailbasic_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_name_textview)).check(matches(withText(fruitPojo.name))); - onView(withId(R.id.fruit_tastyrating_textview)).check(matches(withText( - activity.getString(R.string.fruit_percent, fruitPojo.tastyPercentScore) - ))); - onView(withId(R.id.fruit_citrus_img)).check(matches(withDrawable(fruitPojo.isCitrus ? - R.drawable.lemon_positive : R.drawable.lemon_negative))); - } - - private void swapOrientation(Activity activity){ - activity.setRequestedOrientation(activity.getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ? - ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - - - -} diff --git a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewRotationTestStateBuilder.java b/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewRotationTestStateBuilder.java deleted file mode 100644 index d380ed79..00000000 --- a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewRotationTestStateBuilder.java +++ /dev/null @@ -1,76 +0,0 @@ -package foo.bar.example.foreretrofit.ui.fruit; - -import android.content.pm.ActivityInfo; - -import org.mockito.ArgumentCaptor; - -import androidx.test.InstrumentationRegistry; -import androidx.test.rule.ActivityTestRule; -import co.early.fore.core.WorkMode; -import co.early.fore.core.callbacks.SuccessCallbackWithPayload; -import co.early.fore.core.logging.SystemLogger; -import foo.bar.example.foreretrofit.App; -import foo.bar.example.foreretrofit.OG; -import foo.bar.example.foreretrofit.ProgressBarIdler; -import foo.bar.example.foreretrofit.feature.fruit.FruitFetcher; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; - -/** - * - */ -public class FruitViewRotationTestStateBuilder { - - private final FruitViewRotationTest fruitViewRotationTest; - - FruitViewRotationTestStateBuilder(FruitViewRotationTest fruitViewRotationTest) { - this.fruitViewRotationTest = fruitViewRotationTest; - } - - FruitViewRotationTestStateBuilder withDelayedCallProcessor(){ - - final ArgumentCaptor callback = ArgumentCaptor.forClass(SuccessCallbackWithPayload.class); - doAnswer(__ -> { - - // We need to store this callback so that we can trigger it later - // We can't use any kind of post delayed thing because - // espresso needs all threads to be idle before proceeding - // from this method - fruitViewRotationTest.setCachedSuccessCallback(callback.getValue()); - - return null; - }) - .when(fruitViewRotationTest.mockCallProcessor) - .processCall(any(), any(), any(), callback.capture(), any()); - - return this; - } - - ActivityTestRule createRule(){ - - return new ActivityTestRule(FruitActivity.class) { - @Override - protected void beforeActivityLaunched() { - - new SystemLogger().i("FruitViewRotationTestStateBuilder", "beforeActivityLaunched()"); - - App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext(); - OG.setApplication(app, WorkMode.SYNCHRONOUS); - //inject our test model - OG.putMock(FruitFetcher.class, fruitViewRotationTest.fruitFetcher); - - app.registerActivityLifecycleCallbacks(new ProgressBarIdler()); - - } - - @Override - protected void afterActivityFinished() { - super.afterActivityFinished(); - - this.getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } - }; - } - -} diff --git a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewTest.java b/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewTest.java deleted file mode 100644 index 9f9924ae..00000000 --- a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package foo.bar.example.foreretrofit.ui.fruit; - -import android.app.Activity; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import foo.bar.example.foreretrofit.R; -import foo.bar.example.foreretrofit.api.fruits.FruitPojo; -import foo.bar.example.foreretrofit.feature.fruit.FruitFetcher; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static foo.bar.example.foreretrofit.EspressoTestMatchers.withDrawable; -import static org.hamcrest.Matchers.not; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Here we make sure that the view elements accurately reflect the state of the models - * and that clicking the buttons results in the correct action being performed - */ -@RunWith(AndroidJUnit4.class) -public class FruitViewTest { - - - private FruitFetcher mockFruitFetcher; - - - @Before - public void setup(){ - - //MockitoAnnotations.initMocks(CounterView.this); - System.setProperty("dexmaker.dexcache", InstrumentationRegistry.getTargetContext().getCacheDir().getPath()); - - mockFruitFetcher = mock(FruitFetcher.class); - - } - - - @Test - public void hasCitrusFruit() throws Exception { - - //arrange - Activity activity = new FruitViewTestStateBuilder(mockFruitFetcher) - .isBusy(false) - .hasFruit(new FruitPojo("testFruit1", true, 45)) - .createRule() - .launchActivity(null); - - - //act - - - //assert - onView(withId(R.id.fruit_busy_progbar)).check(matches(not(isDisplayed()))); - onView(withId(R.id.fruit_fetchsuccess_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailadvanced_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailbasic_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_name_textview)).check(matches(withText("testFruit1"))); - onView(withId(R.id.fruit_tastyrating_textview)).check(matches(withText( - activity.getString(R.string.fruit_percent, 45) - ))); - onView(withId(R.id.fruit_citrus_img)).check(matches(withDrawable(R.drawable.lemon_positive))); - } - - - @Test - public void hasNonCitrusFruit() throws Exception { - - //arrange - Activity activity = new FruitViewTestStateBuilder(mockFruitFetcher) - .isBusy(false) - .hasFruit(new FruitPojo("testFruit2", false, 75)) - .createRule() - .launchActivity(null); - - - //act - - - //assert - onView(withId(R.id.fruit_busy_progbar)).check(matches(not(isDisplayed()))); - onView(withId(R.id.fruit_fetchsuccess_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailadvanced_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_fetchfailbasic_btn)).check(matches(isEnabled())); - onView(withId(R.id.fruit_name_textview)).check(matches(withText("testFruit2"))); - onView(withId(R.id.fruit_tastyrating_textview)).check(matches(withText( - activity.getString(R.string.fruit_percent, 75) - ))); - onView(withId(R.id.fruit_citrus_img)).check(matches(withDrawable(R.drawable.lemon_negative))); - } - - - @Test - public void isFetchingFruit() throws Exception { - - //arrange - new FruitViewTestStateBuilder(mockFruitFetcher) - .isBusy(true) - .hasFruit(new FruitPojo("testFruit1", true, 45)) - .createRule() - .launchActivity(null); - - - //act - - - //assert - onView(withId(R.id.fruit_busy_progbar)).check(matches(isDisplayed())); - onView(withId(R.id.fruit_fetchsuccess_btn)).check(matches(not(isEnabled()))); - onView(withId(R.id.fruit_fetchfailadvanced_btn)).check(matches(not(isEnabled()))); - onView(withId(R.id.fruit_fetchfailbasic_btn)).check(matches(not(isEnabled()))); - onView(withId(R.id.fruit_name_textview)).check(matches(not(isDisplayed()))); - onView(withId(R.id.fruit_tastyrating_textview)).check(matches(not(isDisplayed()))); - onView(withId(R.id.fruit_citrus_img)).check(matches(not(isDisplayed()))); - } - - - @Test - public void clickCallsFetchSuccess() throws Exception { - //arrange - new FruitViewTestStateBuilder(mockFruitFetcher) - .isBusy(false) - .hasFruit(new FruitPojo("testFruit2", false, 75)) - .createRule() - .launchActivity(null); - - - //act - onView(withId(R.id.fruit_fetchsuccess_btn)).perform(click()); - - - //assert - verify(mockFruitFetcher).fetchFruits(any(), any()); - } - - - @Test - public void clickCallsFetchFailBasic() throws Exception { - //arrange - new FruitViewTestStateBuilder(mockFruitFetcher) - .isBusy(false) - .hasFruit(new FruitPojo("testFruit2", false, 75)) - .createRule() - .launchActivity(null); - - - //act - onView(withId(R.id.fruit_fetchfailbasic_btn)).perform(click()); - - - //assert - verify(mockFruitFetcher).fetchFruitsButFailBasic(any(), any()); - } - - - @Test - public void clickCallsFetchFailAdvanced() throws Exception { - //arrange - new FruitViewTestStateBuilder(mockFruitFetcher) - .isBusy(false) - .hasFruit(new FruitPojo("testFruit2", false, 75)) - .createRule() - .launchActivity(null); - - - //act - onView(withId(R.id.fruit_fetchfailadvanced_btn)).perform(click()); - - - //assert - verify(mockFruitFetcher).fetchFruitsButFailAdvanced(any(), any()); - } - -} diff --git a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewTestStateBuilder.java b/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewTestStateBuilder.java deleted file mode 100644 index ae7e308c..00000000 --- a/app-examples/example-jv-04retrofit/src/androidTest/java/foo/bar/example/foreretrofit/ui/fruit/FruitViewTestStateBuilder.java +++ /dev/null @@ -1,54 +0,0 @@ -package foo.bar.example.foreretrofit.ui.fruit; - -import androidx.test.InstrumentationRegistry; -import androidx.test.rule.ActivityTestRule; -import co.early.fore.core.WorkMode; -import foo.bar.example.foreretrofit.App; -import foo.bar.example.foreretrofit.OG; -import foo.bar.example.foreretrofit.ProgressBarIdler; -import foo.bar.example.foreretrofit.api.fruits.FruitPojo; -import foo.bar.example.foreretrofit.feature.fruit.FruitFetcher; - -import static org.mockito.Mockito.when; - -/** - * - */ -public class FruitViewTestStateBuilder { - - private FruitFetcher mockFruitFetcher; - - FruitViewTestStateBuilder(FruitFetcher mockFruitFetcher) { - this.mockFruitFetcher = mockFruitFetcher; - } - - FruitViewTestStateBuilder isBusy(boolean busy) { - when(mockFruitFetcher.isBusy()).thenReturn(busy); - return this; - } - - FruitViewTestStateBuilder hasFruit(FruitPojo fruitPojo) { - when(mockFruitFetcher.getCurrentFruit()).thenReturn(fruitPojo); - return this; - } - - ActivityTestRule createRule(){ - - return new ActivityTestRule(FruitActivity.class) { - @Override - protected void beforeActivityLaunched() { - - //get hold of the application - App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext(); - OG.setApplication(app, WorkMode.SYNCHRONOUS); - - //inject our mocks so our UI layer will pick them up - OG.putMock(FruitFetcher.class, mockFruitFetcher); - - app.registerActivityLifecycleCallbacks(new ProgressBarIdler()); - } - - }; - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/AndroidManifest.xml b/app-examples/example-jv-04retrofit/src/main/AndroidManifest.xml deleted file mode 100644 index f418e0d9..00000000 --- a/app-examples/example-jv-04retrofit/src/main/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/App.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/App.java deleted file mode 100644 index e6779b0a..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/App.java +++ /dev/null @@ -1,26 +0,0 @@ -package foo.bar.example.foreretrofit; - -import android.app.Application; - -/** - * Try not to fill this class with lots of code, if possible move it to a model somewhere - */ -public class App extends Application { - - private static App inst; - - @Override - public void onCreate() { - super.onCreate(); - - inst = this; - - OG.setApplication(inst); - OG.init(); - } - - public static App getInst() { - return inst; - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/OG.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/OG.java deleted file mode 100644 index 83cc6d2d..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/OG.java +++ /dev/null @@ -1,96 +0,0 @@ -package foo.bar.example.foreretrofit; - -import android.app.Application; - -import java.util.HashMap; -import java.util.Map; - -import co.early.fore.core.WorkMode; -import co.early.fore.core.logging.AndroidLogger; -import co.early.fore.net.retrofit2.CallProcessorRetrofit2; -import co.early.fore.net.InterceptorLogging; -import foo.bar.example.foreretrofit.api.CustomGlobalErrorHandler; -import foo.bar.example.foreretrofit.api.CustomGlobalRequestInterceptor; -import foo.bar.example.foreretrofit.api.CustomRetrofitBuilder; -import foo.bar.example.foreretrofit.api.fruits.FruitService; -import foo.bar.example.foreretrofit.feature.fruit.FruitFetcher; -import foo.bar.example.foreretrofit.message.UserMessage; -import retrofit2.Retrofit; - -import static co.early.fore.core.Affirm.notNull; - -/** - * - * OG - Object Graph, pure DI implementation - * - * Copyright © 2019 early.co. All rights reserved. - */ -public class OG { - - private static boolean initialized = false; - private static final Map, Object> dependencies = new HashMap<>(); - - - public static void setApplication(Application application) { - setApplication(application, WorkMode.ASYNCHRONOUS); - } - - public static void setApplication(Application application, final WorkMode workMode) { - - notNull(application); - notNull(workMode); - - - // create dependency graph - AndroidLogger logger = new AndroidLogger("fore_"); - - // networking classes common to all models - Retrofit retrofit = CustomRetrofitBuilder.create( - new CustomGlobalRequestInterceptor(logger), - new InterceptorLogging(logger));//logging interceptor should be the last one - - CallProcessorRetrofit2 callProcessor = new CallProcessorRetrofit2( - new CustomGlobalErrorHandler(logger), - logger); - - - // models - FruitFetcher fruitFetcher = new FruitFetcher( - retrofit.create(FruitService.class), - callProcessor, - logger, - workMode); - - - // add models to the dependencies map if you will need them later - dependencies.put(FruitFetcher.class, fruitFetcher); - - } - - public static void init() { - if (!initialized) { - initialized = true; - - // run any necessary initialization code once object graph has been created here - - } - } - - public static T get(Class model) { - - notNull(model); - T t = model.cast(dependencies.get(model)); - notNull(t); - - return t; - } - - public static void putMock(Class clazz, T object) { - - notNull(clazz); - notNull(object); - - dependencies.put(clazz, object); - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomGlobalErrorHandler.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomGlobalErrorHandler.java deleted file mode 100644 index 1e2361e4..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomGlobalErrorHandler.java +++ /dev/null @@ -1,118 +0,0 @@ -package foo.bar.example.foreretrofit.api; - -import com.google.gson.Gson; - -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; - -import androidx.annotation.Nullable; -import co.early.fore.core.logging.Logger; -import co.early.fore.net.retrofit2.ErrorHandler; -import co.early.fore.net.MessageProvider; -import foo.bar.example.foreretrofit.message.UserMessage; -import okhttp3.Request; -import retrofit2.Response; - -import static co.early.fore.core.Affirm.notNull; -import static foo.bar.example.foreretrofit.message.UserMessage.ERROR_CLIENT; -import static foo.bar.example.foreretrofit.message.UserMessage.ERROR_MISC; -import static foo.bar.example.foreretrofit.message.UserMessage.ERROR_NETWORK; -import static foo.bar.example.foreretrofit.message.UserMessage.ERROR_SECURITY_UNKNOWN; -import static foo.bar.example.foreretrofit.message.UserMessage.ERROR_SERVER; -import static foo.bar.example.foreretrofit.message.UserMessage.ERROR_SESSION_TIMED_OUT; - -/** - * You can probably use this class almost as it is for your own app, but you might want to - * customise the behaviour for specific HTTP codes etc, hence it's not in the fore library - */ -public class CustomGlobalErrorHandler implements ErrorHandler { - - private static final String TAG = CustomGlobalErrorHandler.class.getSimpleName(); - - private final Logger logWrapper; - - public CustomGlobalErrorHandler(Logger logWrapper) { - this.logWrapper = notNull(logWrapper); - } - - - @Override - public > UserMessage handleError(Throwable t, Response errorResponse, @Nullable Class customErrorClazz, Request originalRequest) { - - UserMessage message = ERROR_MISC; - - if (errorResponse != null) { - - logWrapper.e(TAG, "handleError() HTTP:" + errorResponse.code()); - - switch (errorResponse.code()) { - - case 401: - message = ERROR_SESSION_TIMED_OUT; - break; - - case 400: - case 405: - message = ERROR_CLIENT; - break; - - case 404://realise this is officially a "client" error, but in my experience if it happens in prod it is usually the fault of the server ;) - case 500: - case 503: - message = ERROR_SERVER; - break; - } - - if (customErrorClazz != null) { - //let's see if we can get more specifics about the error - message = parseCustomError(message, errorResponse, customErrorClazz); - } - - } else {//non HTTP error, probably some connection problem, but might be JSON parsing related also - - logWrapper.e(TAG, "handleError() throwable:" + t); - - if (t != null) { - - if (t instanceof com.google.gson.stream.MalformedJsonException) { - message = ERROR_SERVER; - } else if (t instanceof java.net.UnknownServiceException) { - message = ERROR_SECURITY_UNKNOWN; - } else { - message = ERROR_NETWORK; - } - t.printStackTrace(); - } - } - - - logWrapper.e(TAG, "handleError() returning:" + message); - - return message; - } - - - private > UserMessage parseCustomError(UserMessage provisionalErrorMessage, Response errorResponse, Class customErrorClazz) { - - Gson gson = new Gson(); - - CE customError = null; - - try { - customError = gson.fromJson(new InputStreamReader(errorResponse.errorBody().byteStream(), "UTF-8"), customErrorClazz); - } catch (UnsupportedEncodingException | IllegalStateException | NullPointerException e) { - logWrapper.e(TAG, "parseCustomError() No more error details", e); - } catch (com.google.gson.JsonSyntaxException e) {//the server probably gave us something that is not JSON - logWrapper.e(TAG, "parseCustomError() Problem parsing customServerError", e); - return ERROR_SERVER; - } - - if (customError == null) { - return provisionalErrorMessage; - } else { - return customError.getMessage(); - } - } - - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomGlobalRequestInterceptor.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomGlobalRequestInterceptor.java deleted file mode 100644 index 8846cafc..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomGlobalRequestInterceptor.java +++ /dev/null @@ -1,48 +0,0 @@ -package foo.bar.example.foreretrofit.api; - -import java.io.IOException; - -import co.early.fore.core.Affirm; -import co.early.fore.core.logging.Logger; -import foo.bar.example.foreretrofit.BuildConfig; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -/** - * This will be specific to your own app. - * - * Typically you would construct this class with some kind of Session object or similar that - * you would use to customize the headers according to the logged in status of the user, for example - */ -public class CustomGlobalRequestInterceptor implements Interceptor { - - private final static String TAG = CustomGlobalRequestInterceptor.class.getSimpleName(); - - private final Logger logger; - //private final Session session; - - public CustomGlobalRequestInterceptor(Logger logger) { - this.logger = Affirm.notNull(logger); - } - - - @Override - public Response intercept(Chain chain) throws IOException { - - Request original = chain.request(); - - Request.Builder requestBuilder = original.newBuilder(); - - - requestBuilder.addHeader("Content-Type", "application/json"); - //requestBuilder.addHeader("X-MyApp-Auth-Token", !session.hasSession() ? "expired" : session.getSessionToken()); - requestBuilder.addHeader("User-Agent", "fore-example-user-agent-" + BuildConfig.VERSION_NAME); - - - requestBuilder.method(original.method(), original.body()); - - return chain.proceed(requestBuilder.build()); - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomRetrofitBuilder.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomRetrofitBuilder.java deleted file mode 100644 index e22c5ad8..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/CustomRetrofitBuilder.java +++ /dev/null @@ -1,48 +0,0 @@ -package foo.bar.example.foreretrofit.api; - -import com.google.gson.GsonBuilder; - -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -/** - * Most of this will all be specific to your application, when customising for your own case - * bare in mind that you should be able to use this class in your tests to mock the server - * by passing different interceptors in: - * - * see @{@link co.early.fore.net.testhelpers.InterceptorStubbedService} - * - */ -public class CustomRetrofitBuilder { - - /** - * - * @param interceptors list of interceptors NB if you add a logging interceptor, it has to be - * the last one in the list - * @return Retrofit object suitable for instantiating service interfaces - */ - public static Retrofit create(Interceptor... interceptors){ - - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("http://www.mocky.io/v2/") - .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create())) - .client(createOkHttpClient(interceptors)) - .build(); - - return retrofit; - } - - private static OkHttpClient createOkHttpClient(Interceptor... interceptors){ - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - - for (Interceptor interceptor : interceptors) { - builder.addInterceptor(interceptor); - } - - return builder.build(); - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitPojo.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitPojo.java deleted file mode 100644 index 206093b1..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitPojo.java +++ /dev/null @@ -1,32 +0,0 @@ -package foo.bar.example.foreretrofit.api.fruits; - -/** - * - * - * - * - * The server returns us a list of fruit that look like this: - * - * { - * "name":"papaya", - * "isCitrus":false, - * "tastyPercentScore":98 - * } - * - * - * - * - * - */ -public class FruitPojo { - - public String name; - public boolean isCitrus; - public int tastyPercentScore; - - public FruitPojo(String name, boolean isCitrus, int tastyPercentScore) { - this.name = name; - this.isCitrus = isCitrus; - this.tastyPercentScore = tastyPercentScore; - } -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitService.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitService.java deleted file mode 100644 index 4bdbf346..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitService.java +++ /dev/null @@ -1,24 +0,0 @@ -package foo.bar.example.foreretrofit.api.fruits; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.http.GET; -import retrofit2.http.Query; - -/** - * These stubs are hosted at https://www.mocky.io/ - * - * http://www.mocky.io/v2/59efa0132e0000ef331c5f9b - * http://www.mocky.io/v2/59ef2a6c2e00002a1a1c5dea - * - */ -public interface FruitService { - - @GET("59efa0132e0000ef331c5f9b/") - Call> getFruitsSimulateOk(@Query("mocky-delay") String delayScalaDurationFormat); - - @GET("59ef2a6c2e00002a1a1c5dea/") - Call> getFruitsSimulateNotAuthorised(@Query("mocky-delay") String delayScalaDurationFormat); - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitsCustomError.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitsCustomError.java deleted file mode 100644 index 07b53041..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/api/fruits/FruitsCustomError.java +++ /dev/null @@ -1,57 +0,0 @@ -package foo.bar.example.foreretrofit.api.fruits; - -import com.google.gson.annotations.SerializedName; - -import co.early.fore.core.Affirm; -import co.early.fore.net.MessageProvider; -import foo.bar.example.foreretrofit.message.UserMessage; - -import static foo.bar.example.foreretrofit.message.UserMessage.ERROR_MISC; - -/** - * - * - * - * The server returns custom error codes in this form: - * - * { - * "errorCode":"FRUIT_USER_LOCKED" - * } - * - * - * - * - */ -public class FruitsCustomError implements MessageProvider { - - private ErrorCode errorCode; - - public FruitsCustomError(ErrorCode errorCode) { - this.errorCode = errorCode; - } - - public enum ErrorCode { - - @SerializedName("FRUIT_USER_LOGIN_CREDENTIALS_INCORRECT") - LOGIN_CREDENTIALS_INCORRECT(UserMessage.ERROR_FRUIT_USER_LOGIN_CREDENTIALS_INCORRECT), - @SerializedName("FRUIT_USER_LOCKED") - USER_LOCKED(UserMessage.ERROR_FRUIT_USER_LOCKED), - @SerializedName("FRUIT_USER_NOT_ENABLED") - USER_NOT_ENABLED(UserMessage.ERROR_FRUIT_USER_NOT_ENABLED); - - public final UserMessage userMessage; - - ErrorCode(UserMessage userMessage) { - this.userMessage = Affirm.notNull(userMessage); - } - } - - public UserMessage getMessage() { - if (errorCode == null) { - return ERROR_MISC; - } else { - return errorCode.userMessage; - } - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/feature/fruit/FruitFetcher.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/feature/fruit/FruitFetcher.java deleted file mode 100644 index 91e5cc9e..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/feature/fruit/FruitFetcher.java +++ /dev/null @@ -1,141 +0,0 @@ -package foo.bar.example.foreretrofit.feature.fruit; - -import java.util.List; -import java.util.Random; - -import co.early.fore.core.Affirm; -import co.early.fore.core.WorkMode; -import co.early.fore.core.callbacks.FailureCallbackWithPayload; -import co.early.fore.core.callbacks.SuccessCallback; -import co.early.fore.core.logging.Logger; -import co.early.fore.core.observer.ObservableImp; -import co.early.fore.net.retrofit2.CallProcessorRetrofit2; -import foo.bar.example.foreretrofit.api.fruits.FruitPojo; -import foo.bar.example.foreretrofit.api.fruits.FruitService; -import foo.bar.example.foreretrofit.api.fruits.FruitsCustomError; -import foo.bar.example.foreretrofit.message.UserMessage; - -/** - * gets a list of fruit from the network, selects one at random to be currentFruit - */ -public class FruitFetcher extends ObservableImp{ - - public static final String TAG = FruitFetcher.class.getSimpleName(); - - private final FruitService fruitService; - private final CallProcessorRetrofit2 callProcessor; - private final WorkMode workMode; - private final Logger logger; - private static Random random = new Random(); - - private boolean busy; - private FruitPojo currentFruit = new FruitPojo("(fruitless)", false, 0); - - public FruitFetcher(FruitService fruitService, CallProcessorRetrofit2 callProcessor, Logger logger, WorkMode workMode) { - super(workMode); - this.fruitService = Affirm.notNull(fruitService); - this.callProcessor = Affirm.notNull(callProcessor); - this.logger = Affirm.notNull(logger); - this.workMode = Affirm.notNull(workMode); - } - - - public void fetchFruits(final SuccessCallback successCallback, final FailureCallbackWithPayload failureCallbackWithPayload){ - - logger.i(TAG, "fetchFruits()"); - - Affirm.notNull(successCallback); - Affirm.notNull(failureCallbackWithPayload); - - if (busy){ - failureCallbackWithPayload.fail(UserMessage.ERROR_BUSY); - return; - } - - busy = true; - notifyObservers(); - - callProcessor.processCall(fruitService.getFruitsSimulateOk("3s"), workMode, FruitsCustomError.class, - successResponse -> handleSuccess(successCallback, successResponse), - failureMessage -> handleFailure(failureCallbackWithPayload, failureMessage)); - - } - - - //identical to fetchFruits() but for demo purposes the URL we point to will give us an error, we also don't specify a custom error class here - public void fetchFruitsButFailBasic(final SuccessCallback successCallback, final FailureCallbackWithPayload failureCallbackWithPayload){ - - logger.i(TAG, "fetchFruitsButFailBasic()"); - - Affirm.notNull(successCallback); - Affirm.notNull(failureCallbackWithPayload); - - if (busy){ - failureCallbackWithPayload.fail(UserMessage.ERROR_BUSY); - return; - } - - busy = true; - notifyObservers(); - - callProcessor.processCall(fruitService.getFruitsSimulateNotAuthorised("3s"), workMode, - successResponse -> handleSuccess(successCallback, successResponse), - failureMessage -> handleFailure(failureCallbackWithPayload, failureMessage)); - - } - - - //identical to fetchFruits() but for demo purposes the URL we point to will give us an error - public void fetchFruitsButFailAdvanced(final SuccessCallback successCallback, final FailureCallbackWithPayload failureCallbackWithPayload){ - - logger.i(TAG, "fetchFruitsButFailAdvanced()"); - - Affirm.notNull(successCallback); - Affirm.notNull(failureCallbackWithPayload); - - if (busy){ - failureCallbackWithPayload.fail(UserMessage.ERROR_BUSY); - return; - } - - busy = true; - notifyObservers(); - - callProcessor.processCall(fruitService.getFruitsSimulateNotAuthorised("3s"), workMode, FruitsCustomError.class, - successResponse -> handleSuccess(successCallback, successResponse), - failureMessage -> handleFailure(failureCallbackWithPayload, failureMessage)); - - } - - private void handleSuccess(SuccessCallback successCallBack, List successResponse){ - currentFruit = selectRandomFruit(successResponse); - successCallBack.success(); - complete(); - } - - private void handleFailure(FailureCallbackWithPayload failureCallbackWithPayload, UserMessage failureMessage){ - failureCallbackWithPayload.fail(failureMessage); - complete(); - } - - public boolean isBusy() { - return busy; - } - - public FruitPojo getCurrentFruit() { - return currentFruit; - } - - private void complete(){ - - logger.i(TAG, "complete()"); - - busy = false; - notifyObservers(); - } - - private FruitPojo selectRandomFruit(List listOfFruits){ - return listOfFruits.get(listOfFruits.size() == 1 ? 0 : random.nextInt(listOfFruits.size()-1)); - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/message/UserMessage.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/message/UserMessage.java deleted file mode 100644 index 06786a04..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/message/UserMessage.java +++ /dev/null @@ -1,74 +0,0 @@ -package foo.bar.example.foreretrofit.message; - -import android.os.Parcel; -import android.os.Parcelable; - -import foo.bar.example.foreretrofit.App; -import foo.bar.example.foreretrofit.R; - -/** - * As an enum, this value can be passed around the app to indicate various states. - * If you want to display it to the user you can put it inside a dialog (it implements - * parcelable). Call getString() for the human readable text. - */ -public enum UserMessage implements Parcelable { - - ERROR_MISC(R.string.msg_error_misc), - ERROR_NETWORK(R.string.msg_error_network), - ERROR_SERVER(R.string.msg_error_server), - ERROR_SECURITY_UNKNOWN(R.string.msg_error_misc), - ERROR_CLIENT(R.string.msg_error_client), - ERROR_SESSION_TIMED_OUT(R.string.msg_error_session_timeout), - ERROR_BUSY(R.string.msg_error_busy), - ERROR_CANCELLED(R.string.msg_error_cancelled), - - ERROR_FRUIT_USER_LOGIN_CREDENTIALS_INCORRECT(R.string.msg_error_fruit_user_login_creds_incorrect), - ERROR_FRUIT_USER_LOCKED(R.string.msg_error_fruit_user_locked), - ERROR_FRUIT_USER_NOT_ENABLED(R.string.msg_error_fruit_user_locked); - - - private String message; - private int messageResId; - - UserMessage(int messageResId) { - this.messageResId = messageResId; - } - - public String getString() { - - if (message == null) { - message = getString(messageResId); - } - - return message; - } - - //this should only be called when a UserMessage is actually displayed to a user, so not during a JUnit test - private String getString(int resId) { - return App.getInst().getResources().getString(resId); - } - - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(final Parcel dest, final int flags) { - dest.writeInt(ordinal()); - } - - public static final Creator CREATOR = new Creator() { - @Override - public UserMessage createFromParcel(final Parcel source) { - return UserMessage.values()[source.readInt()]; - } - - @Override - public UserMessage[] newArray(final int size) { - return new UserMessage[size]; - } - }; - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/ui/fruit/FruitActivity.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/ui/fruit/FruitActivity.java deleted file mode 100644 index d1ce0efb..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/ui/fruit/FruitActivity.java +++ /dev/null @@ -1,140 +0,0 @@ -package foo.bar.example.foreretrofit.ui.fruit; - -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.fragment.app.FragmentActivity; -import butterknife.BindView; -import butterknife.ButterKnife; -import co.early.fore.core.callbacks.FailureCallbackWithPayload; -import co.early.fore.core.callbacks.SuccessCallback; -import co.early.fore.core.observer.Observer; -import foo.bar.example.foreretrofit.OG; -import foo.bar.example.foreretrofit.R; -import foo.bar.example.foreretrofit.feature.fruit.FruitFetcher; -import foo.bar.example.foreretrofit.message.UserMessage; -import foo.bar.example.foreretrofit.ui.widgets.AnimatedTastyRatingBar; - -import static android.view.View.GONE; -import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; - - -public class FruitActivity extends FragmentActivity { - - - //models that we need to sync with - private FruitFetcher fruitFetcher = OG.get(FruitFetcher.class); - - - //UI elements that we care about - @BindView(R.id.fruit_fetchsuccess_btn) - public Button fruitRefreshSuccess; - - @BindView(R.id.fruit_fetchfailbasic_btn) - public Button fruitRefreshFailBasic; - - @BindView(R.id.fruit_fetchfailadvanced_btn) - public Button fruitRefreshFailAdvanced; - - @BindView(R.id.fruit_name_textview) - public TextView fruitName; - - @BindView(R.id.fruit_citrus_img) - public ImageView isCitrus; - - @BindView(R.id.fruit_tastyrating_tastybar) - public AnimatedTastyRatingBar tasteValuePercentView; - - @BindView(R.id.fruit_tastyrating_textview) - public TextView tasteScore; - - @BindView(R.id.fruit_busy_progbar) - public ProgressBar busy; - - @BindView(R.id.fruit_detailcontainer_linearlayout) - public View fruitDetailContainer; - - - //single observer reference - Observer observer = this::syncView; - - - - //just because we re-use these in 3 different button clicks - //in this example we define them here - private SuccessCallback successCallback = new SuccessCallback() { - @Override - public void success() { - Toast.makeText(FruitActivity.this, "Success - you can use this trigger to " + - "perform a one off action like starting a new activity or " + - "something", Toast.LENGTH_SHORT).show(); - } - }; - private FailureCallbackWithPayload failureCallback = new FailureCallbackWithPayload() { - @Override - public void fail(UserMessage userMessage) { - Toast.makeText(FruitActivity.this, "Fail - maybe tell the user to try again, message:" + userMessage.getString(), - Toast.LENGTH_SHORT).show(); - } - }; - - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_fruit); - - ButterKnife.bind(this); - - setupButtonClickListeners(); - } - - private void setupButtonClickListeners() { - - fruitRefreshSuccess.setOnClickListener(v -> fruitFetcher.fetchFruits(successCallback, failureCallback)); - - fruitRefreshFailBasic.setOnClickListener(v -> fruitFetcher.fetchFruitsButFailBasic(successCallback, failureCallback)); - - fruitRefreshFailAdvanced.setOnClickListener(v -> fruitFetcher.fetchFruitsButFailAdvanced(successCallback, failureCallback)); - - } - - - //data binding stuff below - - public void syncView(){ - fruitRefreshSuccess.setEnabled(!fruitFetcher.isBusy()); - fruitRefreshFailBasic.setEnabled(!fruitFetcher.isBusy()); - fruitRefreshFailAdvanced.setEnabled(!fruitFetcher.isBusy()); - fruitName.setText(fruitFetcher.getCurrentFruit().name); - isCitrus.setImageResource(fruitFetcher.getCurrentFruit().isCitrus ? R.drawable.lemon_positive : R.drawable.lemon_negative); - tasteValuePercentView.setTastyPercent(fruitFetcher.getCurrentFruit().tastyPercentScore); - tasteScore.setText(String.format(this.getString(R.string.fruit_percent), String.valueOf(fruitFetcher.getCurrentFruit().tastyPercentScore))); - busy.setVisibility(fruitFetcher.isBusy() ? VISIBLE : INVISIBLE); - fruitDetailContainer.setVisibility(fruitFetcher.isBusy() ? GONE : VISIBLE); - } - - - @Override - protected void onStart() { - super.onStart(); - fruitFetcher.addObserver(observer); - syncView(); // <- don't forget this - } - - - @Override - protected void onStop() { - super.onStop(); - fruitFetcher.removeObserver(observer); - } - -} diff --git a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/ui/widgets/AnimatedTastyRatingBar.java b/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/ui/widgets/AnimatedTastyRatingBar.java deleted file mode 100644 index 70042566..00000000 --- a/app-examples/example-jv-04retrofit/src/main/java/foo/bar/example/foreretrofit/ui/widgets/AnimatedTastyRatingBar.java +++ /dev/null @@ -1,111 +0,0 @@ -package foo.bar.example.foreretrofit.ui.widgets; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; - -import androidx.appcompat.widget.AppCompatImageView; -import foo.bar.example.foreretrofit.App; -import foo.bar.example.foreretrofit.R; - -/** - * - */ -public class AnimatedTastyRatingBar extends AppCompatImageView { - - private Paint paint; - - private float widthPx; - private float heightPx; - - private int backgroundColour; - private int progressColour; - - private float tastyPercent = 0; - private float currentPercent = 0; - private float step = 1; - private boolean increasing = false; - - public AnimatedTastyRatingBar(Context context) { - super(context); - } - - public AnimatedTastyRatingBar(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AnimatedTastyRatingBar(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - backgroundColour = App.getInst().getResources().getColor(R.color.colorPrimary); - progressColour = App.getInst().getResources().getColor(R.color.colorPrimaryDark); - - paint = new Paint(); - paint.setFlags(Paint.ANTI_ALIAS_FLAG); - paint.setStyle(Paint.Style.FILL_AND_STROKE); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - widthPx = getWidth(); - heightPx = getHeight(); - - invalidate(); - } - - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - //background - paint.setColor(backgroundColour); - canvas.drawRect(0, 0, widthPx, heightPx, paint); - - //percent bar - paint.setColor(progressColour); - canvas.drawRect(0, 0, (float)((currentPercent * widthPx)/100), heightPx, paint); - - progressAnimation(); - } - - public void setTastyPercent(float tastyPercent){ - this.tastyPercent = tastyPercent; - - if (currentPercent == 0){//don't bother with the animation - currentPercent = tastyPercent; - } - - increasing = (tastyPercent > currentPercent); - - invalidate(); - } - - - private void progressAnimation() { - - if (increasing && currentPercent < tastyPercent) { - - currentPercent = Math.min(tastyPercent, currentPercent + step); - - invalidate(); - - } else if (!increasing && currentPercent > tastyPercent) { - - currentPercent = Math.max(tastyPercent, currentPercent - step); - - invalidate(); - } - } - - -} diff --git a/app-examples/example-jv-04retrofit/src/main/res/layout/activity_fruit.xml b/app-examples/example-jv-04retrofit/src/main/res/layout/activity_fruit.xml deleted file mode 100644 index 1b4fb486..00000000 --- a/app-examples/example-jv-04retrofit/src/main/res/layout/activity_fruit.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -