diff --git a/.github/workflows/check_pr.yml b/.github/workflows/check_pr.yml index f0b8098d9..bf1c8ddc1 100644 --- a/.github/workflows/check_pr.yml +++ b/.github/workflows/check_pr.yml @@ -1,13 +1,6 @@ name: Check PR on: -# push: -# branches: -# '**' -# paths-ignore: -# - '**.md' -# - '.idea/**' -# - '.github/**' pull_request: branches: '**' @@ -19,7 +12,7 @@ on: concurrency: group: environment-${{ github.ref }} - cancel-in-progress: true +# cancel-in-progress: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -75,4 +68,4 @@ jobs: run: ./gradlew testDebugUnitTest - name: Build - run: ./gradlew compileDebugKotlin \ No newline at end of file + run: ./gradlew compileDebugKotlin diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 161b06a99..def7b23ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,8 +15,24 @@ jobs: steps: - uses: actions/checkout@v2 + +# - name: Build GMS Release Artifacts +# run: ./gradlew assembleGmsRelease + +# - name: Build HMS Release Artifacts +# run: ./gradlew assembleHmsRelease + +# - name: Build FOSS Release Artifacts +# run: ./gradlew assembleFossRelease + +# - name: Upload Artifacts to Outputs +# uses: actions/upload-artifact@v2 +# with: +# path: | +# app/build/outputs/apk + - uses: ncipollo/release-action@v1 with: - artifacts: "release.tar.gz,foo/*.txt" + artifacts: "release.tar.gz,foo/*.txt,android-app/build/outputs/apk/**/*.apk" artifactErrorsFailBuild: false - generateReleaseNotes: true \ No newline at end of file + generateReleaseNotes: true diff --git a/.gitignore b/.gitignore index 67634d13b..6d04bccee 100644 --- a/.gitignore +++ b/.gitignore @@ -719,3 +719,2504 @@ /core/build/reports/detekt/detekt.xml /buildSrc/build/classes/kotlin/main/MoviesBuildType.class /buildSrc/build/source-roots/buildSrc/source-roots.txt +/buildSrc/build/kotlin/compileKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +/android-app/src/main/kotlin/org/michaelbel/sandbox/Incrementer.java +/android-app/src/main/kotlin/org/michaelbel/sandbox/JavaSandbox.java +/android-app/src/main/kotlin/org/michaelbel/sandbox/KotlinSandbox.kt +/core/repository-impl/build/.transforms/6c0368dea75ac7dc192a6313cfc99df7/results.bin +/core/repository-impl/build/.transforms/8f1df34a8401b060f309153b97a84bad/results.bin +/core/repository-impl/build/.transforms/80ee83231febe17e04c005c55b518776/results.bin +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/di/RepositoryModule.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ktx/AccountKtxKt.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ktx/MovieKtxKt.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AccountRepositoryImpl$accountDetails$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AccountRepositoryImpl.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AccountRepositoryImpl_Factory.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createRequestToken$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSession$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSessionWithLogin$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$deleteSession$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/AuthenticationRepositoryImpl_Factory.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ImageRepositoryImpl$images$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ImageRepositoryImpl.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/ImageRepositoryImpl_Factory.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/MovieRepositoryImpl$insertAllMovies$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/MovieRepositoryImpl$movieDetails$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/MovieRepositoryImpl$moviesResult$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/MovieRepositoryImpl$page$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/MovieRepositoryImpl.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/MovieRepositoryImpl_Factory.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/NotificationRepositoryImpl$notificationExpireTime$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/NotificationRepositoryImpl.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/NotificationRepositoryImpl_Factory.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2$1.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/debug_dex/org/michaelbel/movies/repository/SettingsRepositoryImpl_Factory.dex +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/transformed/debug/desugar_graph.bin +/core/repository-impl/build/.transforms/184fb2a574651ea263d9a49407dd5084/results.bin +/core/repository-impl/build/.transforms/0192a91232e80986c59f2e8e38ee5282/transformed/classes/classes_dex/classes.dex +/core/repository-impl/build/.transforms/0192a91232e80986c59f2e8e38ee5282/results.bin +/core/repository-impl/build/.transforms/a4ac852d958d958ca7f8913c1c4313b3/results.bin +/core/repository-impl/build/.transforms/b943d75c248f940d1243420c44255680/transformed/hiltAggregated.jar +/core/repository-impl/build/.transforms/b943d75c248f940d1243420c44255680/results.bin +/core/repository-impl/build/.transforms/cf7352294fd9278a97f253a09036c7ec/results.bin +/core/repository-impl/build/.transforms/d9f1eee006100215a5cfc5a9782125e2/results.bin +/core/repository-impl/build/generated/ksp/debug/java/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.java +/core/repository-impl/build/generated/ksp/debug/java/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.java +/core/repository-impl/build/generated/ksp/debug/java/org/michaelbel/movies/repository/AccountRepositoryImpl_Factory.java +/core/repository-impl/build/generated/ksp/debug/java/org/michaelbel/movies/repository/AuthenticationRepositoryImpl_Factory.java +/core/repository-impl/build/generated/ksp/debug/java/org/michaelbel/movies/repository/ImageRepositoryImpl_Factory.java +/core/repository-impl/build/generated/ksp/debug/java/org/michaelbel/movies/repository/MovieRepositoryImpl_Factory.java +/core/repository-impl/build/generated/ksp/debug/java/org/michaelbel/movies/repository/NotificationRepositoryImpl_Factory.java +/core/repository-impl/build/generated/ksp/debug/java/org/michaelbel/movies/repository/SettingsRepositoryImpl_Factory.java +/core/repository-impl/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +/core/repository-impl/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +/core/repository-impl/build/intermediates/aar_metadata/debug/aar-metadata.properties +/core/repository-impl/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/META-INF/repository-impl_debug.kotlin_module +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/di/RepositoryModule.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/AccountKtxKt.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/MovieKtxKt.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepositoryImpl$accountDetails$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepositoryImpl.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createRequestToken$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSession$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSessionWithLogin$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$deleteSession$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AuthenticationRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ImageRepositoryImpl$images$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ImageRepositoryImpl.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ImageRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/MovieRepositoryImpl$insertAllMovies$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/MovieRepositoryImpl$movieDetails$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/MovieRepositoryImpl$moviesResult$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/MovieRepositoryImpl$page$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/MovieRepositoryImpl.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/MovieRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/NotificationRepositoryImpl$notificationExpireTime$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/NotificationRepositoryImpl.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/NotificationRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2$1.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl.class +/core/repository-impl/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/compile_library_classes_jar/debug/classes.jar +/core/repository-impl/build/intermediates/compile_r_class_jar/debug/R.jar +/core/repository-impl/build/intermediates/compile_symbol_list/debug/R.txt +/core/repository-impl/build/intermediates/full_jar/debug/full.jar +/core/repository-impl/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +/core/repository-impl/build/intermediates/incremental/debug/packageDebugResources/merger.xml +/core/repository-impl/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +/core/repository-impl/build/intermediates/incremental/mergeDebugShaders/merger.xml +/core/repository-impl/build/intermediates/incremental/packageDebugAssets/merger.xml +/core/repository-impl/build/intermediates/java_res/debug/out/META-INF/repository-impl_debug.kotlin_module +/core/repository-impl/build/intermediates/javac/debug/classes/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.class +/core/repository-impl/build/intermediates/javac/debug/classes/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.class +/core/repository-impl/build/intermediates/javac/debug/classes/org/michaelbel/movies/repository/AccountRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/javac/debug/classes/org/michaelbel/movies/repository/AuthenticationRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/javac/debug/classes/org/michaelbel/movies/repository/ImageRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/javac/debug/classes/org/michaelbel/movies/repository/MovieRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/javac/debug/classes/org/michaelbel/movies/repository/NotificationRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/javac/debug/classes/org/michaelbel/movies/repository/SettingsRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/local_only_symbol_list/debug/R-def.txt +/core/repository-impl/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +/core/repository-impl/build/intermediates/merged_manifest/debug/AndroidManifest.xml +/core/repository-impl/build/intermediates/navigation_json/debug/navigation.json +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/META-INF/repository-impl_debug.kotlin_module +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/di/RepositoryModule.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ktx/AccountKtxKt.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ktx/MovieKtxKt.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$accountDetails$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AccountRepositoryImpl.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AccountRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createRequestToken$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSession$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSessionWithLogin$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$deleteSession$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ImageRepositoryImpl$images$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ImageRepositoryImpl.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/ImageRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$insertAllMovies$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$movieDetails$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$moviesResult$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$page$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/MovieRepositoryImpl.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/MovieRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/NotificationRepositoryImpl$notificationExpireTime$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/NotificationRepositoryImpl.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/NotificationRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2$1.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl.class +/core/repository-impl/build/intermediates/runtime_library_classes_dir/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl_Factory.class +/core/repository-impl/build/intermediates/runtime_library_classes_jar/debug/classes.jar +/core/repository-impl/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +/core/repository-impl/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +/core/repository-impl/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +/core/repository-impl/build/kotlin/compileDebugKotlin/local-state/build-history.bin +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/1/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.java +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/1/org/michaelbel/movies/repository/AccountRepositoryImpl_Factory.java +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/1/org/michaelbel/movies/repository/AuthenticationRepositoryImpl_Factory.java +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/1/org/michaelbel/movies/repository/ImageRepositoryImpl_Factory.java +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/1/org/michaelbel/movies/repository/MovieRepositoryImpl_Factory.java +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/1/org/michaelbel/movies/repository/NotificationRepositoryImpl_Factory.java +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/1/org/michaelbel/movies/repository/SettingsRepositoryImpl_Factory.java +/core/repository-impl/build/kspCaches/debug/backups/java/byRounds/2/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.java +/core/repository-impl/build/kspCaches/debug/classLookups/counters.tab +/core/repository-impl/build/kspCaches/debug/classLookups/file-to-id.tab +/core/repository-impl/build/kspCaches/debug/classLookups/file-to-id.tab.keystream +/core/repository-impl/build/kspCaches/debug/classLookups/file-to-id.tab.keystream.len +/core/repository-impl/build/kspCaches/debug/classLookups/file-to-id.tab.len +/core/repository-impl/build/kspCaches/debug/classLookups/file-to-id.tab.values.at +/core/repository-impl/build/kspCaches/debug/classLookups/file-to-id.tab_i +/core/repository-impl/build/kspCaches/debug/classLookups/file-to-id.tab_i.len +/core/repository-impl/build/kspCaches/debug/classLookups/id-to-file.tab +/core/repository-impl/build/kspCaches/debug/classLookups/id-to-file.tab.keystream +/core/repository-impl/build/kspCaches/debug/classLookups/id-to-file.tab.keystream.len +/core/repository-impl/build/kspCaches/debug/classLookups/id-to-file.tab.len +/core/repository-impl/build/kspCaches/debug/classLookups/id-to-file.tab.values.at +/core/repository-impl/build/kspCaches/debug/classLookups/id-to-file.tab_i +/core/repository-impl/build/kspCaches/debug/classLookups/id-to-file.tab_i.len +/core/repository-impl/build/kspCaches/debug/classLookups/lookups.tab +/core/repository-impl/build/kspCaches/debug/classLookups/lookups.tab.keystream +/core/repository-impl/build/kspCaches/debug/classLookups/lookups.tab.keystream.len +/core/repository-impl/build/kspCaches/debug/classLookups/lookups.tab.len +/core/repository-impl/build/kspCaches/debug/classLookups/lookups.tab.values.at +/core/repository-impl/build/kspCaches/debug/classLookups/lookups.tab_i +/core/repository-impl/build/kspCaches/debug/classLookups/lookups.tab_i.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/counters.tab +/core/repository-impl/build/kspCaches/debug/symbolLookups/file-to-id.tab +/core/repository-impl/build/kspCaches/debug/symbolLookups/file-to-id.tab.keystream +/core/repository-impl/build/kspCaches/debug/symbolLookups/file-to-id.tab.keystream.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/file-to-id.tab.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/file-to-id.tab.values.at +/core/repository-impl/build/kspCaches/debug/symbolLookups/file-to-id.tab_i +/core/repository-impl/build/kspCaches/debug/symbolLookups/file-to-id.tab_i.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/id-to-file.tab +/core/repository-impl/build/kspCaches/debug/symbolLookups/id-to-file.tab.keystream +/core/repository-impl/build/kspCaches/debug/symbolLookups/id-to-file.tab.keystream.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/id-to-file.tab.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/id-to-file.tab.values.at +/core/repository-impl/build/kspCaches/debug/symbolLookups/id-to-file.tab_i +/core/repository-impl/build/kspCaches/debug/symbolLookups/id-to-file.tab_i.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/lookups.tab +/core/repository-impl/build/kspCaches/debug/symbolLookups/lookups.tab.keystream +/core/repository-impl/build/kspCaches/debug/symbolLookups/lookups.tab.keystream.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/lookups.tab.len +/core/repository-impl/build/kspCaches/debug/symbolLookups/lookups.tab.values.at +/core/repository-impl/build/kspCaches/debug/symbolLookups/lookups.tab_i +/core/repository-impl/build/kspCaches/debug/symbolLookups/lookups.tab_i.len +/core/repository-impl/build/kspCaches/debug/ap-classpath-entries.bin +/core/repository-impl/build/kspCaches/debug/caches.uptodate +/core/repository-impl/build/kspCaches/debug/classpath-entries.bin +/core/repository-impl/build/kspCaches/debug/classpath-structure.bin +/core/repository-impl/build/kspCaches/debug/sourceToOutputs +/core/repository-impl/build/kspCaches/debug/sourceToOutputs.keystream +/core/repository-impl/build/kspCaches/debug/sourceToOutputs.keystream.len +/core/repository-impl/build/kspCaches/debug/sourceToOutputs.len +/core/repository-impl/build/kspCaches/debug/sourceToOutputs.values.at +/core/repository-impl/build/kspCaches/debug/sourceToOutputs_i +/core/repository-impl/build/kspCaches/debug/sourceToOutputs_i.len +/core/repository-impl/build/kspCaches/debug/symbols +/core/repository-impl/build/kspCaches/debug/symbols.keystream +/core/repository-impl/build/kspCaches/debug/symbols.keystream.len +/core/repository-impl/build/kspCaches/debug/symbols.len +/core/repository-impl/build/kspCaches/debug/symbols.values.at +/core/repository-impl/build/kspCaches/debug/symbols_i +/core/repository-impl/build/kspCaches/debug/symbols_i.len +/core/repository-impl/build/outputs/logs/manifest-merger-debug-report.txt +/core/repository-impl/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +/core/repository-impl/build/tmp/kotlin-classes/debug/META-INF/repository-impl_debug.kotlin_module +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/di/RepositoryModule.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/AccountKtxKt.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/MovieKtxKt.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$accountDetails$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AccountRepositoryImpl$special$$inlined$map$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AccountRepositoryImpl.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createRequestToken$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSession$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$createSessionWithLogin$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl$deleteSession$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ImageRepositoryImpl$images$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ImageRepositoryImpl.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$insertAllMovies$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$movieDetails$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$moviesResult$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/MovieRepositoryImpl$page$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/MovieRepositoryImpl.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/NotificationRepositoryImpl$notificationExpireTime$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/NotificationRepositoryImpl.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$3.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$4.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2$1.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5$2.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl$special$$inlined$map$5.class +/core/repository-impl/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepositoryImpl.class +/shared/build/bin/iosSimulatorArm64/debugFramework/shared.framework/Headers/shared.h +/shared/build/bin/iosSimulatorArm64/debugFramework/shared.framework/Modules/module.modulemap +/shared/build/bin/iosSimulatorArm64/debugFramework/shared.framework/Info.plist +/shared/build/bin/iosSimulatorArm64/debugFramework/shared.framework/shared +/shared/build/classes/kotlin/iosSimulatorArm64/main/klib/shared.klib +/shared/build/xcode-frameworks/Debug/iphonesimulator17.4/shared.framework/Headers/shared.h +/shared/build/xcode-frameworks/Debug/iphonesimulator17.4/shared.framework/Modules/module.modulemap +/shared/build/xcode-frameworks/Debug/iphonesimulator17.4/shared.framework/Info.plist +/shared/build/xcode-frameworks/Debug/iphonesimulator17.4/shared.framework/shared +/shared/build/xcode-version.txt +/core/repository-kmp/build/.transforms/1a7f668ad9ac9ce473e111f242c02a6a/transformed/classes/classes_dex/classes.dex +/core/repository-kmp/build/.transforms/1a7f668ad9ac9ce473e111f242c02a6a/results.bin +/core/repository-kmp/build/.transforms/1fe671ae4393e4e027f84b37ce9a5270/results.bin +/core/repository-kmp/build/.transforms/9f7523ccf41e0ad349acb9a12502ef08/transformed/classes/classes_dex/classes.dex +/core/repository-kmp/build/.transforms/9f7523ccf41e0ad349acb9a12502ef08/results.bin +/core/repository-kmp/build/.transforms/22b99cba3b8c91f978fe0a84571b2cf8/results.bin +/core/repository-kmp/build/.transforms/46e87eaba0d4a69ba3316594742789e6/results.bin +/core/repository-kmp/build/.transforms/978f157a4380ba21692bb2f281306dec/results.bin +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res$drawable.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res$font.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res$string.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Amoled.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Brown.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Companion.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Purple.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Red.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/MoviesAliasKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$onShareUrl$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$resultContract$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$onStartSpeechRecognize$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$speechRecognizeContract$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$4$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$4$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-4$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-4$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-5$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-6$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-7$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-8$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-9$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContent$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$onCheckConnectivityClick$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$settingsPanelContract$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailureAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailurePreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoading$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBox$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBox$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatar$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBox$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$1$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$activityContract$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$permissionContract$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIcon$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconAmoledPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconPreview$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/CatKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/GithubKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/GooglePlayKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/MoviesIcons.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/ThemeLightDarkKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/AccountDbKtxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ComposableKtxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ConfigurationKtxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/LazyPagingItemsKtxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$$inlined$debugInspectorInfo$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/SettingsKtxKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1$invoke$$inlined$onDispose$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlightKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$3.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/Fade.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$fadeAnimationSpec$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$shimmerAnimationSpec$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight$Companion.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderHightlightKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$2.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4$1$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder-cf5BqRc$$inlined$debugInspectorInfo$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/Shimmer.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/AccountPreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/AppearancePreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/BooleanPreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/IconAliasPreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/LanguagePreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/MovieListPreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/SuggestionDbPreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/ThemePreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/TitlePreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/VersionPreviewParameterProvider.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceLandscapePreview.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DevicePreviews.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceUserPreviews.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/shortcuts/MoviesShortcutsKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/model/ComposeTheme.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/provider/MoviesRippleTheme.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ColorsKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ShapeKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$2$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$3$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4$1.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$5.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/TypeKt.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/tile/MoviesTileService.dex +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +/core/repository-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/results.bin +/core/repository-kmp/build/.transforms/4961e6432b183d0c31da81f3d82257b4/results.bin +/core/repository-kmp/build/.transforms/7195f730d31a0480017222b46316f6d1/results.bin +/core/repository-kmp/build/.transforms/8799aceebbda839e5866660ee865e3da/transformed/hiltAggregated.jar +/core/repository-kmp/build/.transforms/8799aceebbda839e5866660ee865e3da/results.bin +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/di/RepositoryModule.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$accountDetails$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AccountRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createRequestToken$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSession$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSessionWithLogin$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$deleteSession$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/ImageRepositoryImpl$images$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/ImageRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovie$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovies$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movie$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movieDetails$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesResult$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesWidget$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/MovieRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl$notificationExpireTime$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SearchRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl$updateSuggestions$1.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl_Factory.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/ktx/AccountKtxKt.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/ktx/MovieKtxKt.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/AccountRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/AuthenticationRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/ImageRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/MovieRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/NotificationRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/PagingKeyRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/SearchRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/SettingsRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/repository/SuggestionRepository.dex +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +/core/repository-kmp/build/.transforms/179293bf0f00244b69a8ae4d650c5075/results.bin +/core/repository-kmp/build/.transforms/a3f5543f02789d37ca0fc15b632c925e/results.bin +/core/repository-kmp/build/.transforms/cb6773cc78f1a718b8acc361d860a24b/results.bin +/core/repository-kmp/build/.transforms/dd4d9811ce6beedf72052f8ce8654d5f/results.bin +/core/repository-kmp/build/.transforms/dd0908c841a788912a26ddee00dece3b/results.bin +/core/repository-kmp/build/.transforms/ebda093656123b2160ee3f919e357819/results.bin +/core/repository-kmp/build/.transforms/f183a09daae1fba5095ade06f27f8ef3/results.bin +/core/repository-kmp/build/.transforms/f5938e843bcceb94a2574e1f2ef59c55/results.bin +/core/repository-kmp/build/generated/compose/resourceGenerator/kotlin/movies/core/repository-kmp/generated/resources/Res.kt +/core/repository-kmp/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +/core/repository-kmp/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +/core/repository-kmp/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +/core/repository-kmp/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/META-INF/repository-kmp_debug.kotlin_module +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/di/RepositoryModule.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$accountDetails$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AccountRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createRequestToken$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSession$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSessionWithLogin$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$deleteSession$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/ImageRepositoryImpl$images$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/ImageRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovie$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovies$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movie$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movieDetails$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesResult$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesWidget$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/MovieRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl$notificationExpireTime$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SearchRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl$updateSuggestions$1.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/AccountKtxKt.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/MovieKtxKt.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AccountRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/AuthenticationRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/ImageRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/MovieRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/NotificationRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/PagingKeyRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SearchRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SettingsRepository.class +/core/repository-kmp/build/intermediates/classes/debug/transformDebugClassesWithAsm/dirs/org/michaelbel/movies/repository/SuggestionRepository.class +/core/repository-kmp/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +/core/repository-kmp/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +/core/repository-kmp/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +/core/repository-kmp/build/intermediates/full_jar/debug/createFullJarDebug/full.jar +/core/repository-kmp/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +/core/repository-kmp/build/intermediates/incremental/debug/packageDebugResources/merger.xml +/core/repository-kmp/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +/core/repository-kmp/build/intermediates/incremental/mergeDebugShaders/merger.xml +/core/repository-kmp/build/intermediates/incremental/packageDebugAssets/merger.xml +/core/repository-kmp/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/repository-kmp_debug.kotlin_module +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/AccountRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/ImageRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/MovieRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/SearchRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +/core/repository-kmp/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +/core/repository-kmp/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +/core/repository-kmp/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +/core/repository-kmp/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/META-INF/repository-kmp_debug.kotlin_module +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/di/RepositoryModule.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$accountDetails$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createRequestToken$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSession$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSessionWithLogin$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$deleteSession$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/ImageRepositoryImpl$images$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/ImageRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovie$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovies$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movie$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movieDetails$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesResult$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesWidget$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl$notificationExpireTime$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SearchRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl$updateSuggestions$1.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl_Factory.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/ktx/AccountKtxKt.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/ktx/MovieKtxKt.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/AccountRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/AuthenticationRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/ImageRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/MovieRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/NotificationRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/PagingKeyRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/SearchRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/SettingsRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/repository/SuggestionRepository.class +/core/repository-kmp/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +/core/repository-kmp/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/counters.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.keystream +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.values.at +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab_i +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab_i.len +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/last-build.bin +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/classpath-snapshot/shrunk-classpath-snapshot.bin +/core/repository-kmp/build/kotlin/compileDebugKotlinAndroid/local-state/build-history.bin +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/AccountRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/ImageRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/MovieRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/SearchRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/1/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl_Factory.java +/core/repository-kmp/build/kspCaches/android/androidDebug/backups/java/byRounds/2/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.java +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/counters.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/file-to-id.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/file-to-id.tab.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/file-to-id.tab.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/file-to-id.tab.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/file-to-id.tab.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/file-to-id.tab_i +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/file-to-id.tab_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/id-to-file.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/id-to-file.tab.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/id-to-file.tab.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/id-to-file.tab.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/id-to-file.tab.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/id-to-file.tab_i +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/id-to-file.tab_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/lookups.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/lookups.tab.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/lookups.tab.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/lookups.tab.len +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/lookups.tab.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/lookups.tab_i +/core/repository-kmp/build/kspCaches/android/androidDebug/classLookups/lookups.tab_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/counters.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/file-to-id.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/file-to-id.tab.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/file-to-id.tab.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/file-to-id.tab.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/file-to-id.tab.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/file-to-id.tab_i +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/file-to-id.tab_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/id-to-file.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/id-to-file.tab.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/id-to-file.tab.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/id-to-file.tab.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/id-to-file.tab.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/id-to-file.tab_i +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/id-to-file.tab_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/lookups.tab +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/lookups.tab.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/lookups.tab.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/lookups.tab.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/lookups.tab.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/lookups.tab_i +/core/repository-kmp/build/kspCaches/android/androidDebug/symbolLookups/lookups.tab_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/ap-classpath-entries.bin +/core/repository-kmp/build/kspCaches/android/androidDebug/caches.uptodate +/core/repository-kmp/build/kspCaches/android/androidDebug/classpath-entries.bin +/core/repository-kmp/build/kspCaches/android/androidDebug/classpath-structure.bin +/core/repository-kmp/build/kspCaches/android/androidDebug/sealed +/core/repository-kmp/build/kspCaches/android/androidDebug/sealed.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/sealed.len +/core/repository-kmp/build/kspCaches/android/androidDebug/sealed_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/sourceToOutputs +/core/repository-kmp/build/kspCaches/android/androidDebug/sourceToOutputs.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/sourceToOutputs.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/sourceToOutputs.len +/core/repository-kmp/build/kspCaches/android/androidDebug/sourceToOutputs.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/sourceToOutputs_i +/core/repository-kmp/build/kspCaches/android/androidDebug/sourceToOutputs_i.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbols +/core/repository-kmp/build/kspCaches/android/androidDebug/symbols.keystream +/core/repository-kmp/build/kspCaches/android/androidDebug/symbols.keystream.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbols.len +/core/repository-kmp/build/kspCaches/android/androidDebug/symbols.values.at +/core/repository-kmp/build/kspCaches/android/androidDebug/symbols_i +/core/repository-kmp/build/kspCaches/android/androidDebug/symbols_i.len +/core/repository-kmp/build/outputs/logs/manifest-merger-debug-report.txt +/core/repository-kmp/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +/core/repository-kmp/build/tmp/kotlin-classes/debug/META-INF/repository-kmp_debug.kotlin_module +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/di/RepositoryModule.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$accountDetails$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$flatMapLatest$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl$special$$inlined$map$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createRequestToken$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSession$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$createSessionWithLogin$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl$deleteSession$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/ImageRepositoryImpl$images$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovie$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$insertMovies$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movie$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$movieDetails$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesResult$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl$moviesWidget$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl$notificationExpireTime$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1$2.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2$2.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$2.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3$2.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$3.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4$2.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$4.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5$2.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl$special$$inlined$map$5.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl$updateSuggestions$1.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/AccountKtxKt.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/ExceptionKtxKt.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/MovieKtxKt.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/MovieResponseKtxKt.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ktx/PackageInfoKtxKt.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AccountRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/AuthenticationRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/ImageRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/MovieRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/NotificationRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/PagingKeyRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SearchRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SettingsRepository.class +/core/repository-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/repository/SuggestionRepository.class +/core/ui-kmp/build/.transforms/1fe671ae4393e4e027f84b37ce9a5270/results.bin +/core/ui-kmp/build/.transforms/9f7523ccf41e0ad349acb9a12502ef08/transformed/classes/classes_dex/classes.dex +/core/ui-kmp/build/.transforms/9f7523ccf41e0ad349acb9a12502ef08/results.bin +/core/ui-kmp/build/.transforms/978f157a4380ba21692bb2f281306dec/results.bin +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res$drawable.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res$font.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res$string.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/movies/core/ui-kmp/generated/resources/Res.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Amoled.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Brown.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Companion.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Purple.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias$Red.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/IconAlias.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/appicon/MoviesAliasKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$onShareUrl$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$resultContract$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$onStartSpeechRecognize$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$speechRecognizeContract$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$4$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieColumnKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$4$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/movie/MovieRowKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-4$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-4$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-5$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-6$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-7$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-8$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-9$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContent$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageContentKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$onCheckConnectivityClick$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$settingsPanelContract$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailureAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailurePreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageFailureKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoading$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PageLoadingKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBox$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBox$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatar$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/AccountAvatarKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBox$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ApiKeyBoxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$1$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$activityContract$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$permissionContract$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIcon$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconAmoledPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconPreview$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/compose/SwitchCheckIconKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/CatKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/GithubKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/GooglePlayKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/MoviesIcons.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/icons/ThemeLightDarkKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/AccountDbKtxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ComposableKtxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ConfigurationKtxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/LazyPagingItemsKtxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$$inlined$debugInspectorInfo$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/ModifierKtxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/ktx/SettingsKtxKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1$invoke$$inlined$onDispose$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlightKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$3.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/Fade.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$fadeAnimationSpec$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$shimmerAnimationSpec$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight$Companion.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderHightlightKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$2.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4$1$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder-cf5BqRc$$inlined$debugInspectorInfo$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/PlaceholderKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/placeholder/Shimmer.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/AccountPreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/AppearancePreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/BooleanPreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/IconAliasPreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/LanguagePreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/MovieListPreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/SuggestionDbPreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/ThemePreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/TitlePreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/provider/VersionPreviewParameterProvider.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceLandscapePreview.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DevicePreviews.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/preview/DeviceUserPreviews.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/shortcuts/MoviesShortcutsKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/model/ComposeTheme.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/provider/MoviesRippleTheme.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ColorsKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ShapeKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$2$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$3$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4$1.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$5.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/ThemeKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/theme/TypeKt.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/org/michaelbel/movies/ui/tile/MoviesTileService.dex +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +/core/ui-kmp/build/.transforms/4387c1537cfc8edfbee598e3c04b391b/results.bin +/core/ui-kmp/build/.transforms/4961e6432b183d0c31da81f3d82257b4/results.bin +/core/ui-kmp/build/.transforms/7195f730d31a0480017222b46316f6d1/results.bin +/core/ui-kmp/build/.transforms/a3f5543f02789d37ca0fc15b632c925e/results.bin +/core/ui-kmp/build/.transforms/ebda093656123b2160ee3f919e357819/results.bin +/core/ui-kmp/build/.transforms/f5938e843bcceb94a2574e1f2ef59c55/results.bin +/core/ui-kmp/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +/core/ui-kmp/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +/core/ui-kmp/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +/core/ui-kmp/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +/core/ui-kmp/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +/core/ui-kmp/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +/core/ui-kmp/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_18_up_rating_outline_24.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_file_download_24.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_launcher_icon_amoled.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_launcher_icon_brown.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_launcher_icon_purple.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_launcher_icon_red.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_movie_filter_24.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_search_amoled_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_search_brown_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_search_purple_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_search_red_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_settings_amoled_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_settings_brown_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_settings_purple_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_shortcut_settings_red_48.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_splash_screen_animated_icon.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_splash_screen_branding_image.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/drawable_ic_tmdb_logo.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_bold.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_bold_italic.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_condensed_bold.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_condensed_light.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_condensed_light_italic.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_extrabold.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_extrabold_italic.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_italic.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_light.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_light_italic.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_semibold.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/font_open_sans_semibold_italic.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-anydpi-v26_ic_launcher_amoled.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-anydpi-v26_ic_launcher_brown.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-anydpi-v26_ic_launcher_purple.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-anydpi-v26_ic_launcher_red.xml.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_background_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_background_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_background_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_background_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_foreground_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_foreground_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_foreground_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_foreground_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_monochrome_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_monochrome_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_monochrome_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_monochrome_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-hdpi-v4_ic_launcher_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_background_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_background_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_background_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_background_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_foreground_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_foreground_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_foreground_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_foreground_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_monochrome_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_monochrome_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_monochrome_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_monochrome_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-mdpi-v4_ic_launcher_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_background_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_background_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_background_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_background_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_foreground_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_foreground_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_foreground_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_foreground_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_monochrome_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_monochrome_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_monochrome_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_monochrome_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xhdpi-v4_ic_launcher_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_background_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_background_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_background_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_background_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_foreground_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_foreground_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_foreground_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_foreground_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_monochrome_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_monochrome_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_monochrome_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_monochrome_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxhdpi-v4_ic_launcher_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_background_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_background_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_background_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_background_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_foreground_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_foreground_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_foreground_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_foreground_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_monochrome_amoled.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_monochrome_brown.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_monochrome_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_monochrome_red.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_purple.png.flat +/core/ui-kmp/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/mipmap-xxxhdpi-v4_ic_launcher_red.png.flat +/core/ui-kmp/build/intermediates/full_jar/debug/createFullJarDebug/full.jar +/core/ui-kmp/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml +/core/ui-kmp/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values-night-v8/values-night-v8.xml +/core/ui-kmp/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values-ru/values-ru.xml +/core/ui-kmp/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +/core/ui-kmp/build/intermediates/incremental/debug/packageDebugResources/merger.xml +/core/ui-kmp/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +/core/ui-kmp/build/intermediates/incremental/mergeDebugShaders/merger.xml +/core/ui-kmp/build/intermediates/incremental/packageDebugAssets/merger.xml +/core/ui-kmp/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/ui-kmp_debug.kotlin_module +/core/ui-kmp/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +/core/ui-kmp/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +/core/ui-kmp/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +/core/ui-kmp/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +/core/ui-kmp/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_18_up_rating_outline_24.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_file_download_24.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_launcher_icon_amoled.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_launcher_icon_brown.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_launcher_icon_purple.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_launcher_icon_red.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_movie_filter_24.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_search_amoled_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_search_brown_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_search_purple_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_search_red_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_settings_amoled_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_settings_brown_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_settings_purple_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_shortcut_settings_red_48.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_splash_screen_animated_icon.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_splash_screen_branding_image.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_tmdb_logo.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_bold.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_bold_italic.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_condensed_bold.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_condensed_light.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_condensed_light_italic.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_extrabold.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_extrabold_italic.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_italic.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_light.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_light_italic.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_semibold.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/font/open_sans_semibold_italic.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-anydpi-v26/ic_launcher_amoled.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-anydpi-v26/ic_launcher_brown.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-anydpi-v26/ic_launcher_purple.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-anydpi-v26/ic_launcher_red.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_background_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_background_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_background_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_background_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_foreground_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_foreground_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_foreground_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_foreground_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_monochrome_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_monochrome_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_monochrome_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_monochrome_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-hdpi-v4/ic_launcher_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_background_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_background_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_background_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_background_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_foreground_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_foreground_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_foreground_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_foreground_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_monochrome_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_monochrome_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_monochrome_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_monochrome_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-mdpi-v4/ic_launcher_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_background_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_background_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_background_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_background_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_foreground_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_foreground_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_foreground_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_foreground_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_monochrome_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_monochrome_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_monochrome_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_monochrome_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xhdpi-v4/ic_launcher_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_background_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_background_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_background_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_background_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_foreground_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_foreground_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_foreground_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_foreground_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_monochrome_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_monochrome_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_monochrome_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_monochrome_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxhdpi-v4/ic_launcher_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_background_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_background_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_background_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_background_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_foreground_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_foreground_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_foreground_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_foreground_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_monochrome_amoled.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_monochrome_brown.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_monochrome_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_monochrome_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_purple.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/mipmap-xxxhdpi-v4/ic_launcher_red.png +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/values-night-v8/values-night-v8.xml +/core/ui-kmp/build/intermediates/packaged_res/debug/packageDebugResources/values-ru/values-ru.xml +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/META-INF/ui-kmp_debug.kotlin_module +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/movies/core/ui-kmp/generated/resources/Res$drawable.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/movies/core/ui-kmp/generated/resources/Res$font.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/movies/core/ui-kmp/generated/resources/Res$string.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/movies/core/ui-kmp/generated/resources/Res.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/appicon/IconAlias$Amoled.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/appicon/IconAlias$Brown.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/appicon/IconAlias$Companion.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/appicon/IconAlias$Purple.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/appicon/IconAlias$Red.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/appicon/IconAlias.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/appicon/MoviesAliasKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$onShareUrl$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$resultContract$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$onStartSpeechRecognize$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$speechRecognizeContract$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$4$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$4$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/movie/MovieRowKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-4$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-4$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-5$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-6$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-7$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-8$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-9$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContent$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageContentKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$onCheckConnectivityClick$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$settingsPanelContract$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailureAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailurePreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageFailureKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoading$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PageLoadingKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBox$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBox$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatar$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/AccountAvatarKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBox$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$1$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$activityContract$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$permissionContract$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIcon$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconAmoledPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconPreview$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/icons/CatKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/icons/GithubKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/icons/GooglePlayKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/icons/MoviesIcons.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/icons/ThemeLightDarkKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/AccountDbKtxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/ComposableKtxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/ConfigurationKtxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/LazyPagingItemsKtxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$$inlined$debugInspectorInfo$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/ModifierKtxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/ktx/SettingsKtxKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1$invoke$$inlined$onDispose$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlightKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$3.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/Fade.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$fadeAnimationSpec$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$shimmerAnimationSpec$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight$Companion.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderHightlightKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$2.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4$1$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder-cf5BqRc$$inlined$debugInspectorInfo$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/PlaceholderKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/placeholder/Shimmer.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/AccountPreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/AppearancePreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/BooleanPreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/IconAliasPreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/LanguagePreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/MovieListPreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/SuggestionDbPreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/ThemePreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/TitlePreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/provider/VersionPreviewParameterProvider.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/DeviceLandscapePreview.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/DevicePreviews.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/preview/DeviceUserPreviews.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/shortcuts/MoviesShortcutsKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/model/ComposeTheme.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/provider/MoviesRippleTheme.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ColorsKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ShapeKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$2$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$3$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4$1.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$5.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/ThemeKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/theme/TypeKt.class +/core/ui-kmp/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/org/michaelbel/movies/ui/tile/MoviesTileService.class +/core/ui-kmp/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +/core/ui-kmp/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.values +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab.values.s +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/complementary-files.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/constants.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/package-parts.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.values +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.s +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/counters.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.keystream +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.values +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.values.at +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab.values.s +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab_i +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/caches-jvm/lookups/lookups.tab_i.len +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/cacheable/last-build.bin +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/classpath-snapshot/shrunk-classpath-snapshot.bin +/core/ui-kmp/build/kotlin/compileDebugKotlinAndroid/local-state/build-history.bin +/core/ui-kmp/build/outputs/logs/manifest-merger-debug-report.txt +/core/ui-kmp/build/tmp/kotlin-classes/debug/META-INF/ui-kmp_debug.kotlin_module +/core/ui-kmp/build/tmp/kotlin-classes/debug/movies/core/ui-kmp/generated/resources/Res$drawable.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/movies/core/ui-kmp/generated/resources/Res$font.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/movies/core/ui-kmp/generated/resources/Res$string.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/movies/core/ui-kmp/generated/resources/Res.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/appicon/IconAlias$Amoled.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/appicon/IconAlias$Brown.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/appicon/IconAlias$Companion.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/appicon/IconAlias$Purple.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/appicon/IconAlias$Red.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/appicon/IconAlias.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/appicon/MoviesAliasKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIcon$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt$BackIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/BackIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt$CloseIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/CloseIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$BackIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$CloseIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$DownloadIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SearchIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$SettingsIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ComposableSingletons$VoiceIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt$DownloadIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/DownloadIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIcon$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconAmoledPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt$PasswordIconPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/PasswordIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt$SearchIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SearchIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt$SettingsIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/SettingsIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$onShareUrl$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIcon$resultContract$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconAmoledPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt$ShareIconPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/ShareIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$onStartSpeechRecognize$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIcon$speechRecognizeContract$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt$VoiceIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/iconbutton/VoiceIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieColumnKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/ComposableSingletons$MovieRowKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$$inlined$ConstraintLayout$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$1$4$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumn$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnAmoledPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt$MovieColumnPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieColumnKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$$inlined$ConstraintLayout$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$1$4$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRow$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowAmoledPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt$MovieRowPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/movie/MovieRowKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt$lambda-4$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageContentKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageFailureKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-4$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-5$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-6$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-7$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-8$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt$lambda-9$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PageLoadingKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingFailureBoxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/ComposableSingletons$PagingLoadingBoxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContent$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentColumn$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentGrid$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt$PageContentStaggeredGrid$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageContentKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$$inlined$ConstraintLayout$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$1$onCheckConnectivityClick$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailure$settingsPanelContract$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailureAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt$PageFailurePreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageFailureKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoading$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumn$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingColumnPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGrid$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingGridPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGrid$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt$PageLoadingStaggeredGridPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PageLoadingKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBox$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt$PagingFailureBoxPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingFailureBoxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBox$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt$PagingLoadingBoxPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/page/PagingLoadingBoxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatar$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/AccountAvatarKt$AccountAvatarPreview$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/AccountAvatarKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBox$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt$ApiKeyBoxPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ApiKeyBoxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$ApiKeyBoxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt$lambda-3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$NotificationBottomSheetKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt$lambda-2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/ComposableSingletons$SwitchCheckIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$1$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$activityContract$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheet$permissionContract$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt$NotificationBottomSheetPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/NotificationBottomSheetKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIcon$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconAmoledPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt$SwitchCheckIconPreview$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/compose/SwitchCheckIconKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/icons/CatKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/icons/GithubKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/icons/GooglePlayKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/icons/MoviesIcons.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/icons/ThemeLightDarkKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/AccountDbKtxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/ComposableKtxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/ConfigurationKtxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/LazyPagingItemsKtxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$$inlined$debugInspectorInfo$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/ModifierKtxKt$clickableWithoutRipple$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/ModifierKtxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/ktx/SettingsKtxKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1$invoke$$inlined$onDispose$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnLifecycleEvent$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt$OnResume$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/lifecycle/OnLifecycleEventKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlightKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt$placeholder$3.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/material3/PlaceholderKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/Fade.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$fadeAnimationSpec$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults$shimmerAnimationSpec$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderDefaults.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight$Companion.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderHighlight.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderHightlightKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$2.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4$1$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder$4.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderKt$placeholder-cf5BqRc$$inlined$debugInspectorInfo$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/PlaceholderKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/placeholder/Shimmer.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/AccountPreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/AppearancePreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/BooleanPreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/IconAliasPreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/LanguagePreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/MovieListPreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/SuggestionDbPreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/ThemePreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/TitlePreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/provider/VersionPreviewParameterProvider.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/DeviceLandscapePreview.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/DevicePreviews.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/preview/DeviceUserPreviews.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/shortcuts/MoviesShortcutsKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/model/ComposeTheme.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/provider/MoviesRippleTheme.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ColorsKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ShapeKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$2$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$3$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4$1.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$4.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ThemeKt$MoviesTheme$5.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/ThemeKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/theme/TypeKt.class +/core/ui-kmp/build/tmp/kotlin-classes/debug/org/michaelbel/movies/ui/tile/MoviesTileService.class +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_repository_di_HiltWrapper_RepositoryModule.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/di/HiltWrapper_RepositoryModule.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/AccountRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/ImageRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/MovieRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/SearchRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl_Factory.java +/core/repository-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl_Factory.java diff --git a/android-app/build.gradle.kts b/android-app/build.gradle.kts deleted file mode 100644 index a978f5d5b..000000000 --- a/android-app/build.gradle.kts +++ /dev/null @@ -1,228 +0,0 @@ -import com.google.firebase.appdistribution.gradle.AppDistributionExtension -import org.apache.commons.io.output.ByteArrayOutputStream -import org.jetbrains.kotlin.konan.properties.Properties -import java.io.FileInputStream - -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.application) - alias(libs.plugins.kotlin) - alias(libs.plugins.androidx.navigation.safeargs) - alias(libs.plugins.palantir.git) - id("movies-android-hilt") -} - -val gitCommitsCount: Int by lazy { - val stdout = ByteArrayOutputStream() - rootProject.exec { - commandLine("git", "rev-list", "--count", "HEAD") - standardOutput = stdout - } - stdout.toString().trim().toInt() -} - -val currentTime: Long by lazy { - System.currentTimeMillis() -} - -val gitVersion: groovy.lang.Closure by extra -val versionDetails: groovy.lang.Closure by extra -val versionLastTag: String = versionDetails().lastTag - -tasks.register("prepareReleaseNotes") { - doLast { - exec { - workingDir(rootDir) - executable("./config/scripts/gitlog.sh") - } - } -} - -afterEvaluate { - tasks.findByName("assembleDebug")?.finalizedBy("prepareReleaseNotes") - tasks.findByName("assembleRelease")?.finalizedBy("prepareReleaseNotes") -} - -android { - namespace = "org.michaelbel.movies.app" - flavorDimensions += "version" - - defaultConfig { - applicationId = "org.michaelbel.moviemade" - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - targetSdk = libs.versions.target.sdk.get().toInt() - versionCode = gitCommitsCount - versionName = versionLastTag - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables.useSupportLibrary = true - resourceConfigurations.addAll(listOf("en", "ru")) - - buildConfigField("String", "VERSION_DATE", "\"$currentTime\"") - - setProperty("archivesBaseName", "Movies-v$versionName($versionCode)") - } - - signingConfigs { - val keystoreProperties = Properties() - val keystorePropertiesFile: File = rootProject.file("config/keystore.properties") - if (keystorePropertiesFile.exists()) { - keystoreProperties.load(FileInputStream(keystorePropertiesFile)) - } else { - keystoreProperties["keyAlias"] = System.getenv("KEYSTORE_KEY_ALIAS").orEmpty() - keystoreProperties["keyPassword"] = System.getenv("KEYSTORE_KEY_PASSWORD").orEmpty() - keystoreProperties["storePassword"] = System.getenv("KEYSTORE_STORE_PASSWORD").orEmpty() - keystoreProperties["storeFile"] = System.getenv("KEYSTORE_FILE").orEmpty() - } - - create("release") { - keyAlias = keystoreProperties["keyAlias"] as String - keyPassword = keystoreProperties["keyPassword"] as String - storeFile = file(keystoreProperties["storeFile"] as String) - storePassword = keystoreProperties["storePassword"] as String - } - } - - buildTypes { - release { - isMinifyEnabled = true - isShrinkResources = true - signingConfig = signingConfigs.getByName("release") - applicationIdSuffix = MoviesBuildType.RELEASE.applicationIdSuffix - manifestPlaceholders += mapOf("appName" to "@string/app_name") - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro", - "retrofit2.pro", - "okhttp3.pro", - "coroutines.pro" - ) - /*firebaseAppDistribution { - appId = "1:770317857182:android:876190afbc53df31" - artifactType = "APK" - testers = "michaelbel24865@gmail.com" - groups = "qa" - //releaseNotesFile="$rootProject.rootDir/releaseNotes.txt" - //serviceCredentialsFile = "$rootDir/config/firebase-app-distribution.json" - }*/ - } - debug { - isDebuggable = true - isMinifyEnabled = false - isShrinkResources = false - applicationIdSuffix = MoviesBuildType.DEBUG.applicationIdSuffix - manifestPlaceholders += mapOf("appName" to "@string/app_name_dev") - isDefault = true - } - create("benchmark") { - initWith(getByName("release")) - matchingFallbacks.add("release") - signingConfig = signingConfigs.getByName("debug") - proguardFiles("benchmark-rules.pro") - isDebuggable = false - isMinifyEnabled = true - applicationIdSuffix = MoviesBuildType.BENCHMARK.applicationIdSuffix - } - } - - buildFeatures { - buildConfig = true - compose = true - } - - productFlavors { - create("gms") { - dimension = "version" - applicationId = "org.michaelbel.moviemade" - isDefault = true - } - create("hms") { - dimension = "version" - applicationId = "org.michaelbel.movies" - } - create("foss") { - dimension = "version" - applicationId = "org.michaelbel.movies" - } - } - - dynamicFeatures += setOf(":instant") - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -val gmsImplementation: Configuration by configurations -val hmsImplementation: Configuration by configurations -val fossImplementation: Configuration by configurations -dependencies { - gmsImplementation(project(":core:platform-services:inject")) - hmsImplementation(project(":core:platform-services:inject")) - fossImplementation(project(":core:platform-services:inject")) - - implementation(project(":core:analytics")) - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:navigation")) - implementation(project(":core:platform-services:interactor")) - implementation(project(":core:ui")) - implementation(project(":core:work")) - implementation(project(":feature:auth")) - implementation(project(":feature:account")) - implementation(project(":feature:details")) - implementation(project(":feature:feed")) - implementation(project(":feature:gallery")) - implementation(project(":feature:search")) - implementation(project(":feature:settings")) - - implementation(libs.kotlin.reflect) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - - lintChecks(libs.lint.checks) -} - -val hasGmsDebug: Boolean = gradle.startParameter.taskNames.any { it.contains("GmsDebug", ignoreCase = true) } -val hasGmsRelease: Boolean = gradle.startParameter.taskNames.any { it.contains("GmsRelease", ignoreCase = true) } -val hasGmsBenchmark: Boolean = gradle.startParameter.taskNames.any { it.contains("GmsBenchmark", ignoreCase = true) } - -if (hasGmsDebug || hasGmsRelease || hasGmsBenchmark) { - apply(plugin = libs.plugins.google.services.get().pluginId) - apply(plugin = libs.plugins.firebase.crashlytics.get().pluginId) - apply(plugin = libs.plugins.firebase.appdistribution.get().pluginId) -} - -if (hasGmsRelease) { - configure { - appId = "1:770317857182:android:876190afbc53df31" - artifactType = "APK" - testers = "michaelbel24865@gmail.com" - groups = "qa" - } -} - -val hasHmsDebug: Boolean = gradle.startParameter.taskNames.any { it.contains("HmsDebug", ignoreCase = true) } -val hasHmsRelease: Boolean = gradle.startParameter.taskNames.any { it.contains("HmsRelease", ignoreCase = true) } -val hasHmsBenchmark: Boolean = gradle.startParameter.taskNames.any { it.contains("HmsBenchmark", ignoreCase = true) } - -if (hasHmsDebug || hasHmsRelease || hasHmsBenchmark) { - //apply(plugin = libs.plugins.huawei.services.get().pluginId) -} \ No newline at end of file diff --git a/android-app/src/main/AndroidManifest.xml b/android-app/src/main/AndroidManifest.xml deleted file mode 100644 index 61ec0cb29..000000000 --- a/android-app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android-app/src/main/kotlin/org/michaelbel/movies/App.kt b/android-app/src/main/kotlin/org/michaelbel/movies/App.kt deleted file mode 100644 index 5bd055dc0..000000000 --- a/android-app/src/main/kotlin/org/michaelbel/movies/App.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.michaelbel.movies - -import android.app.Application -import androidx.hilt.work.HiltWorkerFactory -import androidx.work.Configuration -import dagger.hilt.android.HiltAndroidApp -import javax.inject.Inject -import org.michaelbel.movies.common.BuildConfig -import org.michaelbel.movies.common.crashlytics.CrashlyticsTree -import org.michaelbel.movies.platform.app.AppService -import org.michaelbel.movies.platform.crashlytics.CrashlyticsService -import org.michaelbel.movies.ui.appicon.installLauncherIcon -import timber.log.Timber - -@HiltAndroidApp -internal class App: Application(), Configuration.Provider { - - @Inject lateinit var workerFactory: HiltWorkerFactory - @Inject lateinit var appService: AppService - @Inject lateinit var crashlyticsService: CrashlyticsService - - override val workManagerConfiguration: Configuration - get() = Configuration.Builder().setWorkerFactory(workerFactory).build() - - override fun onCreate() { - super.onCreate() - installLauncherIcon() - appService.installApp() - Timber.plant(if (BuildConfig.DEBUG) Timber.DebugTree() else CrashlyticsTree(crashlyticsService)) - } -} \ No newline at end of file diff --git a/android-app/src/main/kotlin/org/michaelbel/movies/MainActivity.kt b/android-app/src/main/kotlin/org/michaelbel/movies/MainActivity.kt deleted file mode 100644 index c1adfa75b..000000000 --- a/android-app/src/main/kotlin/org/michaelbel/movies/MainActivity.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.michaelbel.movies - -import android.os.Bundle -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AppCompatActivity -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import dagger.hilt.android.AndroidEntryPoint -import org.michaelbel.movies.ui.shortcuts.installShortcuts - -/** - * Per-App Language depends on AppCompatActivity (not ComponentActivity). - */ -@AndroidEntryPoint -internal class MainActivity: AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - installSplashScreen() - super.onCreate(savedInstanceState) - installShortcuts() - setContent { - MainActivityContent { statusBarStyle, navigationBarStyle -> - enableEdgeToEdge(statusBarStyle, navigationBarStyle) - } - } - } -} \ No newline at end of file diff --git a/android-app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt b/android-app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt deleted file mode 100644 index 56418b046..000000000 --- a/android-app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.michaelbel.movies - -import android.os.Bundle -import androidx.navigation.NavDestination -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.workDataOf -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import org.michaelbel.movies.analytics.MoviesAnalytics -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.platform.messaging.MessagingService -import org.michaelbel.movies.work.AccountUpdateWorker -import org.michaelbel.movies.work.MoviesDatabaseWorker - -@HiltViewModel -internal class MainViewModel @Inject constructor( - private val interactor: Interactor, - private val analytics: MoviesAnalytics, - private val messagingService: MessagingService, - private val workManager: WorkManager -): BaseViewModel() { - - val currentTheme: StateFlow = interactor.currentTheme - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = AppTheme.FollowSystem - ) - - val dynamicColors: StateFlow = interactor.dynamicColors - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = false - ) - - init { - fetchRemoteConfig() - fetchFirebaseMessagingToken() - prepopulateDatabase() - updateAccountDetails() - } - - fun analyticsTrackDestination(destination: NavDestination, arguments: Bundle?) { - analytics.trackDestination(destination.route, arguments) - } - - private fun fetchRemoteConfig() = launch { - interactor.fetchRemoteConfig() - } - - private fun fetchFirebaseMessagingToken() { - /*messagingService.setTokenListener(object: TokenListener { - override fun onNewToken(token: String) { - printlnDebug("firebase messaging token: $token") - } - })*/ - } - - private fun prepopulateDatabase() { - val request = OneTimeWorkRequestBuilder() - .setInputData(workDataOf(MoviesDatabaseWorker.KEY_FILENAME to MOVIES_DATA_FILENAME)) - .build() - workManager.enqueue(request) - } - - private fun updateAccountDetails() { - val request = OneTimeWorkRequestBuilder() - .build() - workManager.enqueue(request) - } - - companion object { - const val MOVIES_DATA_FILENAME = "movies.json" - } -} \ No newline at end of file diff --git a/android-app/.gitignore b/androidApp/.gitignore similarity index 100% rename from android-app/.gitignore rename to androidApp/.gitignore diff --git a/android-app/benchmark-rules.pro b/androidApp/benchmark-rules.pro similarity index 100% rename from android-app/benchmark-rules.pro rename to androidApp/benchmark-rules.pro diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts new file mode 100644 index 000000000..e8a93be28 --- /dev/null +++ b/androidApp/build.gradle.kts @@ -0,0 +1,225 @@ +import com.google.firebase.appdistribution.gradle.AppDistributionExtension +import org.apache.commons.io.output.ByteArrayOutputStream +import org.jetbrains.kotlin.konan.properties.Properties +import java.io.FileInputStream + +@Suppress("dsl_scope_violation") + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.androidx.navigation.safeargs) + alias(libs.plugins.palantir.git) +} + +val gitCommitsCount by lazy { + val stdout = ByteArrayOutputStream() + rootProject.exec { + commandLine("git", "rev-list", "--count", "HEAD") + standardOutput = stdout + } + stdout.toString().trim().toInt() +} + +val currentTime by lazy { + System.currentTimeMillis() +} + +val gitVersion: groovy.lang.Closure by extra +val versionDetails: groovy.lang.Closure by extra +val versionLastTag: String = versionDetails().lastTag + +tasks.register("prepareReleaseNotes") { + doLast { + exec { + workingDir(rootDir) + executable("./config/scripts/gitlog.sh") + } + } +} + +afterEvaluate { + tasks.findByName("assembleGmsDebug")?.finalizedBy("prepareReleaseNotes") + tasks.findByName("assembleGmsRelease")?.finalizedBy("prepareReleaseNotes") + tasks.findByName("assembleHmsDebug")?.finalizedBy("prepareReleaseNotes") + tasks.findByName("assembleHmsRelease")?.finalizedBy("prepareReleaseNotes") + tasks.findByName("assembleFossDebug")?.finalizedBy("prepareReleaseNotes") + tasks.findByName("assembleFossRelease")?.finalizedBy("prepareReleaseNotes") +} + +android { + namespace = "org.michaelbel.movies.app" + flavorDimensions += "version" + + defaultConfig { + applicationId = "org.michaelbel.moviemade" + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + targetSdk = libs.versions.target.sdk.get().toInt() + versionCode = gitCommitsCount + versionName = versionLastTag + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + resourceConfigurations.addAll(listOf("en", "ru")) + + buildConfigField("String", "VERSION_DATE", "\"$currentTime\"") + + setProperty("archivesBaseName", "Movies-v$versionName($versionCode)") + } + + signingConfigs { + val keystoreProperties = Properties() + val keystorePropertiesFile: File = rootProject.file("config/keystore.properties") + if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) + } else { + keystoreProperties["keyAlias"] = System.getenv("KEYSTORE_KEY_ALIAS").orEmpty() + keystoreProperties["keyPassword"] = System.getenv("KEYSTORE_KEY_PASSWORD").orEmpty() + keystoreProperties["storePassword"] = System.getenv("KEYSTORE_STORE_PASSWORD").orEmpty() + keystoreProperties["storeFile"] = System.getenv("KEYSTORE_FILE").orEmpty() + } + /*create("release") { // todo Uncomment to create a signed release + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = file(keystoreProperties["storeFile"] as String) + storePassword = keystoreProperties["storePassword"] as String + }*/ + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + /*signingConfig = signingConfigs.getByName("release")*/ // todo Uncomment to create a signed release + applicationIdSuffix = MoviesBuildType.RELEASE.applicationIdSuffix + manifestPlaceholders += mapOf("appName" to "@string/app_name") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + "retrofit2.pro", + "okhttp3.pro", + "coroutines.pro" + ) + } + debug { + isDebuggable = true + isMinifyEnabled = false + isShrinkResources = false + applicationIdSuffix = MoviesBuildType.DEBUG.applicationIdSuffix + manifestPlaceholders += mapOf("appName" to "@string/app_name_dev") + isDefault = true + //vcsInfo { include = true } // Version control system integration in App Quality Insights + } + create("benchmark") { + initWith(getByName("release")) + matchingFallbacks.add("release") + signingConfig = signingConfigs.getByName("debug") + proguardFiles("benchmark-rules.pro") + isDebuggable = false + isMinifyEnabled = true + applicationIdSuffix = MoviesBuildType.BENCHMARK.applicationIdSuffix + } + } + + buildFeatures { + buildConfig = true + compose = true + } + + productFlavors { + create("gms") { + dimension = "version" + applicationId = "org.michaelbel.moviemade" + isDefault = true + } + create("hms") { + dimension = "version" + applicationId = "org.michaelbel.movies" + } + create("foss") { + dimension = "version" + applicationId = "org.michaelbel.movies" + } + } + + /*dynamicFeatures += setOf(":instant")*/ + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(libs.versions.jdk.get().toInt()) + targetCompatibility = JavaVersion.toVersion(libs.versions.jdk.get().toInt()) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} + +val gmsImplementation by configurations +val hmsImplementation by configurations +val fossImplementation by configurations +dependencies { + gmsImplementation(project(":core:platform-services:inject-kmp")) + hmsImplementation(project(":core:platform-services:inject-kmp")) + fossImplementation(project(":core:platform-services:inject-kmp")) + implementation(project(":core:analytics-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:debug-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:navigation-kmp")) + implementation(project(":core:platform-services:interactor-kmp")) + implementation(project(":core:ui-kmp")) + implementation(project(":core:widget-kmp")) + implementation(project(":core:work-kmp")) + implementation(project(":feature:account-kmp")) + implementation(project(":feature:auth-kmp")) + implementation(project(":feature:details-kmp")) + implementation(project(":feature:feed-kmp")) + implementation(project(":feature:gallery-kmp")) + implementation(project(":feature:search-kmp")) + implementation(project(":feature:settings-kmp")) + implementation(libs.kotlin.reflect) + implementation(libs.bundles.koin.android) + testImplementation(libs.junit) + androidTestImplementation(libs.bundles.androidx.test.espresso) + androidTestImplementation(libs.androidx.test.ext.junit.ktx) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + androidTestImplementation(libs.androidx.benchmark.junit) + debugImplementation(libs.leakcanary) + lintChecks(libs.lint.checks) +} + +val hasGmsDebug = gradle.startParameter.taskNames.any { it.contains("GmsDebug", ignoreCase = true) } +val hasGmsRelease = gradle.startParameter.taskNames.any { it.contains("GmsRelease", ignoreCase = true) } +val hasGmsBenchmark = gradle.startParameter.taskNames.any { it.contains("GmsBenchmark", ignoreCase = true) } + +if (hasGmsDebug || hasGmsRelease || hasGmsBenchmark) { + apply(plugin = libs.plugins.google.services.get().pluginId) + apply(plugin = libs.plugins.google.firebase.crashlytics.get().pluginId) + apply(plugin = libs.plugins.google.firebase.appdistribution.get().pluginId) +} + +if (hasGmsRelease) { + configure { + appId = "1:770317857182:android:876190afbc53df31" + artifactType = "APK" + testers = "michaelbel24865@gmail.com" + groups = "qa" + } +} + +val hasHmsDebug = gradle.startParameter.taskNames.any { it.contains("HmsDebug", ignoreCase = true) } +val hasHmsRelease = gradle.startParameter.taskNames.any { it.contains("HmsRelease", ignoreCase = true) } +val hasHmsBenchmark = gradle.startParameter.taskNames.any { it.contains("HmsBenchmark", ignoreCase = true) } + +if (hasHmsDebug || hasHmsRelease || hasHmsBenchmark) { + //apply(plugin = libs.plugins.huawei.services.get().pluginId) +} \ No newline at end of file diff --git a/android-app/coroutines.pro b/androidApp/coroutines.pro similarity index 100% rename from android-app/coroutines.pro rename to androidApp/coroutines.pro diff --git a/android-app/okhttp3.pro b/androidApp/okhttp3.pro similarity index 100% rename from android-app/okhttp3.pro rename to androidApp/okhttp3.pro diff --git a/android-app/proguard-rules.pro b/androidApp/proguard-rules.pro similarity index 100% rename from android-app/proguard-rules.pro rename to androidApp/proguard-rules.pro diff --git a/android-app/retrofit2.pro b/androidApp/retrofit2.pro similarity index 100% rename from android-app/retrofit2.pro rename to androidApp/retrofit2.pro diff --git a/android-app/src/debug/google-services.json b/androidApp/src/debug/google-services.json similarity index 100% rename from android-app/src/debug/google-services.json rename to androidApp/src/debug/google-services.json diff --git a/android-app/src/gmsBenchmark/google-services.json b/androidApp/src/gmsBenchmark/google-services.json similarity index 100% rename from android-app/src/gmsBenchmark/google-services.json rename to androidApp/src/gmsBenchmark/google-services.json diff --git a/android-app/src/gmsDebug/google-services.json b/androidApp/src/gmsDebug/google-services.json similarity index 100% rename from android-app/src/gmsDebug/google-services.json rename to androidApp/src/gmsDebug/google-services.json diff --git a/android-app/src/gmsRelease/google-services.json b/androidApp/src/gmsRelease/google-services.json similarity index 100% rename from android-app/src/gmsRelease/google-services.json rename to androidApp/src/gmsRelease/google-services.json diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml new file mode 100644 index 000000000..4942ccacf --- /dev/null +++ b/androidApp/src/main/AndroidManifest.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidApp/src/main/kotlin/org/michaelbel/movies/App.kt b/androidApp/src/main/kotlin/org/michaelbel/movies/App.kt new file mode 100644 index 000000000..21f28c914 --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/movies/App.kt @@ -0,0 +1,40 @@ +package org.michaelbel.movies + +import android.app.Application +import androidx.work.Configuration +import org.koin.android.ext.android.inject +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.androidx.workmanager.factory.KoinWorkerFactory +import org.koin.androidx.workmanager.koin.workManagerFactory +import org.koin.core.context.startKoin +import org.michaelbel.movies.common.crashlytics.CrashlyticsTree +import org.michaelbel.movies.common_kmp.BuildConfig +import org.michaelbel.movies.di.appKoinModule +import org.michaelbel.movies.platform.app.AppService +import org.michaelbel.movies.platform.crashlytics.CrashlyticsService +import org.michaelbel.movies.ui.appicon.installLauncherIcon +import timber.log.Timber + +internal class App: Application(), Configuration.Provider { + + private val workerFactory: KoinWorkerFactory by inject() + private val appService: AppService by inject() + private val crashlyticsService: CrashlyticsService by inject() + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder().setWorkerFactory(workerFactory).build() + + override fun onCreate() { + super.onCreate() + installLauncherIcon() + startKoin { + androidLogger() + androidContext(this@App) + workManagerFactory() + modules(appKoinModule) + } + appService.installApp() + Timber.plant(if (BuildConfig.DEBUG) Timber.DebugTree() else CrashlyticsTree(crashlyticsService)) + } +} \ No newline at end of file diff --git a/androidApp/src/main/kotlin/org/michaelbel/movies/MainActivity.kt b/androidApp/src/main/kotlin/org/michaelbel/movies/MainActivity.kt new file mode 100644 index 000000000..5473d9446 --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/movies/MainActivity.kt @@ -0,0 +1,33 @@ +package org.michaelbel.movies + +import android.os.Bundle +import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.fragment.app.FragmentActivity +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.michaelbel.movies.common.ktx.launchAndCollectIn +import org.michaelbel.movies.ui.ktx.resolveNotificationPreferencesIntent +import org.michaelbel.movies.ui.shortcuts.installShortcuts + +internal class MainActivity: FragmentActivity() { + + private val viewModel: MainViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen().apply { setKeepOnScreenCondition { viewModel.splashLoading.value } } + super.onCreate(savedInstanceState) + installShortcuts() + setContent { + MainActivityContent { statusBarStyle, navigationBarStyle -> + enableEdgeToEdge(statusBarStyle as SystemBarStyle, navigationBarStyle as SystemBarStyle) + } + } + resolveNotificationPreferencesIntent() + viewModel.run { + authenticateFlow.launchAndCollectIn(this@MainActivity) { authenticate(this@MainActivity) } + cancelFlow.launchAndCollectIn(this@MainActivity) { finish() } + } + } +} \ No newline at end of file diff --git a/android-app/src/main/kotlin/org/michaelbel/movies/MainActivityContent.kt b/androidApp/src/main/kotlin/org/michaelbel/movies/MainActivityContent.kt similarity index 82% rename from android-app/src/main/kotlin/org/michaelbel/movies/MainActivityContent.kt rename to androidApp/src/main/kotlin/org/michaelbel/movies/MainActivityContent.kt index 318017944..df04aad3c 100644 --- a/android-app/src/main/kotlin/org/michaelbel/movies/MainActivityContent.kt +++ b/androidApp/src/main/kotlin/org/michaelbel/movies/MainActivityContent.kt @@ -1,15 +1,14 @@ package org.michaelbel.movies -import androidx.activity.SystemBarStyle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController -import org.michaelbel.movies.auth.accountGraph +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.account.accountGraph +import org.michaelbel.movies.account.navigateToAccount import org.michaelbel.movies.auth.authGraph -import org.michaelbel.movies.auth.navigateToAccount import org.michaelbel.movies.auth.navigateToAuth import org.michaelbel.movies.details.detailsGraph import org.michaelbel.movies.details.navigateToDetails @@ -26,18 +25,16 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable internal fun MainActivityContent( - viewModel: MainViewModel = hiltViewModel(), - enableEdgeToEdge: (SystemBarStyle, SystemBarStyle) -> Unit, + viewModel: MainViewModel = koinViewModel(), + enableEdgeToEdge: (Any, Any) -> Unit ) { - val currentTheme by viewModel.currentTheme.collectAsStateWithLifecycle() - val dynamicColors by viewModel.dynamicColors.collectAsStateWithLifecycle() + val themeData by viewModel.themeData.collectAsStateWithLifecycle() val navHostController = rememberNavController().apply { addOnDestinationChangedListener(viewModel::analyticsTrackDestination) } MoviesTheme( - theme = currentTheme, - dynamicColors = dynamicColors, + themeData = themeData, enableEdgeToEdge = enableEdgeToEdge ) { NavHost( diff --git a/androidApp/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt b/androidApp/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt new file mode 100644 index 000000000..c73ee660d --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt @@ -0,0 +1,129 @@ +package org.michaelbel.movies + +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import androidx.navigation.NavDestination +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import org.michaelbel.movies.analytics.MoviesAnalytics +import org.michaelbel.movies.app.BuildConfig +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.biometric.BiometricController +import org.michaelbel.movies.common.biometric.BiometricListener +import org.michaelbel.movies.common.viewmodel.BaseViewModel +import org.michaelbel.movies.debug.notification.DebugNotificationClient +import org.michaelbel.movies.interactor.Interactor +import org.michaelbel.movies.platform.config.ConfigService +import org.michaelbel.movies.platform.messaging.MessagingService +import org.michaelbel.movies.work.AccountUpdateWorker +import org.michaelbel.movies.work.MoviesDatabaseWorker + +internal class MainViewModel( + private val interactor: Interactor, + private val biometricController: BiometricController, + private val analytics: MoviesAnalytics, + private val messagingService: MessagingService, + private val workManager: WorkManager, + private val debugNotificationClient: DebugNotificationClient, + private val configService: ConfigService +): BaseViewModel() { + + private val _authenticateFlow = Channel(Channel.BUFFERED) + val authenticateFlow: Flow = _authenticateFlow.receiveAsFlow() + + private val _cancelFlow = Channel(Channel.BUFFERED) + val cancelFlow: Flow = _cancelFlow.receiveAsFlow() + + private val _splashLoading = MutableStateFlow(true) + val splashLoading: StateFlow = _splashLoading.asStateFlow() + + val themeData: StateFlow = interactor.themeData + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = ThemeData.Default + ) + + init { + fetchBiometric() + fetchRemoteConfig() + fetchFirebaseMessagingToken() + prepopulateDatabase() + updateAccountDetails() + showDebugNotification() + } + + fun analyticsTrackDestination(destination: NavDestination, arguments: Bundle?) { + val hashMap = hashMapOf() + arguments?.keySet()?.forEach { key -> + hashMap[key] = arguments.get(key).toString() + } + analytics.trackDestination(destination.route, hashMap) + } + + fun authenticate(activity: FragmentActivity) { + val biometricListener = object: BiometricListener { + override fun onSuccess() { + _splashLoading.value = false + } + + override fun onCancel() { + launch { _cancelFlow.send(Unit) } + } + } + biometricController.authenticate(activity, biometricListener) + } + + private fun fetchBiometric() = launch { + val isBiometricEnabled = interactor.isBiometricEnabledAsync() + _splashLoading.value = isBiometricEnabled + if (isBiometricEnabled) { + _authenticateFlow.send(Unit) + } + } + + private fun fetchRemoteConfig() = launch { + configService.fetchAndActivate() + } + + private fun fetchFirebaseMessagingToken() { + /*messagingService.setTokenListener(object: TokenListener { + override fun onNewToken(token: String) { + printlnDebug("firebase messaging token: $token") + } + })*/ + } + + private fun prepopulateDatabase() { + val request = OneTimeWorkRequestBuilder() + .setInputData(workDataOf(MoviesDatabaseWorker.KEY_FILENAME to MOVIES_DATA_FILENAME)) + .build() + workManager.enqueue(request) + } + + private fun updateAccountDetails() { + val request = OneTimeWorkRequestBuilder() + .build() + workManager.enqueue(request) + } + + private fun showDebugNotification() { + if (BuildConfig.DEBUG) { + debugNotificationClient.showDebugNotification() + } + } + + companion object { + const val MOVIES_DATA_FILENAME = "movies.json" + } +} \ No newline at end of file diff --git a/androidApp/src/main/kotlin/org/michaelbel/movies/di/AppKoinModule.kt b/androidApp/src/main/kotlin/org/michaelbel/movies/di/AppKoinModule.kt new file mode 100644 index 000000000..927fd4439 --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/movies/di/AppKoinModule.kt @@ -0,0 +1,29 @@ +package org.michaelbel.movies.di + +import org.koin.dsl.module +import org.michaelbel.movies.account.di.accountKoinModule +import org.michaelbel.movies.auth.di.authKoinModule +import org.michaelbel.movies.debug.di.debugKoinModule +import org.michaelbel.movies.details.di.detailsKoinModule +import org.michaelbel.movies.feed.di.feedKoinModule +import org.michaelbel.movies.gallery.di.galleryKoinModule +import org.michaelbel.movies.platform.inject.flavorServiceKtorModule +import org.michaelbel.movies.search.di.searchKoinModule +import org.michaelbel.movies.settings.di.settingsKoinModule +import org.michaelbel.movies.widget.di.glanceKoinModule + +val appKoinModule = module { + includes( + flavorServiceKtorModule, + mainKoinModule, + accountKoinModule, + authKoinModule, + detailsKoinModule, + feedKoinModule, + galleryKoinModule, + searchKoinModule, + settingsKoinModule, + debugKoinModule, + glanceKoinModule + ) +} \ No newline at end of file diff --git a/androidApp/src/main/kotlin/org/michaelbel/movies/di/MainKoinModule.kt b/androidApp/src/main/kotlin/org/michaelbel/movies/di/MainKoinModule.kt new file mode 100644 index 000000000..8c16fbcf9 --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/movies/di/MainKoinModule.kt @@ -0,0 +1,23 @@ +package org.michaelbel.movies.di + +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.dsl.module +import org.michaelbel.movies.MainViewModel +import org.michaelbel.movies.analytics.di.moviesAnalyticsKoinModule +import org.michaelbel.movies.common.biometric.di.biometricKoinModule +import org.michaelbel.movies.debug.di.debugNotificationClientKoinModule +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.platform.inject.flavorServiceKtorModule +import org.michaelbel.movies.work.di.workKoinModule + +val mainKoinModule = module { + includes( + interactorKoinModule, + biometricKoinModule, + moviesAnalyticsKoinModule, + flavorServiceKtorModule, + workKoinModule, + debugNotificationClientKoinModule + ) + viewModelOf(::MainViewModel) +} \ No newline at end of file diff --git a/androidApp/src/main/kotlin/org/michaelbel/sandbox/Incrementer.java b/androidApp/src/main/kotlin/org/michaelbel/sandbox/Incrementer.java new file mode 100644 index 000000000..51d7999ba --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/sandbox/Incrementer.java @@ -0,0 +1,13 @@ +package org.michaelbel.sandbox; + +public class Incrementer { + int count = 0; + + public /*synchronized*/ void inc() { + if (count < 10) { + count++; + System.out.println("count = " + count); + inc(); + } + } +} \ No newline at end of file diff --git a/androidApp/src/main/kotlin/org/michaelbel/sandbox/JavaSandbox.java b/androidApp/src/main/kotlin/org/michaelbel/sandbox/JavaSandbox.java new file mode 100644 index 000000000..ec1533288 --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/sandbox/JavaSandbox.java @@ -0,0 +1,8 @@ +package org.michaelbel.sandbox; + +public class JavaSandbox { + + public static void main(String[] args) { + new Incrementer().inc(); + } +} \ No newline at end of file diff --git a/androidApp/src/main/kotlin/org/michaelbel/sandbox/KotlinSandbox.kt b/androidApp/src/main/kotlin/org/michaelbel/sandbox/KotlinSandbox.kt new file mode 100644 index 000000000..dfc821145 --- /dev/null +++ b/androidApp/src/main/kotlin/org/michaelbel/sandbox/KotlinSandbox.kt @@ -0,0 +1,20 @@ +package org.michaelbel.sandbox + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.consumeEach +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.runBlocking + +fun main() = runBlocking { + fun CoroutineScope.getUsers(): ReceiveChannel = produce { + val users = listOf("Tom", "Bob", "Sam") + for (user in users) { + send(user) + } + } + + getUsers().consumeEach { + it + } +} \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_splash_screen_animated_icon.xml b/androidApp/src/main/res/drawable/ic_splash_screen_animated_icon.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_splash_screen_animated_icon.xml rename to androidApp/src/main/res/drawable/ic_splash_screen_animated_icon.xml diff --git a/core/ui/src/main/res/drawable/ic_splash_screen_branding_image.xml b/androidApp/src/main/res/drawable/ic_splash_screen_branding_image.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_splash_screen_branding_image.xml rename to androidApp/src/main/res/drawable/ic_splash_screen_branding_image.xml diff --git a/core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_amoled.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_amoled.xml similarity index 100% rename from core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_amoled.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_amoled.xml diff --git a/core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml similarity index 100% rename from core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml diff --git a/core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml similarity index 100% rename from core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml diff --git a/core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml similarity index 100% rename from core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml rename to androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_amoled.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_amoled.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_amoled.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_amoled.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_amoled.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_amoled.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_brown.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_brown.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_brown.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_purple.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_purple.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_purple.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_red.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_background_red.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_background_red.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_brown.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_brown.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_brown.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_amoled.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_amoled.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_amoled.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_brown.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_brown.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_brown.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_purple.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_purple.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_purple.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_red.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground_red.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_foreground_red.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_amoled.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_amoled.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_amoled.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_brown.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_brown.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_brown.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_purple.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_purple.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_purple.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_red.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_monochrome_red.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_monochrome_red.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_purple.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_purple.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_purple.png diff --git a/core/ui/src/main/res/mipmap-hdpi/ic_launcher_red.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-hdpi/ic_launcher_red.png rename to androidApp/src/main/res/mipmap-hdpi/ic_launcher_red.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_amoled.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_amoled.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_amoled.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_amoled.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_amoled.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_amoled.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_brown.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_brown.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_brown.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_purple.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_purple.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_purple.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_red.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_background_red.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_background_red.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_brown.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_brown.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_brown.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_amoled.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_amoled.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_amoled.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_brown.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_brown.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_brown.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_purple.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_purple.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_purple.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_red.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground_red.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_foreground_red.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_amoled.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_amoled.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_amoled.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_brown.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_brown.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_brown.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_purple.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_purple.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_purple.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_red.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_monochrome_red.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_monochrome_red.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_purple.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_purple.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_purple.png diff --git a/core/ui/src/main/res/mipmap-mdpi/ic_launcher_red.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-mdpi/ic_launcher_red.png rename to androidApp/src/main/res/mipmap-mdpi/ic_launcher_red.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_amoled.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_amoled.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_amoled.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_amoled.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_amoled.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_amoled.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_brown.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_brown.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_brown.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_purple.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_purple.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_purple.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_red.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_background_red.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_background_red.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_brown.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_brown.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_brown.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_amoled.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_amoled.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_amoled.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_brown.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_brown.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_brown.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_purple.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_purple.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_purple.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_red.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground_red.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_foreground_red.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_amoled.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_amoled.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_amoled.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_brown.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_brown.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_brown.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_purple.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_purple.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_purple.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_red.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_red.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_monochrome_red.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_purple.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_purple.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_purple.png diff --git a/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_red.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xhdpi/ic_launcher_red.png rename to androidApp/src/main/res/mipmap-xhdpi/ic_launcher_red.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_amoled.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_amoled.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_amoled.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_amoled.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_brown.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_brown.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_brown.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_purple.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_purple.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_purple.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_red.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_background_red.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_background_red.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_amoled.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_amoled.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_brown.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_brown.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_brown.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_purple.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_purple.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_purple.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_red.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_red.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground_red.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_amoled.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_amoled.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_brown.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_brown.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_brown.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_purple.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_purple.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_purple.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_red.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_red.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome_red.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png diff --git a/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_red.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_red.png rename to androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_red.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_amoled.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_amoled.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_amoled.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_amoled.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_brown.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_brown.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_brown.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_purple.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_purple.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_purple.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_red.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_background_red.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_background_red.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_amoled.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_amoled.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_brown.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_brown.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_brown.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_purple.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_purple.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_purple.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_red.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_red.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground_red.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_amoled.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_amoled.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_amoled.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_amoled.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_brown.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_brown.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_brown.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_brown.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_purple.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_purple.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_purple.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_red.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_red.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome_red.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png diff --git a/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png similarity index 100% rename from core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png rename to androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png diff --git a/core/ui/src/main/res/values-night-v23/splash_themes.xml b/androidApp/src/main/res/values-night-v23/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values-night-v23/splash_themes.xml rename to androidApp/src/main/res/values-night-v23/splash_themes.xml diff --git a/core/ui/src/main/res/values-night-v27/splash_themes.xml b/androidApp/src/main/res/values-night-v27/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values-night-v27/splash_themes.xml rename to androidApp/src/main/res/values-night-v27/splash_themes.xml diff --git a/core/ui/src/main/res/values-night-v31/splash_themes.xml b/androidApp/src/main/res/values-night-v31/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values-night-v31/splash_themes.xml rename to androidApp/src/main/res/values-night-v31/splash_themes.xml diff --git a/core/ui/src/main/res/values-night/splash_themes.xml b/androidApp/src/main/res/values-night/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values-night/splash_themes.xml rename to androidApp/src/main/res/values-night/splash_themes.xml diff --git a/core/ui/src/main/res/values-v23/splash_themes.xml b/androidApp/src/main/res/values-v23/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values-v23/splash_themes.xml rename to androidApp/src/main/res/values-v23/splash_themes.xml diff --git a/core/ui/src/main/res/values-v27/splash_themes.xml b/androidApp/src/main/res/values-v27/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values-v27/splash_themes.xml rename to androidApp/src/main/res/values-v27/splash_themes.xml diff --git a/core/ui/src/main/res/values-v31/splash_themes.xml b/androidApp/src/main/res/values-v31/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values-v31/splash_themes.xml rename to androidApp/src/main/res/values-v31/splash_themes.xml diff --git a/core/ui/src/main/res/values/splash_themes.xml b/androidApp/src/main/res/values/splash_themes.xml similarity index 100% rename from core/ui/src/main/res/values/splash_themes.xml rename to androidApp/src/main/res/values/splash_themes.xml diff --git a/android-app/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml similarity index 100% rename from android-app/src/main/res/values/strings.xml rename to androidApp/src/main/res/values/strings.xml diff --git a/android-app/src/main/res/xml/data_extraction_rules.xml b/androidApp/src/main/res/xml/data_extraction_rules.xml similarity index 100% rename from android-app/src/main/res/xml/data_extraction_rules.xml rename to androidApp/src/main/res/xml/data_extraction_rules.xml diff --git a/android-app/src/main/res/xml/locale_config.xml b/androidApp/src/main/res/xml/locale_config.xml similarity index 100% rename from android-app/src/main/res/xml/locale_config.xml rename to androidApp/src/main/res/xml/locale_config.xml diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index 148ea5707..4f3b634c9 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -1,7 +1,8 @@ +@file:Suppress("UnstableApiUsage") + plugins { - id("com.android.test") - id("org.jetbrains.kotlin.android") - id("kotlin-android") + alias(libs.plugins.android.test) + alias(libs.plugins.kotlin.android) } android { @@ -22,18 +23,12 @@ android { } } - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=androidx.benchmark.macro.ExperimentalBaselineProfilesApi" - ) - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.toVersion(libs.versions.jdk.get().toInt()) + targetCompatibility = JavaVersion.toVersion(libs.versions.jdk.get().toInt()) } - targetProjectPath = ":android-app" + targetProjectPath = ":androidApp" experimentalProperties["android.experimental.self-instrumenting"] = true } @@ -45,8 +40,8 @@ android { }*/ dependencies { + implementation(libs.bundles.androidx.test.espresso) implementation(libs.androidx.benchmark.macro.junit) - implementation(libs.androidx.espresso.core) implementation(libs.androidx.test.ext.junit.ktx) implementation(libs.androidx.test.uiautomator) } \ No newline at end of file diff --git a/benchmark/src/main/java/org/michaelbel/movies/benchmark/StartupBenchmark.kt b/benchmark/src/main/java/org/michaelbel/movies/benchmark/StartupBenchmark.kt index 3e12abcde..1ee0cbeea 100644 --- a/benchmark/src/main/java/org/michaelbel/movies/benchmark/StartupBenchmark.kt +++ b/benchmark/src/main/java/org/michaelbel/movies/benchmark/StartupBenchmark.kt @@ -1,5 +1,8 @@ +@file:SuppressLint("NewApi") + package org.michaelbel.movies.benchmark +import android.annotation.SuppressLint import androidx.benchmark.macro.BaselineProfileMode import androidx.benchmark.macro.CompilationMode import androidx.benchmark.macro.StartupMode diff --git a/build.gradle.kts b/build.gradle.kts index 609779c24..eae6f182a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,16 +1,21 @@ plugins { - alias(libs.plugins.application) apply false - alias(libs.plugins.library) apply false - alias(libs.plugins.dynamic.feature) apply false - alias(libs.plugins.test) apply false - alias(libs.plugins.kotlin) apply false - alias(libs.plugins.kotlin.ksp) apply false - alias(libs.plugins.firebase.appdistribution) apply false - alias(libs.plugins.firebase.crashlytics) apply false - alias(libs.plugins.google.services) apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.android.dynamic.feature) apply false + alias(libs.plugins.android.test) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.multiplatform) apply false + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.kotlin.cocoapods) apply false alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.kotlin.parcelize) apply false + alias(libs.plugins.compose) apply false + alias(libs.plugins.google.ksp) apply false + alias(libs.plugins.google.services) apply false + alias(libs.plugins.google.firebase.appdistribution) apply false + alias(libs.plugins.google.firebase.crashlytics) apply false + alias(libs.plugins.sqldelight) apply false alias(libs.plugins.androidx.navigation.safeargs) apply false - alias(libs.plugins.hilt) apply false alias(libs.plugins.spotless) alias(libs.plugins.detekt) alias(libs.plugins.palantir.git) @@ -21,5 +26,11 @@ detekt { } subprojects { - apply(plugin = "io.gitlab.arturbosch.detekt") + if (name != "desktopApp") { + apply(plugin = "io.gitlab.arturbosch.detekt") + } +} + +extra.apply { + set("jvmTarget", "11") } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 7a034a208..fe443168a 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -6,13 +6,4 @@ dependencies { compileOnly(libs.gradle.plugin) compileOnly(libs.kotlin.plugin) implementation(libs.javapoet) -} - -gradlePlugin { - plugins { - register("androidHilt") { - id = "movies-android-hilt" - implementationClass = "plugins.AndroidHiltConventionPlugin" - } - } } \ No newline at end of file diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index 85c972e68..c8b301f88 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -1,3 +1,5 @@ +@file:Suppress("UnstableApiUsage") + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { @@ -19,6 +21,4 @@ dependencyResolutionManagement { from(files("../gradle/libs.versions.toml")) } } -} - -rootProject.name = "build-logic" \ No newline at end of file +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/plugins/AndroidHiltConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidHiltConventionPlugin.kt deleted file mode 100644 index 9c881017a..000000000 --- a/buildSrc/src/main/kotlin/plugins/AndroidHiltConventionPlugin.kt +++ /dev/null @@ -1,25 +0,0 @@ -package plugins - -import ktx.implementation -import ktx.ksp -import ktx.libs -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.kotlin.dsl.dependencies - -internal class AndroidHiltConventionPlugin: Plugin { - - override fun apply(target: Project) { - target.run { - pluginManager.run { - apply("dagger.hilt.android.plugin") - apply("com.google.devtools.ksp") - } - - dependencies { - implementation(libs.findLibrary("hilt.android").get()) - ksp(libs.findLibrary("hilt.compiler").get()) - } - } - } -} \ No newline at end of file diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 3ad4cd5b7..25f3eb1e1 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -262,6 +262,7 @@ exceptions: - 'MalformedURLException' - 'NumberFormatException' - 'ParseException' + - 'SerializationException' allowedExceptionNameRegex: '_|(ignore|expected).*' ThrowingExceptionFromFinally: active: true @@ -604,7 +605,7 @@ style: ignoreActualFunction: true excludedFunctions: [] LoopWithTooManyJumpStatements: - active: true + active: false maxJumpCount: 1 MagicNumber: active: false @@ -680,7 +681,7 @@ style: RedundantVisibilityModifierRule: active: false ReturnCount: - active: true + active: false max: 3 excludedFunctions: - 'equals' diff --git a/config/images/10.png b/config/images/10.png index 132016024..c7c8a37cb 100644 Binary files a/config/images/10.png and b/config/images/10.png differ diff --git a/config/images/11.png b/config/images/11.png new file mode 100644 index 000000000..c479e32a9 Binary files /dev/null and b/config/images/11.png differ diff --git a/config/images/12.png b/config/images/12.png new file mode 100644 index 000000000..fa8e455ff Binary files /dev/null and b/config/images/12.png differ diff --git a/config/images/13.png b/config/images/13.png new file mode 100644 index 000000000..eea9b7061 Binary files /dev/null and b/config/images/13.png differ diff --git a/config/images/14.png b/config/images/14.png new file mode 100644 index 000000000..132016024 Binary files /dev/null and b/config/images/14.png differ diff --git a/config/images/5.png b/config/images/5.png index 121fb3852..06a4557dc 100644 Binary files a/config/images/5.png and b/config/images/5.png differ diff --git a/config/images/6.png b/config/images/6.png index 3ca59145e..e713f3c6e 100644 Binary files a/config/images/6.png and b/config/images/6.png differ diff --git a/config/images/9.png b/config/images/9.png index eea9b7061..bfddab5ea 100644 Binary files a/config/images/9.png and b/config/images/9.png differ diff --git a/config/images/badge-appbazar.svg b/config/images/badge-appbazar.svg new file mode 100644 index 000000000..0903fb3da --- /dev/null +++ b/config/images/badge-appbazar.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/images/badge-appgallery.svg b/config/images/badge-appgallery.svg new file mode 100644 index 000000000..e7b2d3ce4 --- /dev/null +++ b/config/images/badge-appgallery.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/images/badge-aptoide.svg b/config/images/badge-aptoide.svg new file mode 100644 index 000000000..04a80cf60 --- /dev/null +++ b/config/images/badge-aptoide.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/config/images/badge-direct-apk.png b/config/images/badge-direct-apk.png index 3c789f0c3..aced14363 100644 Binary files a/config/images/badge-direct-apk.png and b/config/images/badge-direct-apk.png differ diff --git a/config/images/badge-fdroid.svg b/config/images/badge-fdroid.svg new file mode 100644 index 000000000..c59cb3e3f --- /dev/null +++ b/config/images/badge-fdroid.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/images/badge-galaxy.svg b/config/images/badge-galaxy.svg new file mode 100644 index 000000000..3910f57c0 --- /dev/null +++ b/config/images/badge-galaxy.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/images/badge-getapps.svg b/config/images/badge-getapps.svg new file mode 100644 index 000000000..875b65c4a --- /dev/null +++ b/config/images/badge-getapps.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/images/badge-google-play.png b/config/images/badge-google-play.png deleted file mode 100644 index bf5e835d6..000000000 Binary files a/config/images/badge-google-play.png and /dev/null differ diff --git a/config/images/badge-googleplay.svg b/config/images/badge-googleplay.svg new file mode 100644 index 000000000..96c472c2c --- /dev/null +++ b/config/images/badge-googleplay.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/config/images/badge-huawei-appgallery.png b/config/images/badge-huawei-appgallery.png deleted file mode 100644 index 970cc7965..000000000 Binary files a/config/images/badge-huawei-appgallery.png and /dev/null differ diff --git a/config/images/badge-obtainium.png b/config/images/badge-obtainium.png new file mode 100644 index 000000000..5cdd2b6c0 Binary files /dev/null and b/config/images/badge-obtainium.png differ diff --git a/config/images/badge-onestore.svg b/config/images/badge-onestore.svg new file mode 100644 index 000000000..9febbe1d2 --- /dev/null +++ b/config/images/badge-onestore.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/images/badge-rustore.svg b/config/images/badge-rustore.svg new file mode 100644 index 000000000..949cbb652 --- /dev/null +++ b/config/images/badge-rustore.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/images/play_store_app_icon_512x512_rounded.png b/config/images/play_store_app_icon_512x512_rounded.png new file mode 100644 index 000000000..3e3364dc9 Binary files /dev/null and b/config/images/play_store_app_icon_512x512_rounded.png differ diff --git a/core/analytics/.gitignore b/core/analytics-kmp/.gitignore similarity index 100% rename from core/analytics/.gitignore rename to core/analytics-kmp/.gitignore diff --git a/core/analytics-kmp/build.gradle.kts b/core/analytics-kmp/build.gradle.kts new file mode 100644 index 000000000..515a2daf3 --- /dev/null +++ b/core/analytics-kmp/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:platform-services:interactor-kmp")) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.analytics_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/analytics-kmp/src/androidMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt b/core/analytics-kmp/src/androidMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt new file mode 100644 index 000000000..562c45ea7 --- /dev/null +++ b/core/analytics-kmp/src/androidMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt @@ -0,0 +1,35 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.analytics.impl + +import android.os.Bundle +import androidx.core.os.bundleOf +import org.michaelbel.movies.analytics.MoviesAnalytics +import org.michaelbel.movies.analytics.constants.MoviesParams +import org.michaelbel.movies.analytics.model.BaseEvent +import org.michaelbel.movies.platform.analytics.AnalyticsService + +internal actual class MoviesAnalyticsImpl( + private val analyticsService: AnalyticsService +): MoviesAnalytics { + + actual override fun trackDestination(route: String?, arguments: HashMap) { + val args = Bundle() + arguments.forEach { (key, value) -> + args.putString(key, value) + } + val bundle = bundleOf( + analyticsService.screenName to route, + MoviesParams.PARAM_ARGUMENTS to args + ) + analyticsService.logEvent(analyticsService.screenView, bundle) + } + + actual override fun logEvent(event: BaseEvent) { + val bundle = bundleOf() + event.params.forEach { (key, value) -> + bundle.putString(key, value) + } + analyticsService.logEvent(event.name, bundle) + } +} \ No newline at end of file diff --git a/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/MoviesAnalytics.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/MoviesAnalytics.kt new file mode 100644 index 000000000..d25fffdb0 --- /dev/null +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/MoviesAnalytics.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.analytics + +import org.michaelbel.movies.analytics.model.BaseEvent + +interface MoviesAnalytics { + + fun trackDestination( + route: String?, + arguments: HashMap + ) + + fun logEvent( + event: BaseEvent + ) +} \ No newline at end of file diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/constants/MoviesEvents.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/constants/MoviesEvents.kt similarity index 100% rename from core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/constants/MoviesEvents.kt rename to core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/constants/MoviesEvents.kt diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/constants/MoviesParams.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/constants/MoviesParams.kt similarity index 99% rename from core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/constants/MoviesParams.kt rename to core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/constants/MoviesParams.kt index b5a08f844..79efbb9a8 100644 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/constants/MoviesParams.kt +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/constants/MoviesParams.kt @@ -2,7 +2,6 @@ package org.michaelbel.movies.analytics.constants internal object MoviesParams { const val PARAM_ARGUMENTS = "destination_arguments" - const val PARAM_SELECTED_LANGUAGE = "selected_language" const val PARAM_SELECTED_THEME = "selected_theme" const val PARAM_SELECTED_FEED_VIEW = "selected_feed_view" diff --git a/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/di/MoviesAnalyticsKoinModule.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/di/MoviesAnalyticsKoinModule.kt new file mode 100644 index 000000000..3beaa51e6 --- /dev/null +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/di/MoviesAnalyticsKoinModule.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.analytics.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.analytics.MoviesAnalytics +import org.michaelbel.movies.analytics.impl.MoviesAnalyticsImpl + +val moviesAnalyticsKoinModule = module { + singleOf(::MoviesAnalyticsImpl) { bind() } +} \ No newline at end of file diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/ChangeDynamicColorsEvent.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/ChangeDynamicColorsEvent.kt similarity index 100% rename from core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/ChangeDynamicColorsEvent.kt rename to core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/ChangeDynamicColorsEvent.kt index dcc67aaa1..7e35efcbc 100644 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/ChangeDynamicColorsEvent.kt +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/ChangeDynamicColorsEvent.kt @@ -1,8 +1,8 @@ package org.michaelbel.movies.analytics.event import org.michaelbel.movies.analytics.constants.MoviesEvents -import org.michaelbel.movies.analytics.model.BaseEvent import org.michaelbel.movies.analytics.constants.MoviesParams +import org.michaelbel.movies.analytics.model.BaseEvent class ChangeDynamicColorsEvent( enabled: Boolean diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectFeedViewEvent.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectFeedViewEvent.kt similarity index 100% rename from core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectFeedViewEvent.kt rename to core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectFeedViewEvent.kt diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectLanguageEvent.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectLanguageEvent.kt similarity index 100% rename from core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectLanguageEvent.kt rename to core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectLanguageEvent.kt index c7c343e6f..4f726b604 100644 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectLanguageEvent.kt +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectLanguageEvent.kt @@ -1,8 +1,8 @@ package org.michaelbel.movies.analytics.event -import org.michaelbel.movies.analytics.model.BaseEvent import org.michaelbel.movies.analytics.constants.MoviesEvents import org.michaelbel.movies.analytics.constants.MoviesParams +import org.michaelbel.movies.analytics.model.BaseEvent class SelectLanguageEvent( language: String diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectMovieListEvent.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectMovieListEvent.kt similarity index 100% rename from core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectMovieListEvent.kt rename to core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectMovieListEvent.kt diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectThemeEvent.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectThemeEvent.kt similarity index 100% rename from core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectThemeEvent.kt rename to core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectThemeEvent.kt index c0f1af152..023577354 100644 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/event/SelectThemeEvent.kt +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/event/SelectThemeEvent.kt @@ -1,8 +1,8 @@ package org.michaelbel.movies.analytics.event import org.michaelbel.movies.analytics.constants.MoviesEvents -import org.michaelbel.movies.analytics.model.BaseEvent import org.michaelbel.movies.analytics.constants.MoviesParams +import org.michaelbel.movies.analytics.model.BaseEvent class SelectThemeEvent( theme: String diff --git a/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt new file mode 100644 index 000000000..a6c1f6315 --- /dev/null +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt @@ -0,0 +1,12 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.analytics.impl + +import org.michaelbel.movies.analytics.model.BaseEvent + +internal expect class MoviesAnalyticsImpl { + + fun trackDestination(route: String?, arguments: HashMap) + + fun logEvent(event: BaseEvent) +} \ No newline at end of file diff --git a/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/model/BaseEvent.kt b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/model/BaseEvent.kt new file mode 100644 index 000000000..5a9165a6b --- /dev/null +++ b/core/analytics-kmp/src/commonMain/kotlin/org/michaelbel/movies/analytics/model/BaseEvent.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.analytics.model + +open class BaseEvent internal constructor( + val name: String +) { + val params = hashMapOf() + + protected open fun add(key: String, value: Any) { + params[key] = value.toString() + } +} \ No newline at end of file diff --git a/core/analytics-kmp/src/desktopMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt b/core/analytics-kmp/src/desktopMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt new file mode 100644 index 000000000..c9cec7770 --- /dev/null +++ b/core/analytics-kmp/src/desktopMain/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt @@ -0,0 +1,13 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.analytics.impl + +import org.michaelbel.movies.analytics.MoviesAnalytics +import org.michaelbel.movies.analytics.model.BaseEvent + +internal actual class MoviesAnalyticsImpl: MoviesAnalytics { + + actual override fun trackDestination(route: String?, arguments: HashMap) {} + + actual override fun logEvent(event: BaseEvent) {} +} \ No newline at end of file diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts deleted file mode 100644 index a57b1826a..000000000 --- a/core/analytics/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.analytics" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:platform-services:interactor")) -} \ No newline at end of file diff --git a/core/analytics/src/main/AndroidManifest.xml b/core/analytics/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/analytics/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/MoviesAnalytics.kt b/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/MoviesAnalytics.kt deleted file mode 100644 index 0cb3fa8cf..000000000 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/MoviesAnalytics.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.michaelbel.movies.analytics - -import android.os.Bundle -import org.michaelbel.movies.analytics.model.BaseEvent - -interface MoviesAnalytics { - - fun trackDestination(route: String?, arguments: Bundle?) - - fun logEvent(event: BaseEvent) -} \ No newline at end of file diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/di/MoviesAnalyticsModule.kt b/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/di/MoviesAnalyticsModule.kt deleted file mode 100644 index 89abdabbb..000000000 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/di/MoviesAnalyticsModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.analytics.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import org.michaelbel.movies.analytics.MoviesAnalytics -import org.michaelbel.movies.analytics.impl.MoviesAnalyticsImpl - -@Module -@InstallIn(SingletonComponent::class) -internal interface MoviesAnalyticsModule { - - @Binds - @Singleton - fun provideMoviesAnalytics(analytics: MoviesAnalyticsImpl): MoviesAnalytics -} \ No newline at end of file diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt b/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt deleted file mode 100644 index 2bd451f0d..000000000 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/impl/MoviesAnalyticsImpl.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.michaelbel.movies.analytics.impl - -import android.os.Bundle -import androidx.core.os.bundleOf -import javax.inject.Inject -import org.michaelbel.movies.analytics.MoviesAnalytics -import org.michaelbel.movies.analytics.constants.MoviesParams -import org.michaelbel.movies.analytics.model.BaseEvent -import org.michaelbel.movies.platform.analytics.AnalyticsService - -internal class MoviesAnalyticsImpl @Inject constructor( - private val analyticsService: AnalyticsService -): MoviesAnalytics { - - override fun trackDestination(route: String?, arguments: Bundle?) { - val bundle = bundleOf( - analyticsService.screenName to route, - MoviesParams.PARAM_ARGUMENTS to arguments - ) - analyticsService.logEvent(analyticsService.screenView, bundle) - } - - override fun logEvent(event: BaseEvent) { - analyticsService.logEvent(event.name, event.params) - } -} \ No newline at end of file diff --git a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/model/BaseEvent.kt b/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/model/BaseEvent.kt deleted file mode 100644 index 4c3d338d3..000000000 --- a/core/analytics/src/main/kotlin/org/michaelbel/movies/analytics/model/BaseEvent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.analytics.model - -import android.os.Bundle -import androidx.core.os.bundleOf - -open class BaseEvent( - val name: String -) { - val params: Bundle = bundleOf() - - protected open fun add(key: String, value: Any) { - params.putString(key, value.toString()) - } -} \ No newline at end of file diff --git a/core/common/.gitignore b/core/common-kmp/.gitignore similarity index 100% rename from core/common/.gitignore rename to core/common-kmp/.gitignore diff --git a/core/common-kmp/build.gradle.kts b/core/common-kmp/build.gradle.kts new file mode 100644 index 000000000..66ade521a --- /dev/null +++ b/core/common-kmp/build.gradle.kts @@ -0,0 +1,69 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + api(project(":core:platform-services:interactor-kmp")) + implementation(project(":core:analytics-kmp")) + implementation(project(":core:network-kmp")) + implementation(compose.material3) + implementation(libs.bundles.kotlinx.coroutines.common) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.koin.common) + api(libs.kmp.viewmodel) + } + androidMain.dependencies { + api(libs.bundles.kotlinx.coroutines.android) + api(libs.bundles.lifecycle.android) + api(libs.androidx.activity.compose) + api(libs.androidx.biometric.ktx) + api(libs.androidx.core.ktx) + api(libs.androidx.startup.runtime) + api(libs.androidx.work.runtime.ktx) + api(libs.timber) + implementation(libs.bundles.androidx.appcompat) + implementation(libs.androidx.browser) + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.common_kmp" + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + buildConfig = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/BiometricController.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/BiometricController.kt new file mode 100644 index 000000000..1f7b1ee8d --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/BiometricController.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric + +import androidx.fragment.app.FragmentActivity +import kotlinx.coroutines.flow.Flow + +interface BiometricController { + + val isBiometricAvailable: Flow + + fun authenticate(activity: FragmentActivity, biometricListener: BiometricListener) +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/BiometricListener.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/BiometricListener.kt new file mode 100644 index 000000000..5fa948f5f --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/BiometricListener.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.common.biometric + +interface BiometricListener { + + fun onSuccess() + + fun onCancel() +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule.kt new file mode 100644 index 000000000..7ca8a0c60 --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.BiometricController +import org.michaelbel.movies.common.biometric.impl.BiometricControllerImpl + +val biometricKoinModule = module { + singleOf(::BiometricControllerImpl) { bind() } +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl.kt new file mode 100644 index 000000000..4bc439e3c --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl.kt @@ -0,0 +1,50 @@ +package org.michaelbel.movies.common.biometric.impl + +import android.content.Context +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.common.biometric.BiometricController +import org.michaelbel.movies.common.biometric.BiometricListener +import org.michaelbel.movies.common_kmp.R + +internal class BiometricControllerImpl( + private val context: Context +): BiometricController { + + override val isBiometricAvailable: Flow + get() { + val biometricManager = BiometricManager.from(context) + val authenticators = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL) + return flowOf(authenticators == BiometricManager.BIOMETRIC_SUCCESS) + } + + override fun authenticate(activity: FragmentActivity, biometricListener: BiometricListener) { + val biometricPrompt = BiometricPrompt( + activity, + ContextCompat.getMainExecutor(context), + object: BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + biometricListener.onSuccess() + } + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + when (errorCode) { + BiometricPrompt.ERROR_USER_CANCELED -> biometricListener.onCancel() + BiometricPrompt.ERROR_NEGATIVE_BUTTON -> biometricListener.onCancel() + } + } + } + ) + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle(context.getString(R.string.biometric_title)) + .setSubtitle(context.getString(R.string.biometric_subtitle)) + .setDescription(context.getString(R.string.biometric_description)) + .setNegativeButtonText(context.getString(R.string.biometric_cancel)) + .build() + biometricPrompt.authenticate(promptInfo) + } +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/browser/Browser.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/browser/Browser.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/browser/Browser.kt rename to core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/browser/Browser.kt diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/crashlytics/CrashlyticsTree.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/crashlytics/CrashlyticsTree.kt similarity index 87% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/crashlytics/CrashlyticsTree.kt rename to core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/crashlytics/CrashlyticsTree.kt index 6dbb854f7..7b5c7888e 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/crashlytics/CrashlyticsTree.kt +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/crashlytics/CrashlyticsTree.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + package org.michaelbel.movies.common.crashlytics import org.michaelbel.movies.platform.crashlytics.CrashlyticsService diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/FlowKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/FlowKtx.kt new file mode 100644 index 000000000..59070b380 --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/FlowKtx.kt @@ -0,0 +1,22 @@ +package org.michaelbel.movies.common.ktx + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +inline fun Flow.launchAndCollectIn( + owner: LifecycleOwner, + minActiveState: Lifecycle.State = Lifecycle.State.STARTED, + crossinline action: suspend CoroutineScope.(T) -> Unit +): Job = owner.lifecycleScope.launch { + owner.repeatOnLifecycle(minActiveState) { + collect { + action(it) + } + } +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/IntentKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/IntentKtx.kt new file mode 100644 index 000000000..0c0a8cfd3 --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/IntentKtx.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.common.ktx + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import androidx.core.net.toUri + +val Context.appSettingsIntent: Intent + get() { + val intent = Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + "package:$packageName".toUri() + ).apply { + addCategory(Intent.CATEGORY_DEFAULT) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + return intent + } \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt new file mode 100644 index 000000000..ebf81fba1 --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.common.ktx + +import org.michaelbel.movies.common_kmp.BuildConfig + +fun printlnDebug(message: String) { + if (BuildConfig.DEBUG) { + println(message) + } +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/NotificationManagerKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/NotificationManagerKtx.kt new file mode 100644 index 000000000..97193c2ef --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/NotificationManagerKtx.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.common.ktx + +import android.content.Context +import androidx.core.app.NotificationManagerCompat + +val Context.notificationManager: NotificationManagerCompat + get() = NotificationManagerCompat.from(this) \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/PermissionKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/PermissionKtx.kt new file mode 100644 index 000000000..ce35e47fe --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/PermissionKtx.kt @@ -0,0 +1,14 @@ +package org.michaelbel.movies.common.ktx + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.ContextCompat + +val Context.isPostNotificationsPermissionGranted: Boolean + get() = if (Build.VERSION.SDK_INT >= 33) { + ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + } else { + notificationManager.areNotificationsEnabled() + } \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/SavedStateHandleKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/SavedStateHandleKtx.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/SavedStateHandleKtx.kt rename to core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/SavedStateHandleKtx.kt diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/TimeKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/TimeKtx.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/TimeKtx.kt rename to core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/TimeKtx.kt diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/UriKtx.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/UriKtx.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/UriKtx.kt rename to core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/UriKtx.kt diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt new file mode 100644 index 000000000..9389d2619 --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt @@ -0,0 +1,52 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.common.localization.impl + +import android.app.LocaleManager +import android.content.Context +import android.os.Build +import android.os.LocaleList +import androidx.appcompat.app.AppCompatDelegate +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.withContext +import org.michaelbel.movies.analytics.MoviesAnalytics +import org.michaelbel.movies.analytics.event.SelectLanguageEvent +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.common.localization.LocaleController +import org.michaelbel.movies.common.localization.model.AppLanguage +import java.util.Locale + +internal actual class LocaleControllerImpl( + private val context: Context, + private val dispatchers: MoviesDispatchers, + private val analytics: MoviesAnalytics +): LocaleController { + + actual override val language: String + get() = AppCompatDelegate.getApplicationLocales()[0]?.language ?: AppLanguage.English().code + + actual override val appLanguage: Flow = flowOf(AppLanguage.transform(language)) + + actual override suspend fun selectLanguage(language: AppLanguage) { + withContext(dispatchers.io) { + // for AppCompatActivity + /*AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(language.code))*/ + + // for ComponentActivity + if (Build.VERSION.SDK_INT >= 33) { + val localeManager = context.getSystemService(LocaleManager::class.java) + localeManager.applicationLocales = LocaleList.forLanguageTags(AppLanguage.code(language)) + } else { + val locale = Locale(AppLanguage.code(language)) + Locale.setDefault(locale) + + val configuration = context.resources.configuration + configuration.setLocale(locale) + context.createConfigurationContext(configuration) + } + + analytics.logEvent(SelectLanguageEvent(language.toString())) + } + } +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/log/Logger.kt b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/log/Logger.kt new file mode 100644 index 000000000..68a7e73ac --- /dev/null +++ b/core/common-kmp/src/androidMain/kotlin/org/michaelbel/movies/common/log/Logger.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.common.log + +import timber.log.Timber + +actual fun log(throwable: Throwable) { + Timber.e(throwable) +} \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/res/values-ru/strings.xml b/core/common-kmp/src/androidMain/res/values-ru/strings.xml new file mode 100644 index 000000000..bcb8d27a5 --- /dev/null +++ b/core/common-kmp/src/androidMain/res/values-ru/strings.xml @@ -0,0 +1,7 @@ + + + Биометрическая авторизация + Войди в приложение с биометрией + Подтверди личность + Отмена + \ No newline at end of file diff --git a/core/common-kmp/src/androidMain/res/values/strings.xml b/core/common-kmp/src/androidMain/res/values/strings.xml new file mode 100644 index 000000000..d7cbd2930 --- /dev/null +++ b/core/common-kmp/src/androidMain/res/values/strings.xml @@ -0,0 +1,7 @@ + + + Biometric Login + Log in using your biometric credential + Verify identity + Cancel + \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/RepoConfig.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/RepoConfig.kt new file mode 100644 index 000000000..a351ebe32 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/RepoConfig.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.common + +const val MOVIES_GITHUB_URL = "https://github.com/michaelbel/movies" \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/SealedString.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/SealedString.kt new file mode 100644 index 000000000..14327d722 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/SealedString.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.common + +interface SealedString \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/ThemeData.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/ThemeData.kt new file mode 100644 index 000000000..c5b65f59c --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/ThemeData.kt @@ -0,0 +1,29 @@ +package org.michaelbel.movies.common + +import org.michaelbel.movies.common.theme.AppTheme + +data class ThemeData( + val appTheme: AppTheme, + val dynamicColors: Boolean, + val paletteKey: Int, + val seedColor: Int +) { + companion object { + const val STYLE_TONAL_SPOT = 0 + const val STYLE_SPRITZ = 1 + const val STYLE_FRUIT_SALAD = 2 + const val STYLE_VIBRANT = 3 + const val STYLE_MONOCHROME = 4 + + const val DEFAULT_SEED_COLOR = -12687058 // First color from Palette List + const val DEFAULT_SEED_COLOR_HEX = 0xFF3E692E // String.format("#%06X", (0xFFFFFF and DEFAULT_SEED_COLOR)) + + val Default: ThemeData + get() = ThemeData( + appTheme = AppTheme.FollowSystem, + dynamicColors = false, + paletteKey = STYLE_TONAL_SPOT, + seedColor = DEFAULT_SEED_COLOR + ) + } +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/appearance/FeedView.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/appearance/FeedView.kt similarity index 86% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/appearance/FeedView.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/appearance/FeedView.kt index 35896a5e1..ab3744a13 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/appearance/FeedView.kt +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/appearance/FeedView.kt @@ -1,8 +1,9 @@ package org.michaelbel.movies.common.appearance +import org.michaelbel.movies.common.SealedString import org.michaelbel.movies.common.appearance.exceptions.InvalidFeedViewException -sealed interface FeedView { +sealed interface FeedView: SealedString { data object FeedList: FeedView diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/appearance/exceptions/InvalidFeedViewException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/appearance/exceptions/InvalidFeedViewException.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/appearance/exceptions/InvalidFeedViewException.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/appearance/exceptions/InvalidFeedViewException.kt diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/config/RemoteParams.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/config/RemoteParams.kt new file mode 100644 index 000000000..1a64bde37 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/config/RemoteParams.kt @@ -0,0 +1,10 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.config + +object RemoteParams { + const val PARAM_SETTINGS_ICON_VISIBLE = "param_settings_icon_visible" +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/MoviesDispatchers.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/MoviesDispatchers.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/MoviesDispatchers.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/MoviesDispatchers.kt diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersKoinModule.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersKoinModule.kt new file mode 100644 index 000000000..2b010d14f --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersKoinModule.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.dispatchers.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.impl.MoviesDispatchersImpl + +val dispatchersKoinModule = module { + singleOf(::MoviesDispatchersImpl) { bind() } +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt similarity index 83% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt index 5ac749b12..8725cbcf9 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt @@ -3,9 +3,8 @@ package org.michaelbel.movies.common.dispatchers.impl import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import javax.inject.Inject -internal class MoviesDispatchersImpl @Inject constructor(): MoviesDispatchers { +internal class MoviesDispatchersImpl: MoviesDispatchers { override val default: CoroutineDispatcher get() = Dispatchers.Default diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/AccountDetailsException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/AccountDetailsException.kt new file mode 100644 index 000000000..dae7b0848 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/AccountDetailsException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object AccountDetailsException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/ApiKeyNotNullException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/ApiKeyNotNullException.kt new file mode 100644 index 000000000..81dfc89ff --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/ApiKeyNotNullException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object ApiKeyNotNullException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateRequestTokenException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateRequestTokenException.kt new file mode 100644 index 000000000..ad1ed4cae --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateRequestTokenException.kt @@ -0,0 +1,10 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data class CreateRequestTokenException( + val loginViaTmdb: Boolean +): Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionException.kt new file mode 100644 index 000000000..2cbd18f0a --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object CreateSessionException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionWithLoginException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionWithLoginException.kt new file mode 100644 index 000000000..c83cf4869 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionWithLoginException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object CreateSessionWithLoginException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/DeleteSessionException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/DeleteSessionException.kt new file mode 100644 index 000000000..66dca5ff9 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/DeleteSessionException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object DeleteSessionException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/MovieDetailsException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/MovieDetailsException.kt new file mode 100644 index 000000000..b8e2fcaa4 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/MovieDetailsException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object MovieDetailsException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/MoviesUpcomingException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/MoviesUpcomingException.kt new file mode 100644 index 000000000..74069f7b0 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/MoviesUpcomingException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object MoviesUpcomingException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/PageEmptyException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/PageEmptyException.kt new file mode 100644 index 000000000..e91e0771f --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/exceptions/PageEmptyException.kt @@ -0,0 +1,8 @@ +@file:Suppress( + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE" +) + +package org.michaelbel.movies.common.exceptions + +data object PageEmptyException: Exception() \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/gender/GrammaticalGender.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/gender/GrammaticalGender.kt new file mode 100644 index 000000000..23e17cf07 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/gender/GrammaticalGender.kt @@ -0,0 +1,51 @@ +package org.michaelbel.movies.common.gender + +import org.michaelbel.movies.common.SealedString +import org.michaelbel.movies.common.gender.exceptions.InvalidGenderException + +sealed interface GrammaticalGender: SealedString { + + data class NotSpecified( + val value: Int = 0 + ): GrammaticalGender + + data class Neutral( + val value: Int = 1 + ): GrammaticalGender + + data class Feminine( + val value: Int = 2 + ): GrammaticalGender + + data class Masculine( + val value: Int = 3 + ): GrammaticalGender + + companion object { + val VALUES = listOf( + NotSpecified(), + Neutral(), + Feminine(), + Masculine() + ) + + fun transform(gender: Int): GrammaticalGender { + return when (gender) { + 0 -> NotSpecified() + 1 -> Neutral() + 2 -> Feminine() + 3 -> Masculine() + else -> throw InvalidGenderException + } + } + + fun value(gender: GrammaticalGender): Int { + return when (gender) { + is NotSpecified -> NotSpecified().value + is Neutral -> Neutral().value + is Feminine -> Feminine().value + is Masculine -> Masculine().value + } + } + } +} \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/gender/exceptions/InvalidGenderException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/gender/exceptions/InvalidGenderException.kt new file mode 100644 index 000000000..ac0c44813 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/gender/exceptions/InvalidGenderException.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.common.gender.exceptions + +internal data object InvalidGenderException: Exception("Invalid gender") \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/list/MovieList.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/list/MovieList.kt new file mode 100644 index 000000000..b64bdde35 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/list/MovieList.kt @@ -0,0 +1,51 @@ +package org.michaelbel.movies.common.list + +import org.michaelbel.movies.common.SealedString +import org.michaelbel.movies.common.list.exceptions.InvalidMovieListException + +sealed interface MovieList: SealedString { + + data class NowPlaying( + val name: String = "now_playing" + ): MovieList + + data class Popular( + val name: String = "popular" + ): MovieList + + data class TopRated( + val name: String = "top_rated" + ): MovieList + + data class Upcoming( + val name: String = "upcoming" + ): MovieList + + companion object { + val VALUES = listOf( + NowPlaying(), + Popular(), + TopRated(), + Upcoming() + ) + + fun transform(name: String): MovieList { + return when (name) { + NowPlaying().toString() -> NowPlaying() + Popular().toString() -> Popular() + TopRated().toString() -> TopRated() + Upcoming().toString() -> Upcoming() + else -> throw InvalidMovieListException + } + } + + fun name(movieList: MovieList): String { + return when (movieList) { + is NowPlaying -> NowPlaying().name + is Popular -> Popular().name + is TopRated -> TopRated().name + is Upcoming -> Upcoming().name + } + } + } +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/list/exceptions/InvalidMovieListException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/list/exceptions/InvalidMovieListException.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/list/exceptions/InvalidMovieListException.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/list/exceptions/InvalidMovieListException.kt diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/LocaleController.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/LocaleController.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/localization/LocaleController.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/LocaleController.kt diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/di/LocaleKoinModule.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/di/LocaleKoinModule.kt new file mode 100644 index 000000000..7376160e6 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/di/LocaleKoinModule.kt @@ -0,0 +1,17 @@ +package org.michaelbel.movies.common.localization.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.analytics.di.moviesAnalyticsKoinModule +import org.michaelbel.movies.common.dispatchers.di.dispatchersKoinModule +import org.michaelbel.movies.common.localization.LocaleController +import org.michaelbel.movies.common.localization.impl.LocaleControllerImpl + +val localeKoinModule = module { + includes( + dispatchersKoinModule, + moviesAnalyticsKoinModule + ) + singleOf(::LocaleControllerImpl) { bind() } +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/exceptions/InvalidLocaleException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/exceptions/InvalidLocaleException.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/localization/exceptions/InvalidLocaleException.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/exceptions/InvalidLocaleException.kt diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt new file mode 100644 index 000000000..c90f9f333 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt @@ -0,0 +1,15 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.common.localization.impl + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.common.localization.model.AppLanguage + +internal expect class LocaleControllerImpl { + + val language: String + + val appLanguage: Flow + + suspend fun selectLanguage(language: AppLanguage) +} \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/model/AppLanguage.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/model/AppLanguage.kt new file mode 100644 index 000000000..7b156a542 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/localization/model/AppLanguage.kt @@ -0,0 +1,37 @@ +package org.michaelbel.movies.common.localization.model + +import org.michaelbel.movies.common.SealedString +import org.michaelbel.movies.common.localization.exceptions.InvalidLocaleException + +sealed interface AppLanguage: SealedString { + + data class English( + val code: String = "en" + ): AppLanguage + + data class Russian( + val code: String = "ru" + ): AppLanguage + + companion object { + val VALUES = listOf( + English(), + Russian() + ) + + fun transform(code: String): AppLanguage { + return when (code) { + "en" -> English() + "ru" -> Russian() + else -> throw InvalidLocaleException + } + } + + fun code(language: AppLanguage): String { + return when (language) { + is English -> English().code + is Russian -> Russian().code + } + } + } +} \ No newline at end of file diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/log/Logger.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/log/Logger.kt new file mode 100644 index 000000000..854f354a8 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/log/Logger.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.common.log + +expect fun log(throwable: Throwable) \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/theme/AppTheme.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/theme/AppTheme.kt similarity index 89% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/theme/AppTheme.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/theme/AppTheme.kt index 4ef8be99b..38bcc88cd 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/theme/AppTheme.kt +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/theme/AppTheme.kt @@ -1,8 +1,9 @@ package org.michaelbel.movies.common.theme +import org.michaelbel.movies.common.SealedString import org.michaelbel.movies.common.theme.exceptions.InvalidThemeException -sealed interface AppTheme { +sealed interface AppTheme: SealedString { data object NightNo: AppTheme diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/theme/exceptions/InvalidThemeException.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/theme/exceptions/InvalidThemeException.kt similarity index 100% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/theme/exceptions/InvalidThemeException.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/theme/exceptions/InvalidThemeException.kt diff --git a/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt new file mode 100644 index 000000000..e0a57dfd1 --- /dev/null +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.version + +data class AppVersionData( + val flavor: String +) { + companion object { + val Empty = AppVersionData( + flavor = "" + ) + } +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt similarity index 88% rename from core/common/src/main/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt rename to core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt index e985cc74f..2ddb819ac 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt +++ b/core/common-kmp/src/commonMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt @@ -1,15 +1,15 @@ package org.michaelbel.movies.common.viewmodel import androidx.annotation.CallSuper -import androidx.lifecycle.ViewModel -import kotlin.coroutines.CoroutineContext +import com.hoc081098.kmp.viewmodel.ViewModel import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren -import timber.log.Timber +import org.michaelbel.movies.common.log.log +import kotlin.coroutines.CoroutineContext open class BaseViewModel: ViewModel(), CoroutineScope { @@ -28,6 +28,6 @@ open class BaseViewModel: ViewModel(), CoroutineScope { @CallSuper protected open fun handleError(throwable: Throwable) { - Timber.e(throwable) + log(throwable) } } \ No newline at end of file diff --git a/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/browser/Browser.kt b/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/browser/Browser.kt new file mode 100644 index 000000000..c2f9b07e6 --- /dev/null +++ b/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/browser/Browser.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.browser + +import java.awt.Desktop +import java.net.URI + +fun openUrl(url: String) { + val desktop = if (Desktop.isDesktopSupported()) Desktop.getDesktop() else null + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + desktop.browse(URI.create(url)) + } +} \ No newline at end of file diff --git a/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt b/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt new file mode 100644 index 000000000..8b23bb946 --- /dev/null +++ b/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt @@ -0,0 +1,24 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.common.localization.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.common.localization.LocaleController +import org.michaelbel.movies.common.localization.model.AppLanguage + +internal actual class LocaleControllerImpl( + private val dispatchers: MoviesDispatchers +): LocaleController { + + actual override val language: String + get() = AppLanguage.English().code + + actual override val appLanguage: Flow = flowOf(AppLanguage.transform(language)) + + actual override suspend fun selectLanguage(language: AppLanguage) { + withContext(dispatchers.io) {} + } +} \ No newline at end of file diff --git a/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/log/Logger.kt b/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/log/Logger.kt new file mode 100644 index 000000000..cc66cad1f --- /dev/null +++ b/core/common-kmp/src/desktopMain/kotlin/org/michaelbel/movies/common/log/Logger.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.common.log + +actual fun log(throwable: Throwable) {} \ No newline at end of file diff --git a/core/common-kmp/src/desktopMain/res/values-ru/strings.xml b/core/common-kmp/src/desktopMain/res/values-ru/strings.xml new file mode 100644 index 000000000..bcb8d27a5 --- /dev/null +++ b/core/common-kmp/src/desktopMain/res/values-ru/strings.xml @@ -0,0 +1,7 @@ + + + Биометрическая авторизация + Войди в приложение с биометрией + Подтверди личность + Отмена + \ No newline at end of file diff --git a/core/common-kmp/src/desktopMain/res/values/strings.xml b/core/common-kmp/src/desktopMain/res/values/strings.xml new file mode 100644 index 000000000..d7cbd2930 --- /dev/null +++ b/core/common-kmp/src/desktopMain/res/values/strings.xml @@ -0,0 +1,7 @@ + + + Biometric Login + Log in using your biometric credential + Verify identity + Cancel + \ No newline at end of file diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts deleted file mode 100644 index 99f458c61..000000000 --- a/core/common/build.gradle.kts +++ /dev/null @@ -1,65 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.common" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - buildConfig = true - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(project(":core:platform-services:interactor")) - implementation(project(":core:analytics")) - implementation(project(":core:network")) - api(libs.bundles.kotlin.coroutines) - api(libs.androidx.activity.compose) - api(libs.androidx.core.ktx) - api(libs.androidx.paging.compose) - api(libs.androidx.startup.runtime) - api(libs.androidx.work.runtime.ktx) - api(libs.androidx.hilt.work) - api(libs.bundles.lifecycle) - api(libs.timber) - implementation(libs.bundles.appcompat) - implementation(libs.firebase.crashlytics.ktx) - implementation(libs.androidx.browser) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/core/common/src/main/AndroidManifest.xml b/core/common/src/main/AndroidManifest.xml deleted file mode 100644 index 568741e54..000000000 --- a/core/common/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/config/RemoteParams.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/config/RemoteParams.kt deleted file mode 100644 index 55e861201..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/config/RemoteParams.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.michaelbel.movies.common.config - -object RemoteParams { - const val PARAM_SETTINGS_ICON_VISIBLE = "param_settings_icon_visible" -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersModule.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersModule.kt deleted file mode 100644 index 7ebc76d0c..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.common.dispatchers.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.common.dispatchers.impl.MoviesDispatchersImpl - -@Module -@InstallIn(SingletonComponent::class) -internal interface DispatchersModule { - - @Binds - fun provideDispatchers( - dispatchers: MoviesDispatchersImpl - ): MoviesDispatchers -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/AccountDetailsException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/AccountDetailsException.kt deleted file mode 100644 index 032f7c5de..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/AccountDetailsException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data object AccountDetailsException: Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/ApiKeyNotNullException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/ApiKeyNotNullException.kt deleted file mode 100644 index f61ac9000..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/ApiKeyNotNullException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data object ApiKeyNotNullException: Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateRequestTokenException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateRequestTokenException.kt deleted file mode 100644 index a8561cc57..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateRequestTokenException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data class CreateRequestTokenException( - val loginViaTmdb: Boolean -): Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionException.kt deleted file mode 100644 index 8e0817465..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data object CreateSessionException: Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionWithLoginException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionWithLoginException.kt deleted file mode 100644 index d05423c08..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/CreateSessionWithLoginException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data object CreateSessionWithLoginException: Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/DeleteSessionException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/DeleteSessionException.kt deleted file mode 100644 index 33e2c15df..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/DeleteSessionException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data object DeleteSessionException: Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/MovieDetailsException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/MovieDetailsException.kt deleted file mode 100644 index c50de61fa..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/MovieDetailsException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data object MovieDetailsException: Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/PageEmptyException.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/PageEmptyException.kt deleted file mode 100644 index 012d610fc..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/exceptions/PageEmptyException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.common.exceptions - -data object PageEmptyException: Exception() \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt deleted file mode 100644 index 0fb3f8753..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.common.ktx - -import org.michaelbel.movies.common.BuildConfig - -fun printlnDebug(message: String) { - if (BuildConfig.DEBUG) { - println(message) - } -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/NotificationManagerKtx.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/NotificationManagerKtx.kt deleted file mode 100644 index 8051b2cf2..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/NotificationManagerKtx.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.michaelbel.movies.common.ktx - -import android.app.NotificationManager -import android.content.Context -import androidx.core.content.ContextCompat - -val Context.notificationManager: NotificationManager - get() = ContextCompat.getSystemService(this, NotificationManager::class.java) as NotificationManager \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/list/MovieList.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/list/MovieList.kt deleted file mode 100644 index 0d50919af..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/list/MovieList.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.michaelbel.movies.common.list - -import org.michaelbel.movies.common.list.exceptions.InvalidMovieListException - -sealed class MovieList( - val name: String -) { - data object NowPlaying: MovieList("now_playing") - - data object Popular: MovieList("popular") - - data object TopRated: MovieList("top_rated") - - data object Upcoming: MovieList("upcoming") - - companion object { - val VALUES = listOf( - NowPlaying, - Popular, - TopRated, - Upcoming - ) - - fun transform(name: String): MovieList { - return when (name) { - NowPlaying.toString() -> NowPlaying - Popular.toString() -> Popular - TopRated.toString() -> TopRated - Upcoming.toString() -> Upcoming - else -> throw InvalidMovieListException - } - } - } -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/di/LocaleModule.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/di/LocaleModule.kt deleted file mode 100644 index 11003ef4a..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/di/LocaleModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.common.localization.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import org.michaelbel.movies.common.localization.LocaleController -import org.michaelbel.movies.common.localization.impl.LocaleControllerImpl - -@Module -@InstallIn(SingletonComponent::class) -internal interface LocaleModule { - - @Binds - @Singleton - fun provideLocaleController(controller: LocaleControllerImpl): LocaleController -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt deleted file mode 100644 index 4968d5c4f..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.michaelbel.movies.common.localization.impl - -import androidx.appcompat.app.AppCompatDelegate -import androidx.core.os.LocaleListCompat -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.withContext -import org.michaelbel.movies.analytics.MoviesAnalytics -import org.michaelbel.movies.analytics.event.SelectLanguageEvent -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.common.localization.LocaleController -import org.michaelbel.movies.common.localization.model.AppLanguage -import javax.inject.Inject - -internal class LocaleControllerImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val analytics: MoviesAnalytics -): LocaleController { - - override val language: String - get() = AppCompatDelegate.getApplicationLocales()[0]?.language ?: AppLanguage.English.code - - override val appLanguage: Flow = flowOf(AppLanguage.transform(language)) - - override suspend fun selectLanguage(language: AppLanguage) = withContext(dispatchers.io) { - AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(language.code)) - analytics.logEvent(SelectLanguageEvent(language.toString())) - } -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/model/AppLanguage.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/model/AppLanguage.kt deleted file mode 100644 index ca5bf95f7..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/model/AppLanguage.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.michaelbel.movies.common.localization.model - -import org.michaelbel.movies.common.localization.exceptions.InvalidLocaleException - -sealed class AppLanguage( - val code: String -) { - data object English: AppLanguage("en") - - data object Russian: AppLanguage("ru") - - companion object { - val VALUES = listOf( - English, - Russian - ) - - fun transform(code: String): AppLanguage { - return when (code) { - "en" -> English - "ru" -> Russian - else -> throw InvalidLocaleException - } - } - } -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/usecase/UseCase.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/usecase/UseCase.kt deleted file mode 100644 index c870e25a8..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/usecase/UseCase.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.michaelbel.movies.common.usecase - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext -import org.michaelbel.movies.network.Either -import timber.log.Timber - -@Suppress("unused") -abstract class UseCase( - private val coroutineDispatcher: CoroutineDispatcher -) { - - suspend operator fun invoke(parameters: P): Either { - return try { - withContext(coroutineDispatcher) { - execute(parameters).let { Either.Success(it) } - } - } catch (e: Exception) { - Timber.d(e) - Either.Failure(e) - } - } - - @Throws(RuntimeException::class) - protected abstract suspend fun execute(parameters: P): R -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt deleted file mode 100644 index e9df0e215..000000000 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.michaelbel.movies.common.version - -data class AppVersionData( - val version: String, - val code: Long, - val flavor: String, - val isDebug: Boolean -) { - companion object { - val Empty = AppVersionData( - version = "", - code = 0L, - flavor = "", - isDebug = false - ) - } -} \ No newline at end of file diff --git a/core/interactor/.gitignore b/core/debug-kmp/.gitignore similarity index 100% rename from core/interactor/.gitignore rename to core/debug-kmp/.gitignore diff --git a/core/debug-kmp/build.gradle.kts b/core/debug-kmp/build.gradle.kts new file mode 100644 index 000000000..4c357fd73 --- /dev/null +++ b/core/debug-kmp/build.gradle.kts @@ -0,0 +1,57 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + + sourceSets { + commonMain.dependencies { + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(project(":core:common-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:navigation-kmp")) + implementation(project(":core:ui-kmp")) + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + } +} + +android { + namespace = "org.michaelbel.movies.debug_kmp" + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + buildConfig = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/AndroidManifest.xml b/core/debug-kmp/src/androidMain/AndroidManifest.xml new file mode 100644 index 000000000..b2813102a --- /dev/null +++ b/core/debug-kmp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugActivity.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugActivity.kt new file mode 100644 index 000000000..fdca710a3 --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugActivity.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.debug + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import org.michaelbel.movies.debug.ui.DebugActivityContent + +internal class DebugActivity: ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + DebugActivityContent { statusBarStyle, navigationBarStyle -> + enableEdgeToEdge(statusBarStyle as SystemBarStyle, navigationBarStyle as SystemBarStyle) + } + } + } +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugViewModel.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugViewModel.kt new file mode 100644 index 000000000..5824ab66a --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugViewModel.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.debug + +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.viewmodel.BaseViewModel +import org.michaelbel.movies.interactor.Interactor + +internal class DebugViewModel( + interactor: Interactor +): BaseViewModel() { + + val themeData: StateFlow = interactor.themeData + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = ThemeData.Default + ) +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/di/DebugKoinModule.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/di/DebugKoinModule.kt new file mode 100644 index 000000000..d4fde80d3 --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/di/DebugKoinModule.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.debug.di + +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.dsl.module +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.debug.DebugViewModel + +val debugKoinModule = module { + includes( + interactorKoinModule + ) + viewModelOf(::DebugViewModel) +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/di/DebugNotificationClientKoinModule.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/di/DebugNotificationClientKoinModule.kt new file mode 100644 index 000000000..f7bc7daa1 --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/di/DebugNotificationClientKoinModule.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.debug.di + +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module +import org.michaelbel.movies.debug.notification.DebugNotificationClient + +val debugNotificationClientKoinModule = module { + single { DebugNotificationClient(androidContext()) } +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/notification/DebugNotificationClient.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/notification/DebugNotificationClient.kt new file mode 100644 index 000000000..8b532ec3e --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/notification/DebugNotificationClient.kt @@ -0,0 +1,82 @@ +@file:SuppressLint("MissingPermission") + +package org.michaelbel.movies.debug.notification + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.annotation.StringRes +import androidx.core.app.NotificationChannelCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import org.michaelbel.movies.common.ktx.isPostNotificationsPermissionGranted +import org.michaelbel.movies.common.ktx.notificationManager +import org.michaelbel.movies.debug.DebugActivity +import org.michaelbel.movies.debug_kmp.R +import org.michaelbel.movies.ui.icons.MoviesAndroidIcons + +class DebugNotificationClient( + private val context: Context +) { + + fun showDebugNotification() { + createChannel( + channelId = R.string.notification_debug_channel_id, + channelName = R.string.notification_debug_channel_name, + channelDescription = R.string.notification_debug_channel_description + ) + + val notification = NotificationCompat.Builder( + context, + context.getString(R.string.notification_debug_channel_id) + ).apply { + setContentTitle(context.getString(R.string.notification_debug_title)) + setContentText(context.getString(R.string.notification_debug_description)) + setSmallIcon(MoviesAndroidIcons.MovieFilter24) + setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) + setDefaults(NotificationCompat.DEFAULT_LIGHTS) + setGroupSummary(true) + setGroup(GROUP_NAME) + setContentIntent(pendingIntent()) + setAutoCancel(true) + priority = NotificationCompat.PRIORITY_LOW + setSound(null) + }.build() + + if (context.isPostNotificationsPermissionGranted) { + context.notificationManager.notify(TAG, ID, notification) + } + } + + private fun createChannel( + @StringRes channelId: Int, + @StringRes channelName: Int, + @StringRes channelDescription: Int + ) { + val notificationChannel = NotificationChannelCompat.Builder( + context.getString(channelId), + NotificationManagerCompat.IMPORTANCE_HIGH + ).apply { + setName(context.getString(channelName)) + setDescription(context.getString(channelDescription)) + setShowBadge(true) + }.build() + context.notificationManager.createNotificationChannel(notificationChannel) + } + + private fun pendingIntent(): PendingIntent { + return PendingIntent.getActivity( + context, + ID, + Intent(context, DebugActivity::class.java), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + } + + private companion object { + private const val TAG = "debug notification tag" + private const val ID = 420 + private const val GROUP_NAME = "Debug" + } +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/DebugActivityContent.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/DebugActivityContent.kt new file mode 100644 index 000000000..5bc9b67ae --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/DebugActivityContent.kt @@ -0,0 +1,98 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package org.michaelbel.movies.debug.ui + +import android.app.Activity +import android.content.Intent +import android.provider.Settings +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.common.ktx.appSettingsIntent +import org.michaelbel.movies.debug.DebugViewModel +import org.michaelbel.movies.debug_kmp.R +import org.michaelbel.movies.ui.icons.MoviesAndroidIcons +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun DebugActivityContent( + viewModel: DebugViewModel = koinViewModel(), + enableEdgeToEdge: (Any, Any) -> Unit +) { + val themeData by viewModel.themeData.collectAsStateWithLifecycle() + + val context = LocalContext.current + val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + + val topAppBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + state = rememberTopAppBarState(), + canScroll = { true } + ) + + MoviesTheme( + themeData = themeData, + enableEdgeToEdge = enableEdgeToEdge + ) { + Scaffold( + modifier = Modifier.nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), + topBar = { + DebugToolbar( + modifier = Modifier.fillMaxWidth(), + topAppBarScrollBehavior = topAppBarScrollBehavior, + onNavigationIconClick = { (context as Activity).finish() } + ) + }, + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { innerPadding -> + LazyColumn( + modifier = Modifier.navigationBarsPadding(), + state = rememberLazyListState(), + contentPadding = innerPadding + ) { + item { + SettingItem( + title = stringResource(R.string.debug_app_settings), + description = "", + icon = painterResource(MoviesAndroidIcons.SettingsCinematicBlur24), + onClick = { resultContract.launch(context.appSettingsIntent) } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + item { + SettingItem( + title = stringResource(R.string.debug_developer_settings), + description = "", + icon = painterResource(MoviesAndroidIcons.SettingsAccountBox24), + onClick = { resultContract.launch(Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)) } + ) + } + } + } + } +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/DebugToolbar.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/DebugToolbar.kt new file mode 100644 index 000000000..ee9c69a85 --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/DebugToolbar.kt @@ -0,0 +1,44 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package org.michaelbel.movies.debug.ui + +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import org.michaelbel.movies.debug_kmp.R +import org.michaelbel.movies.ui.compose.iconbutton.CloseIcon +import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets + +@Composable +internal fun DebugToolbar( + modifier: Modifier = Modifier, + topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), + onNavigationIconClick: () -> Unit +) { + LargeTopAppBar( + title = { + Text( + text = stringResource(R.string.debug_title), + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + }, + navigationIcon = { + CloseIcon( + onClick = onNavigationIconClick, + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) + ) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + scrolledContainerColor = MaterialTheme.colorScheme.inversePrimary + ), + scrollBehavior = topAppBarScrollBehavior + ) +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/SettingItem.kt b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/SettingItem.kt new file mode 100644 index 000000000..7b2ed5aa4 --- /dev/null +++ b/core/debug-kmp/src/androidMain/kotlin/org/michaelbel/movies/debug/ui/SettingItem.kt @@ -0,0 +1,59 @@ +package org.michaelbel.movies.debug.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.ui.accessibility.MoviesContentDescription + +@Composable +internal fun SettingItem( + title: String, + description: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() } + .padding(horizontal = 8.dp, vertical = 20.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = icon, + contentDescription = MoviesContentDescription.None, + modifier = Modifier + .padding(start = 8.dp, end = 16.dp) + .size(24.dp), + tint = MaterialTheme.colorScheme.onPrimaryContainer + ) + + Column( + modifier = Modifier.weight(1F) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + if (description.isNotEmpty()) { + Text( + text = description, + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.primary) + ) + } + } + } +} \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/res/values-ru/strings.xml b/core/debug-kmp/src/androidMain/res/values-ru/strings.xml new file mode 100644 index 000000000..7b90f393e --- /dev/null +++ b/core/debug-kmp/src/androidMain/res/values-ru/strings.xml @@ -0,0 +1,10 @@ + + + Debug настройки + Debug настройки + Movies Debug настройки + Тапни чтобы открыть + Debug настройки + Настройки приложения + Настройки разработчика + \ No newline at end of file diff --git a/core/debug-kmp/src/androidMain/res/values/strings.xml b/core/debug-kmp/src/androidMain/res/values/strings.xml new file mode 100644 index 000000000..0097e77a8 --- /dev/null +++ b/core/debug-kmp/src/androidMain/res/values/strings.xml @@ -0,0 +1,11 @@ + + + debug_channel_id + Debug Settings + Debug Settings + Movies Debug Settings + Click to open + Debug Settings + App Settings + Developer Settings + \ No newline at end of file diff --git a/core/navigation/.gitignore b/core/interactor-kmp/.gitignore similarity index 100% rename from core/navigation/.gitignore rename to core/interactor-kmp/.gitignore diff --git a/core/interactor-kmp/build.gradle.kts b/core/interactor-kmp/build.gradle.kts new file mode 100644 index 000000000..e6c78ed83 --- /dev/null +++ b/core/interactor-kmp/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.compose) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:platform-services:interactor-kmp")) + implementation(project(":core:network-kmp")) + api(project(":core:analytics-kmp")) + api(project(":core:common-kmp")) + api(project(":core:persistence-kmp")) + api(project(":core:repository-kmp")) + implementation(compose.runtime) + implementation(compose.runtimeSaveable) + implementation(libs.bundles.kotlinx.coroutines.common) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.interactor_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/AccountInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/AccountInteractor.kt similarity index 87% rename from core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/AccountInteractor.kt rename to core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/AccountInteractor.kt index 8932c6227..400396806 100644 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/AccountInteractor.kt +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/AccountInteractor.kt @@ -1,11 +1,11 @@ package org.michaelbel.movies.interactor import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.persistence.database.entity.AccountDb +import org.michaelbel.movies.persistence.database.entity.AccountPojo interface AccountInteractor { - val account: Flow + val account: Flow suspend fun accountId(): Int? diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/AuthenticationInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/AuthenticationInteractor.kt new file mode 100644 index 000000000..c8a1c2f33 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/AuthenticationInteractor.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.interactor + +import org.michaelbel.movies.interactor.entity.Password +import org.michaelbel.movies.interactor.entity.Username +import org.michaelbel.movies.network.model.Session +import org.michaelbel.movies.network.model.Token + +interface AuthenticationInteractor { + + suspend fun createRequestToken(loginViaTmdb: Boolean): Token + + suspend fun createSessionWithLogin( + username: Username, + password: Password, + requestToken: String + ): Token + + suspend fun createSession(token: String): Session + + suspend fun deleteSession() +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt new file mode 100644 index 000000000..c88adbb88 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.interactor + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.ImagePojo + +interface ImageInteractor { + + fun imagesFlow( + movieId: Int + ): Flow> + + suspend fun images( + movieId: Int + ) +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/Interactor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/Interactor.kt new file mode 100644 index 000000000..73f6de47e --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/Interactor.kt @@ -0,0 +1,19 @@ +package org.michaelbel.movies.interactor + +class Interactor( + accountInteractor: AccountInteractor, + authenticationInteractor: AuthenticationInteractor, + imageInteractor: ImageInteractor, + movieInteractor: MovieInteractor, + notificationInteractor: NotificationInteractor, + searchInteractor: SearchInteractor, + settingsInteractor: SettingsInteractor, + suggestionInteractor: SuggestionInteractor +): AccountInteractor by accountInteractor, + AuthenticationInteractor by authenticationInteractor, + ImageInteractor by imageInteractor, + MovieInteractor by movieInteractor, + NotificationInteractor by notificationInteractor, + SearchInteractor by searchInteractor, + SettingsInteractor by settingsInteractor, + SuggestionInteractor by suggestionInteractor \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt new file mode 100644 index 000000000..13378193b --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt @@ -0,0 +1,60 @@ +package org.michaelbel.movies.interactor + +import androidx.paging.PagingData +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini + +interface MovieInteractor { + + fun moviesPagingData( + movieList: MovieList + ): Flow> + + fun moviesPagingData( + searchQuery: String + ): Flow> + + fun moviesPagingSource( + pagingKey: String + ): PagingSource + + fun moviesFlow( + pagingKey: String, + limit: Int + ): Flow> + + suspend fun movie( + pagingKey: String, + movieId: Int + ): MoviePojo + + suspend fun movieDetails( + pagingKey: String, + movieId: Int + ): MoviePojo + + suspend fun moviesWidget(): List + + suspend fun removeMovies( + pagingKey: String + ) + + suspend fun removeMovie( + pagingKey: String, + movieId: Int + ) + + suspend fun insertMovie( + pagingKey: String, + movie: MoviePojo + ) + + suspend fun updateMovieColors( + movieId: Int, + containerColor: Int, + onContainerColor: Int + ) +} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/NotificationInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/NotificationInteractor.kt similarity index 100% rename from core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/NotificationInteractor.kt rename to core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/NotificationInteractor.kt diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SearchInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SearchInteractor.kt new file mode 100644 index 000000000..712f4083e --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SearchInteractor.kt @@ -0,0 +1,10 @@ +package org.michaelbel.movies.interactor + +import kotlinx.coroutines.flow.StateFlow + +interface SearchInteractor { + + val isSearchActive: StateFlow + + fun setSearchActive(value: Boolean) +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SettingsInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SettingsInteractor.kt new file mode 100644 index 000000000..26fc22aaf --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SettingsInteractor.kt @@ -0,0 +1,50 @@ +package org.michaelbel.movies.interactor + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.theme.AppTheme + +interface SettingsInteractor { + + val currentTheme: Flow + + val currentFeedView: Flow + + val currentMovieList: Flow + + val themeData: Flow + + val isBiometricEnabled: Flow + + suspend fun isBiometricEnabledAsync(): Boolean + + suspend fun selectTheme( + appTheme: AppTheme + ) + + suspend fun selectFeedView( + feedView: FeedView + ) + + suspend fun selectMovieList( + movieList: MovieList + ) + + suspend fun setDynamicColors( + value: Boolean + ) + + suspend fun setPaletteKey( + paletteKey: Int + ) + + suspend fun setSeedColor( + seedColor: Int + ) + + suspend fun setBiometricEnabled( + enabled: Boolean + ) +} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/SuggestionInteractor.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SuggestionInteractor.kt similarity index 78% rename from core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/SuggestionInteractor.kt rename to core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SuggestionInteractor.kt index aa6010ea4..f15c9dc7d 100644 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/SuggestionInteractor.kt +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/SuggestionInteractor.kt @@ -1,11 +1,11 @@ package org.michaelbel.movies.interactor import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.persistence.database.entity.SuggestionDb +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo interface SuggestionInteractor { - fun suggestions(): Flow> + fun suggestions(): Flow> suspend fun updateSuggestions() } \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/di/InteractorKoinModule.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/di/InteractorKoinModule.kt new file mode 100644 index 000000000..1e0bbc256 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/di/InteractorKoinModule.kt @@ -0,0 +1,44 @@ +package org.michaelbel.movies.interactor.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.analytics.di.moviesAnalyticsKoinModule +import org.michaelbel.movies.common.dispatchers.di.dispatchersKoinModule +import org.michaelbel.movies.interactor.AccountInteractor +import org.michaelbel.movies.interactor.AuthenticationInteractor +import org.michaelbel.movies.interactor.ImageInteractor +import org.michaelbel.movies.interactor.Interactor +import org.michaelbel.movies.interactor.MovieInteractor +import org.michaelbel.movies.interactor.NotificationInteractor +import org.michaelbel.movies.interactor.SearchInteractor +import org.michaelbel.movies.interactor.SettingsInteractor +import org.michaelbel.movies.interactor.SuggestionInteractor +import org.michaelbel.movies.interactor.impl.AccountInteractorImpl +import org.michaelbel.movies.interactor.impl.AuthenticationInteractorImpl +import org.michaelbel.movies.interactor.impl.ImageInteractorImpl +import org.michaelbel.movies.interactor.impl.MovieInteractorImpl +import org.michaelbel.movies.interactor.impl.NotificationInteractorImpl +import org.michaelbel.movies.interactor.impl.SearchInteractorImpl +import org.michaelbel.movies.interactor.impl.SettingsInteractorImpl +import org.michaelbel.movies.interactor.impl.SuggestionInteractorImpl +import org.michaelbel.movies.persistence.database.di.moviesDatabaseKoinModule +import org.michaelbel.movies.repository.di.repositoryKoinModule + +val interactorKoinModule = module { + includes( + dispatchersKoinModule, + repositoryKoinModule, + moviesDatabaseKoinModule, + moviesAnalyticsKoinModule + ) + singleOf(::AccountInteractorImpl) { bind() } + singleOf(::AuthenticationInteractorImpl) { bind() } + singleOf(::ImageInteractorImpl) { bind() } + singleOf(::MovieInteractorImpl) { bind() } + singleOf(::NotificationInteractorImpl) { bind() } + singleOf(::SearchInteractorImpl) { bind() } + singleOf(::SettingsInteractorImpl) { bind() } + singleOf(::SuggestionInteractorImpl) { bind() } + single { Interactor(get(), get(), get(), get(), get(), get(), get(), get()) } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/entity/Password.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/entity/Password.kt new file mode 100644 index 000000000..983062fec --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/entity/Password.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.interactor.entity + +@JvmInline +value class Password( + val value: String +) \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/entity/Username.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/entity/Username.kt new file mode 100644 index 000000000..8fd18d208 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/entity/Username.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.interactor.entity + +@JvmInline +value class Username( + val value: String +) \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/AccountInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/AccountInteractorImpl.kt new file mode 100644 index 000000000..c154622df --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/AccountInteractorImpl.kt @@ -0,0 +1,28 @@ +package org.michaelbel.movies.interactor.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.interactor.AccountInteractor +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import org.michaelbel.movies.repository.AccountRepository + +internal class AccountInteractorImpl( + private val dispatchers: MoviesDispatchers, + private val accountRepository: AccountRepository +): AccountInteractor { + + override val account: Flow = accountRepository.account + + override suspend fun accountId(): Int { + return withContext(dispatchers.io) { accountRepository.accountId() } + } + + override suspend fun accountExpireTime(): Long? { + return withContext(dispatchers.io) { accountRepository.accountExpireTime() } + } + + override suspend fun accountDetails() { + return withContext(dispatchers.io) { accountRepository.accountDetails() } + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/AuthenticationInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/AuthenticationInteractorImpl.kt new file mode 100644 index 000000000..ef17064c8 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/AuthenticationInteractorImpl.kt @@ -0,0 +1,38 @@ +package org.michaelbel.movies.interactor.impl + +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.interactor.AuthenticationInteractor +import org.michaelbel.movies.interactor.entity.Password +import org.michaelbel.movies.interactor.entity.Username +import org.michaelbel.movies.network.model.Session +import org.michaelbel.movies.network.model.Token +import org.michaelbel.movies.repository.AuthenticationRepository + +internal class AuthenticationInteractorImpl( + private val dispatchers: MoviesDispatchers, + private val authenticationRepository: AuthenticationRepository +): AuthenticationInteractor { + + override suspend fun createRequestToken(loginViaTmdb: Boolean): Token { + return withContext(dispatchers.io) { authenticationRepository.createRequestToken(loginViaTmdb) } + } + + override suspend fun createSessionWithLogin( + username: Username, + password: Password, + requestToken: String + ): Token { + return withContext(dispatchers.io) { + authenticationRepository.createSessionWithLogin(username.value, password.value, requestToken) + } + } + + override suspend fun createSession(token: String): Session { + return withContext(dispatchers.io) { authenticationRepository.createSession(token) } + } + + override suspend fun deleteSession() { + return withContext(dispatchers.io) { authenticationRepository.deleteSession() } + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/ImageInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/ImageInteractorImpl.kt new file mode 100644 index 000000000..2bb6e23c8 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/ImageInteractorImpl.kt @@ -0,0 +1,22 @@ +package org.michaelbel.movies.interactor.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.interactor.ImageInteractor +import org.michaelbel.movies.persistence.database.entity.ImagePojo +import org.michaelbel.movies.repository.ImageRepository + +internal class ImageInteractorImpl( + private val dispatchers: MoviesDispatchers, + private val imageRepository: ImageRepository +): ImageInteractor { + + override fun imagesFlow(movieId: Int): Flow> { + return imageRepository.imagesFlow(movieId) + } + + override suspend fun images(movieId: Int) { + return withContext(dispatchers.io) { imageRepository.images(movieId) } + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/MovieInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/MovieInteractorImpl.kt new file mode 100644 index 000000000..ad794cfe9 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/MovieInteractorImpl.kt @@ -0,0 +1,104 @@ +@file:OptIn(ExperimentalPagingApi::class) + +package org.michaelbel.movies.interactor.impl + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.interactor.MovieInteractor +import org.michaelbel.movies.interactor.ktx.nameOrLocalList +import org.michaelbel.movies.interactor.remote.FeedMoviesRemoteMediator +import org.michaelbel.movies.interactor.remote.SearchMoviesRemoteMediator +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.persistence.database.MoviesDatabase +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini +import org.michaelbel.movies.repository.MovieRepository +import org.michaelbel.movies.repository.PagingKeyRepository +import org.michaelbel.movies.repository.SearchRepository + +internal class MovieInteractorImpl( + private val dispatchers: MoviesDispatchers, + private val searchRepository: SearchRepository, + private val movieRepository: MovieRepository, + private val pagingKeyRepository: PagingKeyRepository, + private val moviesDatabase: MoviesDatabase +): MovieInteractor { + + override fun moviesPagingData(movieList: MovieList): Flow> { + return Pager( + config = PagingConfig( + pageSize = MovieResponse.DEFAULT_PAGE_SIZE, + enablePlaceholders = true + ), + remoteMediator = FeedMoviesRemoteMediator( + movieRepository = movieRepository, + pagingKeyRepository = pagingKeyRepository, + moviesDatabase = moviesDatabase, + movieList = MovieList.name(movieList) + ), + pagingSourceFactory = { movieRepository.moviesPagingSource(movieList.nameOrLocalList) } + ).flow + } + + override fun moviesPagingData(searchQuery: String): Flow> { + return Pager( + config = PagingConfig( + pageSize = MovieResponse.DEFAULT_PAGE_SIZE, + enablePlaceholders = true + ), + remoteMediator = SearchMoviesRemoteMediator( + pagingKeyRepository = pagingKeyRepository, + searchRepository = searchRepository, + movieRepository = movieRepository, + moviesDatabase = moviesDatabase, + query = searchQuery + ), + pagingSourceFactory = { movieRepository.moviesPagingSource(searchQuery) } + ).flow + } + + override fun moviesPagingSource(pagingKey: String): PagingSource { + return movieRepository.moviesPagingSource(pagingKey) + } + + override fun moviesFlow(pagingKey: String, limit: Int): Flow> { + return movieRepository.moviesFlow(pagingKey, limit) + } + + override suspend fun moviesWidget(): List { + return withContext(dispatchers.io) { movieRepository.moviesWidget() } + } + + override suspend fun movie(pagingKey: String, movieId: Int): MoviePojo { + return withContext(dispatchers.io) { movieRepository.movie(pagingKey, movieId) } + } + + override suspend fun movieDetails(pagingKey: String, movieId: Int): MoviePojo { + return withContext(dispatchers.io) { movieRepository.movieDetails(pagingKey, movieId) } + } + + override suspend fun removeMovies(pagingKey: String) { + return withContext(dispatchers.io) { movieRepository.removeMovies(pagingKey) } + } + + override suspend fun removeMovie(pagingKey: String, movieId: Int) { + return withContext(dispatchers.io) { movieRepository.removeMovie(pagingKey, movieId) } + } + + override suspend fun insertMovie(pagingKey: String, movie: MoviePojo) { + return withContext(dispatchers.io) { movieRepository.insertMovie(pagingKey, movie) } + } + + override suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) { + return withContext(dispatchers.io) { + movieRepository.updateMovieColors(movieId, containerColor, onContainerColor) + } + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/NotificationInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/NotificationInteractorImpl.kt new file mode 100644 index 000000000..727254da7 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/NotificationInteractorImpl.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.interactor.impl + +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.interactor.NotificationInteractor +import org.michaelbel.movies.repository.NotificationRepository + +internal class NotificationInteractorImpl( + private val dispatchers: MoviesDispatchers, + private val notificationRepository: NotificationRepository +): NotificationInteractor { + + override suspend fun notificationExpireTime(): Long { + return withContext(dispatchers.io) { notificationRepository.notificationExpireTime() } + } + + override suspend fun updateNotificationExpireTime() { + withContext(dispatchers.io) { notificationRepository.updateNotificationExpireTime() } + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SearchInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SearchInteractorImpl.kt new file mode 100644 index 000000000..9a1672c9a --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SearchInteractorImpl.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.interactor.impl + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.michaelbel.movies.interactor.SearchInteractor + +internal class SearchInteractorImpl: SearchInteractor { + + private val isActiveMutableFlow: MutableStateFlow = MutableStateFlow(true) + + override val isSearchActive: StateFlow + get() = isActiveMutableFlow.asStateFlow() + + override fun setSearchActive(value: Boolean) { + isActiveMutableFlow.value = value + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SettingsInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SettingsInteractorImpl.kt new file mode 100644 index 000000000..0dc32776a --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SettingsInteractorImpl.kt @@ -0,0 +1,83 @@ +package org.michaelbel.movies.interactor.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import org.michaelbel.movies.analytics.MoviesAnalytics +import org.michaelbel.movies.analytics.event.ChangeDynamicColorsEvent +import org.michaelbel.movies.analytics.event.SelectFeedViewEvent +import org.michaelbel.movies.analytics.event.SelectMovieListEvent +import org.michaelbel.movies.analytics.event.SelectThemeEvent +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.interactor.SettingsInteractor +import org.michaelbel.movies.repository.SettingsRepository + +internal class SettingsInteractorImpl( + private val dispatchers: MoviesDispatchers, + private val settingsRepository: SettingsRepository, + private val analytics: MoviesAnalytics +): SettingsInteractor { + + override val currentTheme: Flow = settingsRepository.currentTheme + + override val currentFeedView: Flow = settingsRepository.currentFeedView + + override val currentMovieList: Flow = settingsRepository.currentMovieList + + override val themeData: Flow = settingsRepository.themeData + + override val isBiometricEnabled: Flow = settingsRepository.isBiometricEnabled + + override suspend fun isBiometricEnabledAsync(): Boolean { + return settingsRepository.isBiometricEnabledAsync() + } + + override suspend fun selectTheme(appTheme: AppTheme) { + withContext(dispatchers.main) { + settingsRepository.selectTheme(appTheme) + analytics.logEvent(SelectThemeEvent(appTheme.toString())) + } + } + + override suspend fun selectFeedView(feedView: FeedView) { + withContext(dispatchers.main) { + settingsRepository.selectFeedView(feedView) + analytics.logEvent(SelectFeedViewEvent(feedView.toString())) + } + } + + override suspend fun selectMovieList(movieList: MovieList) { + withContext(dispatchers.main) { + settingsRepository.selectMovieList(movieList) + analytics.logEvent(SelectMovieListEvent(movieList.toString())) + } + } + + override suspend fun setDynamicColors(value: Boolean) { + withContext(dispatchers.main) { + settingsRepository.setDynamicColors(value) + analytics.logEvent(ChangeDynamicColorsEvent(value)) + } + } + + override suspend fun setPaletteKey(paletteKey: Int) { + withContext(dispatchers.main) { + settingsRepository.setPaletteKey(paletteKey) + } + } + + override suspend fun setSeedColor(seedColor: Int) { + withContext(dispatchers.main) { + settingsRepository.setSeedColor(seedColor) + } + } + + override suspend fun setBiometricEnabled(enabled: Boolean) { + withContext(dispatchers.main) { + settingsRepository.setBiometricEnabled(enabled) + } + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SuggestionInteractorImpl.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SuggestionInteractorImpl.kt new file mode 100644 index 000000000..a74d61b94 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/impl/SuggestionInteractorImpl.kt @@ -0,0 +1,22 @@ +package org.michaelbel.movies.interactor.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.interactor.SuggestionInteractor +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo +import org.michaelbel.movies.repository.SuggestionRepository + +internal class SuggestionInteractorImpl( + private val dispatchers: MoviesDispatchers, + private val suggestionRepository: SuggestionRepository +): SuggestionInteractor { + + override fun suggestions(): Flow> { + return suggestionRepository.suggestions() + } + + override suspend fun updateSuggestions() { + withContext(dispatchers.io) { suggestionRepository.updateSuggestions() } + } +} \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/MovieListKtx.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/MovieListKtx.kt new file mode 100644 index 000000000..45c0b8c0e --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/MovieListKtx.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.interactor.ktx + +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty +import org.michaelbel.movies.persistence.database.entity.MoviePojo + +val MovieList.nameOrLocalList: String + get() = if (isTmdbApiKeyEmpty) MoviePojo.MOVIES_LOCAL_LIST else MovieList.name(this) \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/PasswordKtx.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/PasswordKtx.kt new file mode 100644 index 000000000..d00584dcb --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/PasswordKtx.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.interactor.ktx + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import org.michaelbel.movies.interactor.entity.Password + +val Password.isNotEmpty: Boolean + get() = value.isNotEmpty() + +val Password.trim: Password + get() = Password(value.trim()) + +val PasswordSaver: Saver, String> + get() = Saver( + save = { it.value.value }, + restore = { mutableStateOf(Password(it)) } + ) \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/UsernameKtx.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/UsernameKtx.kt new file mode 100644 index 000000000..66c1f3377 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/ktx/UsernameKtx.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.interactor.ktx + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import org.michaelbel.movies.interactor.entity.Username + +val Username.isNotEmpty: Boolean + get() = value.isNotEmpty() + +val Username.trim: Username + get() = Username(value.trim()) + +val UsernameSaver: Saver, String> + get() = Saver( + save = { it.value.value }, + restore = { mutableStateOf(Username(it)) } + ) \ No newline at end of file diff --git a/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/remote/FeedMoviesRemoteMediator.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/remote/FeedMoviesRemoteMediator.kt new file mode 100644 index 000000000..28d2714d7 --- /dev/null +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/remote/FeedMoviesRemoteMediator.kt @@ -0,0 +1,57 @@ +@file:OptIn(ExperimentalPagingApi::class) + +package org.michaelbel.movies.interactor.remote + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import org.michaelbel.movies.common.exceptions.PageEmptyException +import org.michaelbel.movies.network.ktx.isEmpty +import org.michaelbel.movies.network.ktx.isPaginationReached +import org.michaelbel.movies.network.ktx.nextPage +import org.michaelbel.movies.persistence.database.MoviesDatabase +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.repository.MovieRepository +import org.michaelbel.movies.repository.PagingKeyRepository + +class FeedMoviesRemoteMediator( + private val pagingKeyRepository: PagingKeyRepository, + private val movieRepository: MovieRepository, + private val moviesDatabase: MoviesDatabase, + private val movieList: String +): RemoteMediator() { + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + return try { + val loadKey = when (loadType) { + LoadType.REFRESH -> 1 + LoadType.PREPEND -> pagingKeyRepository.prevPage(movieList) + LoadType.APPEND -> pagingKeyRepository.page(movieList) + } ?: return MediatorResult.Success(endOfPaginationReached = true) + + val moviesResult = movieRepository.moviesResult(movieList, loadKey) + + moviesDatabase.withTransaction { + if (loadType == LoadType.REFRESH) { + pagingKeyRepository.removePagingKey(movieList) + movieRepository.removeMovies(movieList) + } + + if (moviesResult.isEmpty) { + throw PageEmptyException + } + + pagingKeyRepository.insertPagingKey(movieList, moviesResult.nextPage, moviesResult.totalPages) + movieRepository.insertMovies(movieList, moviesResult.page, moviesResult.results) + } + + MediatorResult.Success(endOfPaginationReached = moviesResult.isPaginationReached) + } catch (e: Exception) { + MediatorResult.Error(e) + } + } +} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/remote/SearchMoviesRemoteMediator.kt b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/remote/SearchMoviesRemoteMediator.kt similarity index 75% rename from core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/remote/SearchMoviesRemoteMediator.kt rename to core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/remote/SearchMoviesRemoteMediator.kt index be9e8102b..8bb6e8c83 100644 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/remote/SearchMoviesRemoteMediator.kt +++ b/core/interactor-kmp/src/commonMain/kotlin/org/michaelbel/movies/interactor/remote/SearchMoviesRemoteMediator.kt @@ -1,17 +1,17 @@ +@file:OptIn(ExperimentalPagingApi::class) + package org.michaelbel.movies.interactor.remote +import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator -import androidx.room.withTransaction import org.michaelbel.movies.common.exceptions.PageEmptyException import org.michaelbel.movies.network.ktx.isEmpty import org.michaelbel.movies.network.ktx.isPaginationReached import org.michaelbel.movies.network.ktx.nextPage -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result -import org.michaelbel.movies.persistence.database.AppDatabase -import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.MoviesDatabase +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.repository.MovieRepository import org.michaelbel.movies.repository.PagingKeyRepository import org.michaelbel.movies.repository.SearchRepository @@ -20,16 +20,16 @@ class SearchMoviesRemoteMediator( private val pagingKeyRepository: PagingKeyRepository, private val movieRepository: MovieRepository, private val searchRepository: SearchRepository, - private val database: AppDatabase, + private val moviesDatabase: MoviesDatabase, private val query: String -): RemoteMediator() { +): RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState + state: PagingState ): MediatorResult { return try { - val loadKey: Int = when (loadType) { + val loadKey = when (loadType) { LoadType.REFRESH -> 1 LoadType.PREPEND -> pagingKeyRepository.prevPage(query) LoadType.APPEND -> pagingKeyRepository.page(query) @@ -39,12 +39,9 @@ class SearchMoviesRemoteMediator( throw PageEmptyException } - val moviesResult: Result = searchRepository.searchMoviesResult( - query = query, - page = loadKey - ) + val moviesResult = searchRepository.searchMoviesResult(query, loadKey) - database.withTransaction { + moviesDatabase.withTransaction { if (loadType == LoadType.REFRESH) { pagingKeyRepository.removePagingKey(query) movieRepository.removeMovies(query) diff --git a/core/interactor/build.gradle.kts b/core/interactor/build.gradle.kts deleted file mode 100644 index 8ddb7be4a..000000000 --- a/core/interactor/build.gradle.kts +++ /dev/null @@ -1,51 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.interactor" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=androidx.paging.ExperimentalPagingApi" - ) - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:platform-services:interactor")) - implementation(project(":core:network")) - api(project(":core:analytics")) - api(project(":core:common")) - api(project(":core:persistence")) - api(project(":core:repository")) -} \ No newline at end of file diff --git a/core/interactor/src/main/AndroidManifest.xml b/core/interactor/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/interactor/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/AuthenticationInteractor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/AuthenticationInteractor.kt deleted file mode 100644 index 77e161f59..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/AuthenticationInteractor.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.michaelbel.movies.interactor - -import org.michaelbel.movies.network.model.Session -import org.michaelbel.movies.network.model.Token - -interface AuthenticationInteractor { - - suspend fun createRequestToken(loginViaTmdb: Boolean): Token - - suspend fun createSessionWithLogin( - username: String, - password: String, - requestToken: String - ): Token - - suspend fun createSession(token: String): Session - - suspend fun deleteSession() -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt deleted file mode 100644 index 104a26771..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.michaelbel.movies.interactor - -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.persistence.database.entity.ImageDb - -interface ImageInteractor { - - fun imagesFlow(movieId: Int): Flow> - - suspend fun images(movieId: Int) -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/Interactor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/Interactor.kt deleted file mode 100644 index 3b31c6b84..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/Interactor.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.michaelbel.movies.interactor - -import javax.inject.Inject - -class Interactor @Inject constructor( - accountInteractor: AccountInteractor, - authenticationInteractor: AuthenticationInteractor, - imageInteractor: ImageInteractor, - movieInteractor: MovieInteractor, - notificationInteractor: NotificationInteractor, - settingsInteractor: SettingsInteractor, - suggestionInteractor: SuggestionInteractor -): AccountInteractor by accountInteractor, - AuthenticationInteractor by authenticationInteractor, - ImageInteractor by imageInteractor, - MovieInteractor by movieInteractor, - NotificationInteractor by notificationInteractor, - SettingsInteractor by settingsInteractor, - SuggestionInteractor by suggestionInteractor \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt deleted file mode 100644 index 46ac9b921..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.michaelbel.movies.interactor - -import androidx.paging.PagingData -import androidx.paging.PagingSource -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.persistence.database.entity.MovieDb - -interface MovieInteractor { - - fun moviesPagingData(movieList: MovieList): Flow> - - fun moviesPagingData(searchQuery: String): Flow> - - fun moviesPagingSource(pagingKey: String): PagingSource - - fun moviesFlow(pagingKey: String, limit: Int): Flow> - - suspend fun movie(pagingKey: String, movieId: Int): MovieDb - - suspend fun movieDetails(pagingKey: String, movieId: Int): MovieDb - - suspend fun removeMovies(pagingKey: String) - - suspend fun removeMovie(pagingKey: String, movieId: Int) - - suspend fun insertMovie(pagingKey: String, movie: MovieDb) - - suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/SettingsInteractor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/SettingsInteractor.kt deleted file mode 100644 index 57e941ab5..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/SettingsInteractor.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.michaelbel.movies.interactor - -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.version.AppVersionData - -interface SettingsInteractor { - - val currentTheme: Flow - - val currentFeedView: Flow - - val currentMovieList: Flow - - val dynamicColors: Flow - - val isSettingsIconVisible: Flow - - val isPlayServicesAvailable: Flow - - val appVersionData: Flow - - suspend fun selectTheme(appTheme: AppTheme) - - suspend fun selectFeedView(feedView: FeedView) - - suspend fun selectMovieList(movieList: MovieList) - - suspend fun setDynamicColors(value: Boolean) - - suspend fun fetchRemoteConfig() -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/di/InteractorModule.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/di/InteractorModule.kt deleted file mode 100644 index 9f9695fee..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/di/InteractorModule.kt +++ /dev/null @@ -1,68 +0,0 @@ -package org.michaelbel.movies.interactor.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import org.michaelbel.movies.interactor.AccountInteractor -import org.michaelbel.movies.interactor.AuthenticationInteractor -import org.michaelbel.movies.interactor.ImageInteractor -import org.michaelbel.movies.interactor.MovieInteractor -import org.michaelbel.movies.interactor.NotificationInteractor -import org.michaelbel.movies.interactor.SettingsInteractor -import org.michaelbel.movies.interactor.SuggestionInteractor -import org.michaelbel.movies.interactor.impl.AccountInteractorImpl -import org.michaelbel.movies.interactor.impl.AuthenticationInteractorImpl -import org.michaelbel.movies.interactor.impl.ImageInteractorImpl -import org.michaelbel.movies.interactor.impl.MovieInteractorImpl -import org.michaelbel.movies.interactor.impl.NotificationInteractorImpl -import org.michaelbel.movies.interactor.impl.SettingsInteractorImpl -import org.michaelbel.movies.interactor.impl.SuggestionInteractorImpl - -@Module -@InstallIn(SingletonComponent::class) -internal interface InteractorModule { - - @Binds - @Singleton - fun provideAccountInteractor( - interactor: AccountInteractorImpl - ): AccountInteractor - - @Binds - @Singleton - fun provideAuthenticationInteractor( - interactor: AuthenticationInteractorImpl - ): AuthenticationInteractor - - @Binds - @Singleton - fun provideImageInteractor( - interactor: ImageInteractorImpl - ): ImageInteractor - - @Binds - @Singleton - fun provideMovieInteractor( - interactor: MovieInteractorImpl - ): MovieInteractor - - @Binds - @Singleton - fun provideNotificationInteractor( - interactor: NotificationInteractorImpl - ): NotificationInteractor - - @Binds - @Singleton - fun provideSettingsInteractor( - interactor: SettingsInteractorImpl - ): SettingsInteractor - - @Binds - @Singleton - fun provideSuggestionInteractor( - interactor: SuggestionInteractorImpl - ): SuggestionInteractor -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/AccountInteractorImpl.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/AccountInteractorImpl.kt deleted file mode 100644 index 41f21fa6f..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/AccountInteractorImpl.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.michaelbel.movies.interactor.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.interactor.AccountInteractor -import org.michaelbel.movies.persistence.database.entity.AccountDb -import org.michaelbel.movies.repository.AccountRepository - -@Singleton -internal class AccountInteractorImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val accountRepository: AccountRepository -): AccountInteractor { - - override val account: Flow = accountRepository.account - - override suspend fun accountId(): Int? { - return withContext(dispatchers.io) { - accountRepository.accountId() - } - } - - override suspend fun accountExpireTime(): Long? { - return withContext(dispatchers.io) { - accountRepository.accountExpireTime() - } - } - - override suspend fun accountDetails() { - return withContext(dispatchers.io) { - accountRepository.accountDetails() - } - } -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/AuthenticationInteractorImpl.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/AuthenticationInteractorImpl.kt deleted file mode 100644 index 6bbc7d5e8..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/AuthenticationInteractorImpl.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.michaelbel.movies.interactor.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.interactor.AuthenticationInteractor -import org.michaelbel.movies.network.model.Session -import org.michaelbel.movies.network.model.Token -import org.michaelbel.movies.repository.AuthenticationRepository - -@Singleton -internal class AuthenticationInteractorImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val authenticationRepository: AuthenticationRepository -): AuthenticationInteractor { - - override suspend fun createRequestToken(loginViaTmdb: Boolean): Token { - return withContext(dispatchers.io) { authenticationRepository.createRequestToken(loginViaTmdb) } - } - - override suspend fun createSessionWithLogin( - username: String, - password: String, - requestToken: String - ): Token { - return withContext(dispatchers.io) { - authenticationRepository.createSessionWithLogin(username, password, requestToken) - } - } - - override suspend fun createSession(token: String): Session { - return withContext(dispatchers.io) { - authenticationRepository.createSession(token) - } - } - - override suspend fun deleteSession() { - return withContext(dispatchers.io) { - authenticationRepository.deleteSession() - } - } -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/ImageInteractorImpl.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/ImageInteractorImpl.kt deleted file mode 100644 index 538c497a5..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/ImageInteractorImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.michaelbel.movies.interactor.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.interactor.ImageInteractor -import org.michaelbel.movies.persistence.database.entity.ImageDb -import org.michaelbel.movies.repository.ImageRepository - -@Singleton -internal class ImageInteractorImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val imageRepository: ImageRepository -): ImageInteractor { - - override fun imagesFlow(movieId: Int): Flow> { - return imageRepository.imagesFlow(movieId) - } - - override suspend fun images(movieId: Int) { - return withContext(dispatchers.io) { - imageRepository.images(movieId) - } - } -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/MovieInteractorImpl.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/MovieInteractorImpl.kt deleted file mode 100644 index 4a8218100..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/MovieInteractorImpl.kt +++ /dev/null @@ -1,112 +0,0 @@ -package org.michaelbel.movies.interactor.impl - -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import androidx.paging.PagingSource -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.interactor.MovieInteractor -import org.michaelbel.movies.interactor.ktx.nameOrLocalList -import org.michaelbel.movies.interactor.remote.FeedMoviesRemoteMediator -import org.michaelbel.movies.interactor.remote.SearchMoviesRemoteMediator -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.persistence.database.AppDatabase -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.repository.MovieRepository -import org.michaelbel.movies.repository.PagingKeyRepository -import org.michaelbel.movies.repository.SearchRepository - -@Singleton -internal class MovieInteractorImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val searchRepository: SearchRepository, - private val movieRepository: MovieRepository, - private val pagingKeyRepository: PagingKeyRepository, - private val database: AppDatabase -): MovieInteractor { - - override fun moviesPagingData(movieList: MovieList): Flow> { - return Pager( - config = PagingConfig( - pageSize = MovieResponse.DEFAULT_PAGE_SIZE, - enablePlaceholders = true - ), - remoteMediator = FeedMoviesRemoteMediator( - movieRepository = movieRepository, - pagingKeyRepository = pagingKeyRepository, - database = database, - movieList = movieList.name - ), - pagingSourceFactory = { movieRepository.moviesPagingSource(movieList.nameOrLocalList) } - ).flow - } - - override fun moviesPagingData(searchQuery: String): Flow> { - return Pager( - config = PagingConfig( - pageSize = MovieResponse.DEFAULT_PAGE_SIZE, - enablePlaceholders = true - ), - remoteMediator = SearchMoviesRemoteMediator( - pagingKeyRepository = pagingKeyRepository, - searchRepository = searchRepository, - movieRepository = movieRepository, - database = database, - query = searchQuery - ), - pagingSourceFactory = { movieRepository.moviesPagingSource(searchQuery) } - ).flow - } - - override fun moviesPagingSource(pagingKey: String): PagingSource { - return movieRepository.moviesPagingSource(pagingKey) - } - - override fun moviesFlow(pagingKey: String, limit: Int): Flow> { - return movieRepository.moviesFlow( - pagingKey = pagingKey, - limit = limit - ) - } - - override suspend fun movie(pagingKey: String, movieId: Int): MovieDb { - return withContext(dispatchers.io) { - movieRepository.movie(pagingKey, movieId) - } - } - - override suspend fun movieDetails(pagingKey: String, movieId: Int): MovieDb { - return withContext(dispatchers.io) { - movieRepository.movieDetails(pagingKey, movieId) - } - } - - override suspend fun removeMovies(pagingKey: String) { - return withContext(dispatchers.io) { - movieRepository.removeMovies(pagingKey) - } - } - - override suspend fun removeMovie(pagingKey: String, movieId: Int) { - return withContext(dispatchers.io) { - movieRepository.removeMovie(pagingKey, movieId) - } - } - - override suspend fun insertMovie(pagingKey: String, movie: MovieDb) { - return withContext(dispatchers.io) { - movieRepository.insertMovie(pagingKey, movie) - } - } - - override suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) { - return withContext(dispatchers.io) { - movieRepository.updateMovieColors(movieId, containerColor, onContainerColor) - } - } -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/NotificationInteractorImpl.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/NotificationInteractorImpl.kt deleted file mode 100644 index a5e56c649..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/NotificationInteractorImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.michaelbel.movies.interactor.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.interactor.NotificationInteractor -import org.michaelbel.movies.repository.NotificationRepository - -@Singleton -internal class NotificationInteractorImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val notificationRepository: NotificationRepository -): NotificationInteractor { - - override suspend fun notificationExpireTime(): Long { - return withContext(dispatchers.io) { - notificationRepository.notificationExpireTime() - } - } - - override suspend fun updateNotificationExpireTime() { - withContext(dispatchers.io) { - notificationRepository.updateNotificationExpireTime() - } - } -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/SettingsInteractorImpl.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/SettingsInteractorImpl.kt deleted file mode 100644 index a6fe87a10..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/SettingsInteractorImpl.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.michaelbel.movies.interactor.impl - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.withContext -import org.michaelbel.movies.analytics.MoviesAnalytics -import org.michaelbel.movies.analytics.event.ChangeDynamicColorsEvent -import org.michaelbel.movies.analytics.event.SelectFeedViewEvent -import org.michaelbel.movies.analytics.event.SelectMovieListEvent -import org.michaelbel.movies.analytics.event.SelectThemeEvent -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.config.RemoteParams -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.version.AppVersionData -import org.michaelbel.movies.interactor.SettingsInteractor -import org.michaelbel.movies.platform.app.AppService -import org.michaelbel.movies.platform.config.ConfigService -import org.michaelbel.movies.repository.SettingsRepository -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -internal class SettingsInteractorImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val settingsRepository: SettingsRepository, - private val configService: ConfigService, - private val analytics: MoviesAnalytics, - appService: AppService -): SettingsInteractor { - - override val currentTheme: Flow = settingsRepository.currentTheme - - override val currentFeedView: Flow = settingsRepository.currentFeedView - - override val currentMovieList: Flow = settingsRepository.currentMovieList - - override val dynamicColors: Flow = settingsRepository.dynamicColors - - override val isSettingsIconVisible: Flow = configService.getBooleanFlow(RemoteParams.PARAM_SETTINGS_ICON_VISIBLE) - - override val isPlayServicesAvailable: Flow = flowOf(appService.isPlayServicesAvailable) - - override val appVersionData: Flow = settingsRepository.appVersionData - - override suspend fun selectTheme(appTheme: AppTheme) = withContext(dispatchers.main) { - settingsRepository.selectTheme(appTheme) - analytics.logEvent(SelectThemeEvent(appTheme.toString())) - } - - override suspend fun selectFeedView(feedView: FeedView) = withContext(dispatchers.main) { - settingsRepository.selectFeedView(feedView) - analytics.logEvent(SelectFeedViewEvent(feedView.toString())) - } - - override suspend fun selectMovieList(movieList: MovieList) = withContext(dispatchers.main) { - settingsRepository.selectMovieList(movieList) - analytics.logEvent(SelectMovieListEvent(movieList.toString())) - } - - override suspend fun setDynamicColors(value: Boolean) = withContext(dispatchers.main) { - settingsRepository.setDynamicColors(value) - analytics.logEvent(ChangeDynamicColorsEvent(value)) - } - - override suspend fun fetchRemoteConfig() { - withContext(dispatchers.main) { - configService.fetchAndActivate() - } - } -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/SuggestionInteractorImpl.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/SuggestionInteractorImpl.kt deleted file mode 100644 index 43d325944..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/impl/SuggestionInteractorImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.michaelbel.movies.interactor.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.interactor.SuggestionInteractor -import org.michaelbel.movies.persistence.database.entity.SuggestionDb -import org.michaelbel.movies.repository.SuggestionRepository - -@Singleton -internal class SuggestionInteractorImpl @Inject constructor( - private val dispatchers: MoviesDispatchers, - private val suggestionRepository: SuggestionRepository -): SuggestionInteractor { - - override fun suggestions(): Flow> { - return suggestionRepository.suggestions() - } - - override suspend fun updateSuggestions() { - withContext(dispatchers.io) { - suggestionRepository.updateSuggestions() - } - } -} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ktx/MovieListKtx.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ktx/MovieListKtx.kt deleted file mode 100644 index e593b3f59..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ktx/MovieListKtx.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.michaelbel.movies.interactor.ktx - -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.network.isTmdbApiKeyEmpty -import org.michaelbel.movies.persistence.database.entity.MovieDb - -val MovieList.nameOrLocalList: String - get() = if (isTmdbApiKeyEmpty) MovieDb.MOVIES_LOCAL_LIST else name \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/remote/FeedMoviesRemoteMediator.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/remote/FeedMoviesRemoteMediator.kt deleted file mode 100644 index 5c66435b8..000000000 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/remote/FeedMoviesRemoteMediator.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.michaelbel.movies.interactor.remote - -import androidx.paging.LoadType -import androidx.paging.PagingState -import androidx.paging.RemoteMediator -import androidx.room.withTransaction -import org.michaelbel.movies.common.exceptions.PageEmptyException -import org.michaelbel.movies.network.ktx.isEmpty -import org.michaelbel.movies.network.ktx.isPaginationReached -import org.michaelbel.movies.network.ktx.nextPage -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result -import org.michaelbel.movies.persistence.database.AppDatabase -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.repository.MovieRepository -import org.michaelbel.movies.repository.PagingKeyRepository - -class FeedMoviesRemoteMediator( - private val pagingKeyRepository: PagingKeyRepository, - private val movieRepository: MovieRepository, - private val database: AppDatabase, - private val movieList: String -): RemoteMediator() { - - override suspend fun load( - loadType: LoadType, - state: PagingState - ): MediatorResult { - return try { - val loadKey: Int = when (loadType) { - LoadType.REFRESH -> 1 - LoadType.PREPEND -> pagingKeyRepository.prevPage(movieList) - LoadType.APPEND -> pagingKeyRepository.page(movieList) - } ?: return MediatorResult.Success(endOfPaginationReached = true) - - val moviesResult: Result = movieRepository.moviesResult( - movieList = movieList, - page = loadKey - ) - - database.withTransaction { - if (loadType == LoadType.REFRESH) { - pagingKeyRepository.removePagingKey(movieList) - movieRepository.removeMovies(movieList) - } - - if (moviesResult.isEmpty) { - throw PageEmptyException - } - - pagingKeyRepository.insertPagingKey(movieList, moviesResult.nextPage, moviesResult.totalPages) - movieRepository.insertMovies(movieList, moviesResult.page, moviesResult.results) - } - - MediatorResult.Success(endOfPaginationReached = moviesResult.isPaginationReached) - } catch (e: Exception) { - MediatorResult.Error(e) - } - } -} \ No newline at end of file diff --git a/core/network/.gitignore b/core/navigation-kmp/.gitignore similarity index 100% rename from core/network/.gitignore rename to core/navigation-kmp/.gitignore diff --git a/core/navigation-kmp/build.gradle.kts b/core/navigation-kmp/build.gradle.kts new file mode 100644 index 000000000..3c44e83f8 --- /dev/null +++ b/core/navigation-kmp/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + androidMain.dependencies { + api(libs.androidx.navigation.compose) + } + } +} + +android { + namespace = "org.michaelbel.movies.navigation_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/navigation/src/main/kotlin/org/michaelbel/movies/navigation/ktx/NavHostControllerKtx.kt b/core/navigation-kmp/src/androidMain/kotlin/org/michaelbel/movies/navigation/ktx/NavHostControllerKtx.kt similarity index 100% rename from core/navigation/src/main/kotlin/org/michaelbel/movies/navigation/ktx/NavHostControllerKtx.kt rename to core/navigation-kmp/src/androidMain/kotlin/org/michaelbel/movies/navigation/ktx/NavHostControllerKtx.kt diff --git a/core/navigation/src/main/kotlin/org/michaelbel/movies/navigation/MoviesNavigationDestination.kt b/core/navigation-kmp/src/commonMain/kotlin/org/michaelbel/movies/navigation/MoviesNavigationDestination.kt similarity index 100% rename from core/navigation/src/main/kotlin/org/michaelbel/movies/navigation/MoviesNavigationDestination.kt rename to core/navigation-kmp/src/commonMain/kotlin/org/michaelbel/movies/navigation/MoviesNavigationDestination.kt diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts deleted file mode 100644 index 9a6d6b17a..000000000 --- a/core/navigation/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.navigation" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(libs.androidx.hilt.navigation.compose) - api(libs.androidx.navigation.compose) -} \ No newline at end of file diff --git a/core/navigation/src/main/AndroidManifest.xml b/core/navigation/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/navigation/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/notifications/.gitignore b/core/network-kmp/.gitignore similarity index 100% rename from core/notifications/.gitignore rename to core/network-kmp/.gitignore diff --git a/core/network-kmp/build.gradle.kts b/core/network-kmp/build.gradle.kts new file mode 100644 index 000000000..1be5d90f2 --- /dev/null +++ b/core/network-kmp/build.gradle.kts @@ -0,0 +1,76 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.android.library) +} + +val tmdbApiKey: String by lazy { + gradleLocalProperties(rootDir, providers).getProperty("TMDB_API_KEY").orEmpty().ifEmpty { + System.getenv("TMDB_API_KEY").orEmpty() + } +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(libs.kotlinx.serialization.json) + implementation(libs.bundles.ktor.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.bundles.ktor.android) + implementation(libs.androidx.startup.runtime) + implementation(libs.okhttp.logging.interceptor) + implementation(libs.flaker.android.okhttp) + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.network_kmp" + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + buildConfigField("String", "TMDB_API_KEY", "\"$tmdbApiKey\"") + } + + buildFeatures { + buildConfig = true + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } + + dependencies { + implementation(libs.chucker.library) { + exclude(group = "androidx.constraintlayout") + } + debugImplementation(libs.chucker.library) { + exclude(group = "androidx.constraintlayout") + } + releaseImplementation(libs.chucker.library.no.op) { + exclude(group = "androidx.constraintlayout") + } + debugImplementation(libs.flaker.android.okhttp) + releaseImplementation(libs.flaker.android.okhttp.noop) + } +} \ No newline at end of file diff --git a/core/network/src/main/AndroidManifest.xml b/core/network-kmp/src/androidMain/AndroidManifest.xml similarity index 100% rename from core/network/src/main/AndroidManifest.xml rename to core/network-kmp/src/androidMain/AndroidManifest.xml diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/chucker/ChuckerKoinModule.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/chucker/ChuckerKoinModule.kt new file mode 100644 index 000000000..3108e91f7 --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/chucker/ChuckerKoinModule.kt @@ -0,0 +1,23 @@ +package org.michaelbel.movies.network.chucker + +import com.chuckerteam.chucker.api.ChuckerCollector +import com.chuckerteam.chucker.api.ChuckerInterceptor +import com.chuckerteam.chucker.api.RetentionManager +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val chuckerKoinModule = module { + single { + val chuckerCollector = ChuckerCollector( + context = androidContext(), + showNotification = true, + retentionPeriod = RetentionManager.Period.ONE_HOUR + ) + val maxContentLength = 250_000L + ChuckerInterceptor.Builder(androidContext()) + .collector(chuckerCollector) + .maxContentLength(maxContentLength) + .alwaysReadResponseBody(true) + .build() + } +} \ No newline at end of file diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt new file mode 100644 index 000000000..6b0a668f2 --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.network.config + +import org.michaelbel.movies.network_kmp.BuildConfig + +private val tmdbApiKey: String + get() = BuildConfig.TMDB_API_KEY + +actual val isTmdbApiKeyEmpty: Boolean + get() = tmdbApiKey.isEmpty() || tmdbApiKey == "null" \ No newline at end of file diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt new file mode 100644 index 000000000..7a3589f53 --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt @@ -0,0 +1,40 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.network.connectivity + +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +actual class NetworkManager( + private val connectivityManager: ConnectivityManager +) { + val status: Flow = callbackFlow { + val networkCallback = object: ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + trySend(NetworkStatus.Available) + } + + override fun onLost(network: Network) { + trySend(NetworkStatus.Unavailable) + } + + override fun onUnavailable() { + trySend(NetworkStatus.Unavailable) + } + } + + val request = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + connectivityManager.registerNetworkCallback(request, networkCallback) + + awaitClose { + connectivityManager.unregisterNetworkCallback(networkCallback) + } + } +} \ No newline at end of file diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/di/ConnectivityKoinModule.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/di/ConnectivityKoinModule.kt new file mode 100644 index 000000000..bc566f9ee --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/di/ConnectivityKoinModule.kt @@ -0,0 +1,10 @@ +package org.michaelbel.movies.network.connectivity.di + +import android.net.ConnectivityManager +import androidx.core.content.ContextCompat +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val connectivityKoinModule = module { + single { ContextCompat.getSystemService(androidContext(), ConnectivityManager::class.java) as ConnectivityManager } +} \ No newline at end of file diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/di/NetworkManagerKoinModule.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/di/NetworkManagerKoinModule.kt new file mode 100644 index 000000000..6e67b57f0 --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/connectivity/di/NetworkManagerKoinModule.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.network.connectivity.di + +import org.koin.dsl.module +import org.michaelbel.movies.network.connectivity.NetworkManager + +val networkManagerKoinModule = module { + includes( + connectivityKoinModule + ) + single { NetworkManager(get()) } +} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/flaker/FlakerInitializer.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/flaker/FlakerInitializer.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/flaker/FlakerInitializer.kt rename to core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/flaker/FlakerInitializer.kt diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/flaker/di/FlakerKoinModule.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/flaker/di/FlakerKoinModule.kt new file mode 100644 index 000000000..79ac61601 --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/flaker/di/FlakerKoinModule.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.network.flaker.di + +import io.github.rotbolt.flakerokhttpcore.FlakerInterceptor +import org.koin.dsl.module + +val flakerKoinModule = module { + single { FlakerInterceptor.Builder().build() } +} \ No newline at end of file diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt new file mode 100644 index 000000000..c67a9fa9a --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt @@ -0,0 +1,59 @@ +package org.michaelbel.movies.network.ktor.di + +import com.chuckerteam.chucker.api.ChuckerInterceptor +import io.github.rotbolt.flakerokhttpcore.FlakerInterceptor +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.http.ContentType +import io.ktor.http.contentType +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import okhttp3.logging.HttpLoggingInterceptor +import org.koin.dsl.module +import org.michaelbel.movies.network.chucker.chuckerKoinModule +import org.michaelbel.movies.network.config.TMDB_API_ENDPOINT +import org.michaelbel.movies.network.flaker.di.flakerKoinModule +import org.michaelbel.movies.network.okhttp.di.CONNECT_TIMEOUT_MILLIS +import org.michaelbel.movies.network.okhttp.di.HTTP_CACHE_SIZE_BYTES +import org.michaelbel.movies.network.okhttp.di.okhttpKoinModule +import org.michaelbel.movies.network.okhttp.interceptor.ApikeyInterceptor + +private const val REQUEST_TIMEOUT_MILLIS = 10_000L +private const val SOCKET_TIMEOUT_SECONDS = 10_000L + +actual val ktorKoinModule = module { + includes( + chuckerKoinModule, + flakerKoinModule, + okhttpKoinModule + ) + single { + val ktor = HttpClient(OkHttp) { + defaultRequest { + contentType(ContentType.Application.Json) + url(TMDB_API_ENDPOINT) + } + install(ContentNegotiation) { + json(Json { ignoreUnknownKeys = true }) + } + install(HttpTimeout) { + requestTimeoutMillis = REQUEST_TIMEOUT_MILLIS + connectTimeoutMillis = CONNECT_TIMEOUT_MILLIS + socketTimeoutMillis = SOCKET_TIMEOUT_SECONDS + } + engine { + clientCacheSize = HTTP_CACHE_SIZE_BYTES + config { + addInterceptor(get()) + addInterceptor(get()) + addInterceptor(get()) + addInterceptor(get()) + } + } + } + ktor + } +} \ No newline at end of file diff --git a/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/okhttp/di/OkhttpKoinModule.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/okhttp/di/OkhttpKoinModule.kt new file mode 100644 index 000000000..2da8609d4 --- /dev/null +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/okhttp/di/OkhttpKoinModule.kt @@ -0,0 +1,51 @@ +package org.michaelbel.movies.network.okhttp.di + +import com.chuckerteam.chucker.api.ChuckerInterceptor +import io.github.rotbolt.flakerokhttpcore.FlakerInterceptor +import okhttp3.Cache +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module +import org.michaelbel.movies.network.chucker.chuckerKoinModule +import org.michaelbel.movies.network.flaker.di.flakerKoinModule +import org.michaelbel.movies.network.okhttp.interceptor.ApikeyInterceptor +import org.michaelbel.movies.network_kmp.BuildConfig +import java.util.concurrent.TimeUnit + +const val HTTP_CACHE_SIZE_BYTES = 1024 * 1024 * 50 +const val CONNECT_TIMEOUT_MILLIS = 10_000L +private const val READ_TIMEOUT_MILLIS = 10_000L +private const val WRITE_TIMEOUT_MILLIS = 10_000L +private const val CALL_TIMEOUT_MILLIS = 0L + +val okhttpKoinModule = module { + includes( + chuckerKoinModule, + flakerKoinModule + ) + single { Cache(androidContext().cacheDir, HTTP_CACHE_SIZE_BYTES.toLong()) } + single { + HttpLoggingInterceptor().apply { + level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else level + } + } + single { ApikeyInterceptor(BuildConfig.TMDB_API_KEY) } + single { + val builder = OkHttpClient.Builder().apply { + addInterceptor(get()) + addInterceptor(get()) + addInterceptor(get()) + addInterceptor(get()) + callTimeout(CALL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + connectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + readTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + writeTimeout(WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + retryOnConnectionFailure(true) + followRedirects(true) + followSslRedirects(true) + cache(get()) + } + builder.build() + } +} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/interceptor/ApikeyInterceptor.kt b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/okhttp/interceptor/ApikeyInterceptor.kt similarity index 87% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/interceptor/ApikeyInterceptor.kt rename to core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/okhttp/interceptor/ApikeyInterceptor.kt index 7e86b9d39..698b51d97 100644 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/interceptor/ApikeyInterceptor.kt +++ b/core/network-kmp/src/androidMain/kotlin/org/michaelbel/movies/network/okhttp/interceptor/ApikeyInterceptor.kt @@ -1,7 +1,6 @@ package org.michaelbel.movies.network.okhttp.interceptor import okhttp3.Interceptor -import okhttp3.Request import okhttp3.Response internal class ApikeyInterceptor( @@ -9,7 +8,7 @@ internal class ApikeyInterceptor( ): Interceptor { override fun intercept(chain: Interceptor.Chain): Response { - val originalRequest: Request = chain.request() + val originalRequest = chain.request() val newHttpUrl = originalRequest.url.newBuilder() .addQueryParameter("api_key", apiKey) .build() diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/AccountNetworkService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/AccountNetworkService.kt new file mode 100644 index 000000000..815641a68 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/AccountNetworkService.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.network + +import org.michaelbel.movies.network.ktor.KtorAccountService +import org.michaelbel.movies.network.model.Account + +class AccountNetworkService internal constructor( + private val ktorAccountService: KtorAccountService +) { + + suspend fun accountDetails( + sessionId: String + ): Account { + return ktorAccountService.accountDetails(sessionId) + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/AuthenticationNetworkService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/AuthenticationNetworkService.kt new file mode 100644 index 000000000..550746705 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/AuthenticationNetworkService.kt @@ -0,0 +1,36 @@ +package org.michaelbel.movies.network + +import org.michaelbel.movies.network.ktor.KtorAuthenticationService +import org.michaelbel.movies.network.model.DeletedSession +import org.michaelbel.movies.network.model.RequestToken +import org.michaelbel.movies.network.model.Session +import org.michaelbel.movies.network.model.SessionRequest +import org.michaelbel.movies.network.model.Token +import org.michaelbel.movies.network.model.Username + +class AuthenticationNetworkService internal constructor( + private val ktorAuthenticationService: KtorAuthenticationService +) { + + suspend fun createRequestToken(): Token { + return ktorAuthenticationService.createRequestToken() + } + + suspend fun createSessionWithLogin( + username: Username + ): Token { + return ktorAuthenticationService.createSessionWithLogin(username) + } + + suspend fun createSession( + authToken: RequestToken + ): Session { + return ktorAuthenticationService.createSession(authToken) + } + + suspend fun deleteSession( + sessionRequest: SessionRequest + ): DeletedSession { + return ktorAuthenticationService.deleteSession(sessionRequest) + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/MovieNetworkService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/MovieNetworkService.kt new file mode 100644 index 000000000..8b366f18f --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/MovieNetworkService.kt @@ -0,0 +1,33 @@ +package org.michaelbel.movies.network + +import org.michaelbel.movies.network.ktor.KtorMovieService +import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.network.model.Movie +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result + +class MovieNetworkService internal constructor( + private val ktorMovieService: KtorMovieService +) { + + suspend fun movies( + list: String, + language: String, + page: Int + ): Result { + return ktorMovieService.movies(list, language, page) + } + + suspend fun movie( + movieId: Int, + language: String + ): Movie { + return ktorMovieService.movie(movieId, language) + } + + suspend fun images( + movieId: Int + ): ImagesResponse { + return ktorMovieService.images(movieId) + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/SearchNetworkService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/SearchNetworkService.kt new file mode 100644 index 000000000..397123d1e --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/SearchNetworkService.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.network + +import org.michaelbel.movies.network.ktor.KtorSearchService +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result + +class SearchNetworkService internal constructor( + private val ktorSearchService: KtorSearchService +) { + + suspend fun searchMovies( + query: String, + language: String, + page: Int + ): Result { + return ktorSearchService.searchMovies(query, language, page) + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/GravatarConfig.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/GravatarConfig.kt new file mode 100644 index 000000000..7d8619a2f --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/GravatarConfig.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.network.config + +const val GRAVATAR_URL = "https://www.gravatar.com/avatar/%s" \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/ScreenState.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/ScreenState.kt similarity index 80% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/ScreenState.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/ScreenState.kt index e1566d46a..a8a525df7 100644 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/ScreenState.kt +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/ScreenState.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.network +package org.michaelbel.movies.network.config sealed interface ScreenState { diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt new file mode 100644 index 000000000..89ba76364 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.network.config + +expect val isTmdbApiKeyEmpty: Boolean \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbConstants.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbConstants.kt new file mode 100644 index 000000000..d58860a01 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbConstants.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.network.config + +const val TMDB_API_ENDPOINT = "https://api.themoviedb.org/3/" +const val TMDB_URL = "https://themoviedb.org" +const val TMDB_TERMS_OF_USE = "$TMDB_URL/documentation/website/terms-of-use" +const val TMDB_PRIVACY_POLICY = "$TMDB_URL/privacy-policy" +const val TMDB_REGISTER = "$TMDB_URL/signup" +const val TMDB_RESET_PASSWORD = "$TMDB_URL/reset-password" +const val TMDB_MOVIE_URL = "$TMDB_URL/movie/%d" +const val TMDB_AUTH_URL = "$TMDB_URL/authenticate/%s?redirect_to=%s" +const val TMDB_AUTH_REDIRECT_URL = "movies://redirect_url" \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbImage.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbImage.kt similarity index 97% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbImage.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbImage.kt index be830ad9a..63e3f45d0 100644 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbImage.kt +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/config/TmdbImage.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.network +package org.michaelbel.movies.network.config import org.michaelbel.movies.network.model.image.BackdropSize import org.michaelbel.movies.network.model.image.PosterSize diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt new file mode 100644 index 000000000..2dae30207 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt @@ -0,0 +1,5 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.network.connectivity + +expect class NetworkManager \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/NetworkStatus.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkStatus.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/NetworkStatus.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkStatus.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/ktx/NetworkStatusKtx.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/connectivity/ktx/NetworkStatusKtx.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/ktx/NetworkStatusKtx.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/connectivity/ktx/NetworkStatusKtx.kt diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/di/NetworkKoinModule.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/di/NetworkKoinModule.kt new file mode 100644 index 000000000..8b46c117f --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/di/NetworkKoinModule.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.network.di + +import org.koin.dsl.module +import org.michaelbel.movies.network.AccountNetworkService +import org.michaelbel.movies.network.AuthenticationNetworkService +import org.michaelbel.movies.network.MovieNetworkService +import org.michaelbel.movies.network.SearchNetworkService +import org.michaelbel.movies.network.ktor.di.ktorNetworkKoinModule + +val networkKoinModule = module { + includes( + ktorNetworkKoinModule + ) + single { AccountNetworkService(get()) } + single { AuthenticationNetworkService(get()) } + single { MovieNetworkService(get()) } + single { SearchNetworkService(get()) } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorAccountService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorAccountService.kt new file mode 100644 index 000000000..5400e6b3b --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorAccountService.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.network.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.parameter +import org.michaelbel.movies.network.model.Account + +internal class KtorAccountService( + private val ktorHttpClient: HttpClient +) { + + suspend fun accountDetails( + sessionId: String + ): Account { + return ktorHttpClient.get("account") { + parameter("session_id", sessionId) + }.body() + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorAuthenticationService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorAuthenticationService.kt new file mode 100644 index 000000000..e1601a1ef --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorAuthenticationService.kt @@ -0,0 +1,47 @@ +package org.michaelbel.movies.network.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.delete +import io.ktor.client.request.get +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import org.michaelbel.movies.network.model.DeletedSession +import org.michaelbel.movies.network.model.RequestToken +import org.michaelbel.movies.network.model.Session +import org.michaelbel.movies.network.model.SessionRequest +import org.michaelbel.movies.network.model.Token +import org.michaelbel.movies.network.model.Username + +internal class KtorAuthenticationService( + private val ktorHttpClient: HttpClient +) { + + suspend fun createRequestToken(): Token { + return ktorHttpClient.get("authentication/token/new?").body() + } + + suspend fun createSessionWithLogin( + username: Username + ): Token { + return ktorHttpClient.post("authentication/token/validate_with_login?") { + setBody(username) + }.body() + } + + suspend fun createSession( + authToken: RequestToken + ): Session { + return ktorHttpClient.post("authentication/session/new?") { + setBody(authToken) + }.body() + } + + suspend fun deleteSession( + sessionRequest: SessionRequest + ): DeletedSession { + return ktorHttpClient.delete("authentication/session?") { + setBody(sessionRequest) + }.body() + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorMovieService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorMovieService.kt new file mode 100644 index 000000000..c8db3f5d7 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorMovieService.kt @@ -0,0 +1,41 @@ +package org.michaelbel.movies.network.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.parameter +import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.network.model.Movie +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result + +internal class KtorMovieService( + private val ktorHttpClient: HttpClient +) { + + suspend fun movies( + list: String, + language: String, + page: Int + ): Result { + return ktorHttpClient.get("movie/$list") { + parameter("language", language) + parameter("page", page) + }.body() + } + + suspend fun movie( + movieId: Int, + language: String + ): Movie { + return ktorHttpClient.get("movie/$movieId") { + parameter("language", language) + }.body() + } + + suspend fun images( + movieId: Int + ): ImagesResponse { + return ktorHttpClient.get("movie/$movieId/images").body() + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorSearchService.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorSearchService.kt new file mode 100644 index 000000000..cc0230302 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/KtorSearchService.kt @@ -0,0 +1,25 @@ +package org.michaelbel.movies.network.ktor + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.parameter +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result + +internal class KtorSearchService( + private val ktorHttpClient: HttpClient +) { + + suspend fun searchMovies( + query: String, + language: String, + page: Int + ): Result { + return ktorHttpClient.get("search/movie") { + parameter("query", query) + parameter("language", language) + parameter("page", page) + }.body() + } +} \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt new file mode 100644 index 000000000..b52622243 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.network.ktor.di + +import org.koin.core.module.Module + +expect val ktorKoinModule: Module \ No newline at end of file diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorNetworkKoinModule.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorNetworkKoinModule.kt new file mode 100644 index 000000000..c5603415c --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorNetworkKoinModule.kt @@ -0,0 +1,17 @@ +package org.michaelbel.movies.network.ktor.di + +import org.koin.dsl.module +import org.michaelbel.movies.network.ktor.KtorAccountService +import org.michaelbel.movies.network.ktor.KtorAuthenticationService +import org.michaelbel.movies.network.ktor.KtorMovieService +import org.michaelbel.movies.network.ktor.KtorSearchService + +val ktorNetworkKoinModule = module { + includes( + ktorKoinModule + ) + single { KtorAccountService(get()) } + single { KtorAuthenticationService(get()) } + single { KtorMovieService(get()) } + single { KtorSearchService(get()) } +} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ResultKtx.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktx/ResultKtx.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ResultKtx.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktx/ResultKtx.kt diff --git a/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt new file mode 100644 index 000000000..4644119f6 --- /dev/null +++ b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.network.ktx + +import org.michaelbel.movies.network.config.ScreenState + +val ScreenState.isFailure: Boolean + get() = this is ScreenState.Failure + +val ScreenState.throwable: Throwable + get() = (this as ScreenState.Failure).throwable \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Account.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Account.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Account.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Account.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Avatar.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Avatar.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Avatar.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Avatar.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Cast.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Cast.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Cast.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Cast.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Collection.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Collection.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Collection.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Collection.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Company.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Company.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Company.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Company.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Country.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Country.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Country.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Country.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/CreditsResponse.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/CreditsResponse.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/CreditsResponse.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/CreditsResponse.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Crew.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Crew.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Crew.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Crew.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Dates.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Dates.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Dates.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Dates.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/DeletedSession.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/DeletedSession.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/DeletedSession.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/DeletedSession.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Fave.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Fave.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Fave.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Fave.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Genre.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Genre.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Genre.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Genre.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/GenresResponse.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/GenresResponse.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/GenresResponse.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/GenresResponse.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/GrAvatar.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/GrAvatar.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/GrAvatar.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/GrAvatar.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/GuestSession.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/GuestSession.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/GuestSession.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/GuestSession.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Image.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Image.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Image.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Image.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Keyword.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Keyword.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Keyword.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Keyword.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/KeywordsResponse.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/KeywordsResponse.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/KeywordsResponse.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/KeywordsResponse.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Language.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Language.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Language.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Language.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Mark.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Mark.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Mark.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Mark.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Movie.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Movie.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Movie.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Movie.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/MovieResponse.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/MovieResponse.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/MovieResponse.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/MovieResponse.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Network.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Network.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Network.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Network.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Person.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Person.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Person.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Person.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Rated.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Rated.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Rated.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Rated.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/RequestToken.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/RequestToken.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/RequestToken.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/RequestToken.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Result.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Result.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Result.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Result.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Review.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Review.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Review.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Review.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Session.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Session.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Session.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Session.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/SessionRequest.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/SessionRequest.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/SessionRequest.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/SessionRequest.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/TmdbAvatar.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/TmdbAvatar.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/TmdbAvatar.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/TmdbAvatar.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Token.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Token.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Token.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Token.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Username.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Username.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Username.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Username.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Video.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Video.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Video.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Video.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/Watch.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Watch.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/Watch.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/Watch.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/BackdropSize.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/BackdropSize.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/BackdropSize.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/BackdropSize.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/LogoSize.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/LogoSize.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/LogoSize.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/LogoSize.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/PosterSize.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/PosterSize.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/PosterSize.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/PosterSize.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/ProfileSize.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/ProfileSize.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/ProfileSize.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/ProfileSize.kt diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/StillSize.kt b/core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/StillSize.kt similarity index 100% rename from core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/StillSize.kt rename to core/network-kmp/src/commonMain/kotlin/org/michaelbel/movies/network/model/image/StillSize.kt diff --git a/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt b/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt new file mode 100644 index 000000000..596938f77 --- /dev/null +++ b/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/config/TmdbConfig.kt @@ -0,0 +1,4 @@ +package org.michaelbel.movies.network.config + +actual val isTmdbApiKeyEmpty: Boolean + get() = true \ No newline at end of file diff --git a/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt b/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt new file mode 100644 index 000000000..46fa999d5 --- /dev/null +++ b/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt @@ -0,0 +1,5 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.network.connectivity + +actual class NetworkManager \ No newline at end of file diff --git a/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt b/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt new file mode 100644 index 000000000..a3dcc25a5 --- /dev/null +++ b/core/network-kmp/src/desktopMain/kotlin/org/michaelbel/movies/network/ktor/di/KtorKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.network.ktor.di + +import org.koin.dsl.module + +actual val ktorKoinModule = module {} \ No newline at end of file diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts deleted file mode 100644 index 93ca55afa..000000000 --- a/core/network/build.gradle.kts +++ /dev/null @@ -1,63 +0,0 @@ -import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties - -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - alias(libs.plugins.kotlin.serialization) - id("movies-android-hilt") -} - -val tmdbApiKey: String by lazy { - gradleLocalProperties(rootDir).getProperty("TMDB_API_KEY").orEmpty().ifEmpty { - System.getenv("TMDB_API_KEY").orEmpty() - } -} - -android { - namespace = "org.michaelbel.movies.network" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - buildConfigField("String", "TMDB_API_KEY", "\"$tmdbApiKey\"") - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - buildConfig = true - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(libs.androidx.startup.runtime) - api(libs.kotlin.serialization.json) - implementation(libs.okhttp.logging.interceptor) - implementation(libs.retrofit.converter.serialization) - api(libs.retrofit) - debugImplementation(libs.chucker.library) - releaseImplementation(libs.chucker.library.no.op) - debugImplementation(libs.flaker.android.okhttp) - releaseImplementation(libs.flaker.android.okhttp.noop) - // implementation(libs.chucker.library.no.op) enable for benchmark -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/Either.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/Either.kt deleted file mode 100644 index 46ca4b271..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/Either.kt +++ /dev/null @@ -1,261 +0,0 @@ -@file:Suppress("unused") - -package org.michaelbel.movies.network - -/** - * A class that encapsulates a successful result with a value of type T - * or a failure result with an [Throwable] exception - */ -@Suppress("unchecked_cast") -sealed class Either { - data class Success(val value: T): Either() - data class Failure(val exception: Throwable): Either() - - override fun toString(): String { - return when (this) { - is Success<*> -> "Success[data=$value]" - is Failure -> "Failure[exception=$exception]" - } - } - - companion object { - /** - * Construct a safe Either from statement - * ```kotlin - * Either.on { "something" } - * ``` - */ - inline fun on(f: () -> T): Either = try { - val result = f() - if (result is Either<*>) { - result as Either - } else { - Success(result) - } - } catch (ex: Exception) { - Failure(ex) - } - } -} - -val Either<*>.success: Boolean - get() = this is Either.Success && value != null - -fun Either.successOr(fallback: T): T { - return (this as? Either.Success)?.data ?: fallback -} - -val Either<*>.failure: Boolean - get() = this is Either.Failure - -val Either.data: T? - get() = (this as? Either.Success)?.value - -val Either.throwable: Throwable? - get() = (this as? Either.Failure)?.exception - -/** - * Unwrap and receive the success result data or receive the default value in failure case - * ```kotlin - * val data = useCase.getData() - * .takeOrDefault { - * "default data" - * } - * ``` - */ -inline fun Either.takeOrDefault(default: () -> T): T = when (this) { - is Either.Success -> this.value - is Either.Failure -> default() -} - -/** - * Unwrap and receive the success result data or receive '''null''' in failure case - * ```kotlin - * val data = useCase.getData() - * .takeOrNull() - */ -fun Either.takeOrNull(): T? = when (this) { - is Either.Success -> this.data - is Either.Failure -> null -} - -/** - * Transform the success result by applying a function to it to another Either - * ```kotlin - * useCase.getData() - * .flatMap { Reaction.of { "Flatmapped data" } } - * ``` - */ -inline fun Either.flatMap(f: (T) -> Either) = try { - when (this) { - is Either.Success -> f(this.value) - is Either.Failure -> this - } -} catch (e: Exception) { - Either.Failure(e) -} - -/** - * Transform the success result by applying a function to it - * ```kotlin - * useCase.getData() - * .map { "Convert to another string" } - * ``` - */ -inline fun Either.map(f: (T) -> R) = try { - when (this) { - is Either.Success -> Either.Success(f(this.value)) - is Either.Failure -> this - } -} catch (e: Exception) { - Either.Failure(e) -} - -/** - * Transform the error result by applying a function to it - * ```kotlin - * useCase.getData() - * .errorMap { IllegalStateException("something went wrong") } - * ``` - */ -inline fun Either.errorMap(f: (Throwable) -> Throwable) = try { - when (this) { - is Either.Success -> this - is Either.Failure -> Either.Failure(f(this.exception)) - } -} catch (e: Exception) { - Either.Failure(e) -} - -/** - * Transform the failure result by applying a function to it to another Either - * ```kotlin - * useCase.getData() - * .recover { "New reaction, much better then old" } - * ``` - */ -inline fun Either.recover(transform: (exception: Throwable) -> T): Either = try { - when (this) { - is Either.Success -> this - is Either.Failure -> Either.on { transform(this.exception) } - } -} catch (e: Exception) { - Either.Failure(e) -} - -/** - * Handle the Either result with one action with success and failure - * ```kotlin - * useCase.getData(data) - * .flatHandle { success, failure -> - * Log.d("LOG", "Let's combine results ${success.toString() + failure.toString()}") - * } - * ``` - */ -inline fun Either.flatHandle(f: (T?, Throwable?) -> Unit) { - when (this) { - is Either.Success -> f(this.data, null) - is Either.Failure -> f(null, this.exception) - } -} - -/** - * Register an action to take when Either is nevermind - * ```kotlin - * useCase.getData() - * .doOnComplete { Log.d("Let's dance in any case!") } - * ``` - */ -inline fun Either.doOnComplete(f: () -> Unit) { - f() -} - -/** - * Handle the Either result with on success and on failure actions - * ```kotlin - * useCase.getData(data) - * .handle( - * success = { liveData.postValue(it) }, - * failure = { Log.d("LOG", "Failure. That's a shame") } - * ) -``` - */ -inline fun Either.handle(success: (T) -> Unit, failure: (Throwable) -> Unit) { - when (this) { - is Either.Success -> success(this.value) - is Either.Failure -> failure(this.exception) - } -} - -/** - * Handle the Either result with on success and on failure actions and transform them to the new object - * ```kotlin - * useCase.getData() - * .zip( - * success = { State.Success(it) }, - * failure = { State.Failure } - * ) - * ``` - */ -inline fun Either.zip(success: (T) -> R, failure: (Throwable) -> R): R = - when (this) { - is Either.Success -> success(this.value) - is Either.Failure -> failure(this.exception) - } - -/** - * Register an action to take when Either is Success - * ```kotlin - * useCase.getData() - * .doOnSuccess { Log.d("Success! Let's dance!") } - * ``` - */ -inline fun Either.doOnSuccess(f: (T) -> Unit): Either = try { - when (this) { - is Either.Success -> { - f(this.value) - this - } - is Either.Failure -> this - } -} catch (e: Exception) { - Either.Failure(e) -} - -/** - * Either an action to take when Either is Failure - * ```kotlin - * useCase.getData() - * .doOnError { Log.d("Error! Let's dance but sadly =(") } - * ``` - */ -inline fun Either.doOnFailure(f: (Throwable) -> Unit): Either = try { - when (this) { - is Either.Success -> this - is Either.Failure -> { - f(this.exception) - this - } - } -} catch (e: Exception) { - Either.Failure(e) -} - -/** - * Check the success result by a function - * ```kotlin - * useCase.getData() - * .check { it.isNotEmpty() } - * ``` - */ -inline fun Either.check(message: String = "", f: (T) -> Boolean): Either = try { - when (this) { - is Either.Success -> { - check(f(this.value)) { message } - this - } - is Either.Failure -> this - } -} catch (e: Exception) { - Either.Failure(e) -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/GravatarConfig.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/GravatarConfig.kt deleted file mode 100644 index 86fff4a24..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/GravatarConfig.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.michaelbel.movies.network - -const val GRAVATAR_URL = "https://www.gravatar.com/avatar/%s" \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbConfig.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbConfig.kt deleted file mode 100644 index 6429bf25b..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbConfig.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.michaelbel.movies.network - -const val TMDB_URL = "https://themoviedb.org" -const val TMDB_TERMS_OF_USE = "$TMDB_URL/documentation/website/terms-of-use" -const val TMDB_PRIVACY_POLICY = "$TMDB_URL/privacy-policy" -const val TMDB_REGISTER = "$TMDB_URL/signup" -const val TMDB_RESET_PASSWORD = "$TMDB_URL/reset-password" -const val TMDB_MOVIE_URL = "$TMDB_URL/movie/%d" -const val TMDB_AUTH_URL = "$TMDB_URL/authenticate/%s?redirect_to=%s" -const val TMDB_AUTH_REDIRECT_URL = "movies://redirect_url" - -private val tmdbApiKey: String - get() = BuildConfig.TMDB_API_KEY - -val isTmdbApiKeyEmpty: Boolean - get() = tmdbApiKey.isEmpty() || tmdbApiKey == "null" \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/chucker/ChuckerModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/chucker/ChuckerModule.kt deleted file mode 100644 index 757d8c47d..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/chucker/ChuckerModule.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.michaelbel.movies.network.chucker - -import android.content.Context -import com.chuckerteam.chucker.api.ChuckerCollector -import com.chuckerteam.chucker.api.ChuckerInterceptor -import com.chuckerteam.chucker.api.RetentionManager -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -internal object ChuckerModule { - - @Provides - fun provideChuckerInterceptor( - @ApplicationContext context: Context - ): ChuckerInterceptor { - val chuckerCollector = ChuckerCollector( - context = context, - showNotification = true, - retentionPeriod = RetentionManager.Period.ONE_HOUR - ) - return ChuckerInterceptor.Builder(context) - .collector(chuckerCollector) - .maxContentLength(MAX_CONTENT_LENGTH) - .alwaysReadResponseBody(true) - .build() - } - - private const val MAX_CONTENT_LENGTH = 250_000L -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt deleted file mode 100644 index 22c129723..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/NetworkManager.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.michaelbel.movies.network.connectivity - -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow - -class NetworkManager @Inject constructor( - private val connectivityManager: ConnectivityManager -) { - val status: Flow = callbackFlow { - val networkCallback = object: ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - trySend(NetworkStatus.Available) - } - - override fun onLost(network: Network) { - trySend(NetworkStatus.Unavailable) - } - - override fun onUnavailable() { - trySend(NetworkStatus.Unavailable) - } - } - - val request = NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build() - connectivityManager.registerNetworkCallback(request, networkCallback) - - awaitClose { - connectivityManager.unregisterNetworkCallback(networkCallback) - } - } -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/di/ConnectivityModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/di/ConnectivityModule.kt deleted file mode 100644 index 696b35674..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/connectivity/di/ConnectivityModule.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.michaelbel.movies.network.connectivity.di - -import android.content.Context -import android.net.ConnectivityManager -import androidx.core.content.ContextCompat -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -internal object ConnectivityModule { - - @Provides - fun provideConnectivityService( - @ApplicationContext context: Context - ): ConnectivityManager { - return ContextCompat.getSystemService( - context, - ConnectivityManager::class.java - ) as ConnectivityManager - } -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/flaker/di/FlakerModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/flaker/di/FlakerModule.kt deleted file mode 100644 index 79ad5a24f..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/flaker/di/FlakerModule.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.michaelbel.movies.network.flaker.di - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import io.github.rotbolt.flakerokhttpcore.FlakerInterceptor - -@Module -@InstallIn(SingletonComponent::class) -internal object FlakerModule { - - @Provides - fun provideFlakerInterceptor(): FlakerInterceptor = FlakerInterceptor.Builder().build() -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt deleted file mode 100644 index 777937c7c..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.network.ktx - -import org.michaelbel.movies.network.ScreenState - -val ScreenState.isFailure: Boolean - get() = this is ScreenState.Failure - -val ScreenState.throwable: Throwable - get() = (this as ScreenState.Failure).throwable \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/AccountStates.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/AccountStates.kt deleted file mode 100644 index bc99171f4..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/AccountStates.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.michaelbel.movies.network.model - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class AccountStates( - @SerialName("id") val id: Int, - @SerialName("favorite") val favorite: Boolean, - @SerialName("watchlist") val watchlist: Boolean - //@SerialName("rated") val rated: Rated -) \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/OkhttpModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/OkhttpModule.kt deleted file mode 100644 index d438302a3..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/OkhttpModule.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.michaelbel.movies.network.okhttp - -import android.content.Context -import com.chuckerteam.chucker.api.ChuckerInterceptor -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import io.github.rotbolt.flakerokhttpcore.FlakerInterceptor -import java.util.concurrent.TimeUnit -import javax.inject.Singleton -import okhttp3.Cache -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import org.michaelbel.movies.network.BuildConfig -import org.michaelbel.movies.network.okhttp.interceptor.ApikeyInterceptor - -@Module -@InstallIn(SingletonComponent::class) -internal object OkhttpModule { - - @Provides - @Singleton - fun httpCache( - @ApplicationContext context: Context - ): Cache { - return Cache(context.cacheDir, HTTP_CACHE_SIZE_BYTES) - } - - @Provides - @Singleton - fun provideLoggingInterceptor(): HttpLoggingInterceptor { - return HttpLoggingInterceptor().apply { - level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else level - } - } - - @Provides - @Singleton - fun provideApikeyInterceptor(): ApikeyInterceptor { - return ApikeyInterceptor(BuildConfig.TMDB_API_KEY) - } - - @Provides - @Singleton - fun provideOkHttp( - chuckerInterceptor: ChuckerInterceptor, - flakerInterceptor: FlakerInterceptor, - httpLoggingInterceptor: HttpLoggingInterceptor, - apikeyInterceptor: ApikeyInterceptor, - cache: Cache - ): OkHttpClient { - val builder = OkHttpClient.Builder().apply { - addInterceptor(chuckerInterceptor) - addInterceptor(flakerInterceptor) - addInterceptor(httpLoggingInterceptor) - addInterceptor(apikeyInterceptor) - callTimeout(CALL_TIMEOUT_SECONDS, TimeUnit.SECONDS) - connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS) - writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS) - retryOnConnectionFailure(true) - followRedirects(true) - followSslRedirects(true) - cache(cache) - } - return builder.build() - } - - private const val HTTP_CACHE_SIZE_BYTES = 1024 * 1024 * 50L - private const val CALL_TIMEOUT_SECONDS = 0L - private const val CONNECT_TIMEOUT_SECONDS = 10L - private const val READ_TIMEOUT_SECONDS = 10L - private const val WRITE_TIMEOUT_SECONDS = 10L -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/retrofit/RetrofitModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/retrofit/RetrofitModule.kt deleted file mode 100644 index da75c8039..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/retrofit/RetrofitModule.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.michaelbel.movies.network.retrofit - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import okhttp3.OkHttpClient -import retrofit2.Converter -import retrofit2.Retrofit - -@Module -@InstallIn(SingletonComponent::class) -internal object RetrofitModule { - - private const val TMDB_API_ENDPOINT = "https://api.themoviedb.org/3/" - - @Provides - @Singleton - fun provideRetrofit( - converterFactory: Converter.Factory, - okHttpClient: OkHttpClient - ): Retrofit { - return Retrofit.Builder() - .baseUrl(TMDB_API_ENDPOINT) - .addConverterFactory(converterFactory) - .client(okHttpClient) - .build() - } -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/serialization/ConverterFactoryModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/serialization/ConverterFactoryModule.kt deleted file mode 100644 index ef842a107..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/serialization/ConverterFactoryModule.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.michaelbel.movies.network.serialization - -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import kotlinx.serialization.json.Json -import okhttp3.MediaType -import okhttp3.MediaType.Companion.toMediaType -import retrofit2.Converter - -@Module -@InstallIn(SingletonComponent::class) -internal object ConverterFactoryModule { - - @Provides - @Singleton - fun provideSerializationConverterFactory(): Converter.Factory { - val contentType: MediaType = MEDIA_TYPE_APPLICATION_JSON.toMediaType() - val format = Json { ignoreUnknownKeys = true } - return format.asConverterFactory(contentType) - } - - private const val MEDIA_TYPE_APPLICATION_JSON = "application/json" -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/account/AccountService.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/account/AccountService.kt deleted file mode 100644 index 41cf6ccee..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/account/AccountService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.michaelbel.movies.network.service.account - -import org.michaelbel.movies.network.model.Account -import retrofit2.http.GET -import retrofit2.http.Query - -interface AccountService { - - @GET("account") - suspend fun accountDetails( - @Query("session_id") sessionId: String - ): Account -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/authentication/AuthenticationService.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/authentication/AuthenticationService.kt deleted file mode 100644 index 2d89a4530..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/authentication/AuthenticationService.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.michaelbel.movies.network.service.authentication - -import org.michaelbel.movies.network.model.DeletedSession -import org.michaelbel.movies.network.model.RequestToken -import org.michaelbel.movies.network.model.Session -import org.michaelbel.movies.network.model.SessionRequest -import org.michaelbel.movies.network.model.Token -import org.michaelbel.movies.network.model.Username -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.HTTP -import retrofit2.http.POST - -interface AuthenticationService { - - @GET("authentication/token/new?") - suspend fun createRequestToken(): Token - - @POST("authentication/token/validate_with_login?") - suspend fun createSessionWithLogin( - @Body username: Username - ): Token - - @POST("authentication/session/new?") - suspend fun createSession( - @Body authToken: RequestToken - ): Session - - @HTTP(method = "DELETE", path = "authentication/session?", hasBody = true) - suspend fun deleteSession( - @Body sessionRequest: SessionRequest - ): DeletedSession -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/di/ServiceModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/di/ServiceModule.kt deleted file mode 100644 index 13b2eb877..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/di/ServiceModule.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.michaelbel.movies.network.service.di - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import org.michaelbel.movies.network.service.account.AccountService -import org.michaelbel.movies.network.service.authentication.AuthenticationService -import org.michaelbel.movies.network.service.ktx.createService -import org.michaelbel.movies.network.service.movie.MovieService -import org.michaelbel.movies.network.service.search.SearchService -import retrofit2.Retrofit - -@Module -@InstallIn(SingletonComponent::class) -internal object ServiceModule { - - @Provides - @Singleton - fun provideAuthenticationService( - retrofit: Retrofit - ): AuthenticationService = retrofit.createService() - - @Provides - @Singleton - fun provideAccountService( - retrofit: Retrofit - ): AccountService = retrofit.createService() - - @Provides - @Singleton - fun provideMovieService( - retrofit: Retrofit - ): MovieService = retrofit.createService() - - @Provides - @Singleton - fun provideSearchService( - retrofit: Retrofit - ): SearchService = retrofit.createService() -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/ktx/RetrofitKtx.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/ktx/RetrofitKtx.kt deleted file mode 100644 index 9e8252897..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/ktx/RetrofitKtx.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.michaelbel.movies.network.service.ktx - -import retrofit2.Retrofit - -internal inline fun Retrofit.createService(): T = create(T::class.java) \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/movie/MovieService.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/movie/MovieService.kt deleted file mode 100644 index 7efac924e..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/movie/MovieService.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.michaelbel.movies.network.service.movie - -import org.michaelbel.movies.network.model.ImagesResponse -import org.michaelbel.movies.network.model.Movie -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result -import retrofit2.http.GET -import retrofit2.http.Path -import retrofit2.http.Query - -interface MovieService { - - @GET("movie/{list}") - suspend fun movies( - @Path("list") list: String, - @Query("language") language: String, - @Query("page") page: Int - ): Result - - @GET("movie/{movie_id}") - suspend fun movie( - @Path("movie_id") id: Int, - @Query("language") language: String - ): Movie - - @GET("movie/{movie_id}/images") - suspend fun images( - @Path("movie_id") id: Int - ): ImagesResponse -} \ No newline at end of file diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/search/SearchService.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/search/SearchService.kt deleted file mode 100644 index 190ec4c5c..000000000 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/search/SearchService.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.michaelbel.movies.network.service.search - -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result -import retrofit2.http.GET -import retrofit2.http.Query - -interface SearchService { - - @GET("search/movie") - suspend fun searchMovies( - @Query("query") query: String, - @Query("language") language: String, - @Query("page") page: Int - ): Result -} \ No newline at end of file diff --git a/core/persistence/.gitignore b/core/notifications-kmp/.gitignore similarity index 100% rename from core/persistence/.gitignore rename to core/notifications-kmp/.gitignore diff --git a/core/notifications-kmp/build.gradle.kts b/core/notifications-kmp/build.gradle.kts new file mode 100644 index 000000000..696de2214 --- /dev/null +++ b/core/notifications-kmp/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:common-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:ui-kmp")) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.notifications_kmp" + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/notifications-kmp/src/androidMain/AndroidManifest.xml b/core/notifications-kmp/src/androidMain/AndroidManifest.xml new file mode 100644 index 000000000..551440023 --- /dev/null +++ b/core/notifications-kmp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/NotificationClient.kt b/core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/NotificationClient.kt similarity index 76% rename from core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/NotificationClient.kt rename to core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/NotificationClient.kt index 772501e17..a798c419f 100644 --- a/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/NotificationClient.kt +++ b/core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/NotificationClient.kt @@ -1,3 +1,5 @@ +@file:SuppressLint("MissingPermission") + package org.michaelbel.movies.notifications import android.annotation.SuppressLint @@ -9,29 +11,26 @@ import androidx.annotation.StringRes import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import androidx.core.content.ContextCompat import androidx.core.net.toUri -import dagger.hilt.android.qualifiers.ApplicationContext -import java.util.concurrent.TimeUnit -import javax.inject.Inject import kotlinx.coroutines.delay +import org.michaelbel.movies.common.ktx.isPostNotificationsPermissionGranted import org.michaelbel.movies.common.ktx.isTimePasses +import org.michaelbel.movies.common.ktx.notificationManager import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.notifications.ktx.isPostNotificationsPermissionGranted import org.michaelbel.movies.notifications.model.MoviesPush -import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.notifications_kmp.R +import org.michaelbel.movies.ui.icons.MoviesAndroidIcons +import java.util.concurrent.TimeUnit -@SuppressLint("MissingPermission") -class NotificationClient @Inject constructor( - @ApplicationContext private val context: Context, - private val notificationManager: NotificationManagerCompat, +class NotificationClient( + private val context: Context, private val interactor: Interactor ) { suspend fun notificationsPermissionRequired(time: Long): Boolean { - val expireTime: Long = interactor.notificationExpireTime() - val currentTime: Long = System.currentTimeMillis() - val isTimePasses: Boolean = isTimePasses(ONE_DAY_MILLS, expireTime, currentTime) + val expireTime = interactor.notificationExpireTime() + val currentTime = System.currentTimeMillis() + val isTimePasses = isTimePasses(ONE_DAY_MILLS, expireTime, currentTime) delay(time) return !context.isPostNotificationsPermissionGranted && isTimePasses } @@ -53,9 +52,8 @@ class NotificationClient @Inject constructor( ).apply { setContentTitle(push.notificationTitle) setContentText(push.notificationDescription) - setSmallIcon(MoviesIcons.MovieFilter24) + setSmallIcon(MoviesAndroidIcons.MovieFilter24) setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) - color = ContextCompat.getColor(context, R.color.primary) setDefaults(NotificationCompat.DEFAULT_LIGHTS) setGroupSummary(true) setGroup(GROUP_NAME) @@ -67,7 +65,7 @@ class NotificationClient @Inject constructor( }.build() if (context.isPostNotificationsPermissionGranted) { - notificationManager.notify(TAG, push.notificationId, notification) + context.notificationManager.notify(TAG, push.notificationId, notification) } } @@ -88,9 +86,8 @@ class NotificationClient @Inject constructor( ).apply { setContentTitle(context.getString(contentTitleRes)) setContentText(context.getString(contentTextRes)) - setSmallIcon(MoviesIcons.FileDownload24) + setSmallIcon(MoviesAndroidIcons.FileDownload24) setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) - color = ContextCompat.getColor(context, R.color.primary) setDefaults(NotificationCompat.DEFAULT_LIGHTS) setVibrate(VIBRATE_PATTERN) priority = NotificationCompat.PRIORITY_HIGH @@ -100,12 +97,12 @@ class NotificationClient @Inject constructor( }.build() if (context.isPostNotificationsPermissionGranted) { - notificationManager.notify(DOWNLOAD_IMAGE_NOTIFICATION_TAG, notificationId, notification) + context.notificationManager.notify(DOWNLOAD_IMAGE_NOTIFICATION_TAG, notificationId, notification) } } fun cancelDownloadImageNotification(notificationId: Int) { - notificationManager.cancel(DOWNLOAD_IMAGE_NOTIFICATION_TAG, notificationId) + context.notificationManager.cancel(DOWNLOAD_IMAGE_NOTIFICATION_TAG, notificationId) } private fun createChannel( @@ -113,7 +110,7 @@ class NotificationClient @Inject constructor( @StringRes channelName: Int, @StringRes channelDescription: Int ) { - val notificationChannel: NotificationChannelCompat = NotificationChannelCompat.Builder( + val notificationChannel = NotificationChannelCompat.Builder( context.getString(channelId), NotificationManagerCompat.IMPORTANCE_HIGH ).apply { @@ -121,7 +118,7 @@ class NotificationClient @Inject constructor( setDescription(context.getString(channelDescription)) setShowBadge(true) }.build() - notificationManager.createNotificationChannel(notificationChannel) + context.notificationManager.createNotificationChannel(notificationChannel) } private fun MoviesPush.pendingIntent(): PendingIntent { diff --git a/core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/di/notificationClientKoinModule.kt b/core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/di/notificationClientKoinModule.kt new file mode 100644 index 000000000..0d3f75a8f --- /dev/null +++ b/core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/di/notificationClientKoinModule.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.notifications.di + +import org.koin.dsl.module +import org.michaelbel.movies.notifications.NotificationClient + +val notificationClientKoinModule = module { + single { NotificationClient(get(), get()) } +} \ No newline at end of file diff --git a/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/model/MoviesPush.kt b/core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/model/MoviesPush.kt similarity index 100% rename from core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/model/MoviesPush.kt rename to core/notifications-kmp/src/androidMain/kotlin/org/michaelbel/movies/notifications/model/MoviesPush.kt diff --git a/core/notifications/src/main/res/drawable/ic_movie_filter_24.xml b/core/notifications-kmp/src/androidMain/res/drawable/ic_movie_filter_24.xml similarity index 100% rename from core/notifications/src/main/res/drawable/ic_movie_filter_24.xml rename to core/notifications-kmp/src/androidMain/res/drawable/ic_movie_filter_24.xml diff --git a/core/notifications/src/main/res/values-ru/strings.xml b/core/notifications-kmp/src/androidMain/res/values-ru/strings.xml similarity index 100% rename from core/notifications/src/main/res/values-ru/strings.xml rename to core/notifications-kmp/src/androidMain/res/values-ru/strings.xml diff --git a/core/notifications/src/main/res/values/strings.xml b/core/notifications-kmp/src/androidMain/res/values/strings.xml similarity index 100% rename from core/notifications/src/main/res/values/strings.xml rename to core/notifications-kmp/src/androidMain/res/values/strings.xml diff --git a/core/notifications/build.gradle.kts b/core/notifications/build.gradle.kts deleted file mode 100644 index bf9303ff1..000000000 --- a/core/notifications/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.notifications" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:ui")) -} \ No newline at end of file diff --git a/core/notifications/src/main/AndroidManifest.xml b/core/notifications/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/notifications/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/di/NotificationModule.kt b/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/di/NotificationModule.kt deleted file mode 100644 index 533c9918a..000000000 --- a/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/di/NotificationModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.michaelbel.movies.notifications.di - -import android.content.Context -import androidx.core.app.NotificationManagerCompat -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object NotificationModule { - - @Provides - @Singleton - fun provideAppDatabase( - @ApplicationContext context: Context - ): NotificationManagerCompat = NotificationManagerCompat.from(context) -} \ No newline at end of file diff --git a/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/ktx/PermissionKtx.kt b/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/ktx/PermissionKtx.kt deleted file mode 100644 index b8d2e73dd..000000000 --- a/core/notifications/src/main/kotlin/org/michaelbel/movies/notifications/ktx/PermissionKtx.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.notifications.ktx - -import android.Manifest -import android.content.Context -import android.content.pm.PackageManager -import android.os.Build -import androidx.core.content.ContextCompat -import org.michaelbel.movies.common.ktx.notificationManager - -internal val Context.isPostNotificationsPermissionGranted: Boolean - get() = if (Build.VERSION.SDK_INT >= 33) { - ContextCompat.checkSelfPermission( - this, - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - } else { - notificationManager.areNotificationsEnabled() - } \ No newline at end of file diff --git a/core/platform-services/foss/.gitignore b/core/persistence-kmp/.gitignore similarity index 100% rename from core/platform-services/foss/.gitignore rename to core/persistence-kmp/.gitignore diff --git a/core/persistence-kmp/build.gradle.kts b/core/persistence-kmp/build.gradle.kts new file mode 100644 index 000000000..798f48d5b --- /dev/null +++ b/core/persistence-kmp/build.gradle.kts @@ -0,0 +1,60 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.google.ksp) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:common-kmp")) + implementation(project(":core:network-kmp")) + implementation(libs.bundles.datastore.common) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.bundles.datastore.android) + implementation(libs.bundles.androidx.room) + implementation(libs.koin.android) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(libs.bundles.datastore.desktop) + } + } +} + +android { + namespace = "org.michaelbel.movies.persistence_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + buildConfig = true + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } + + dependencies { + ksp(libs.androidx.room.compiler) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt new file mode 100644 index 000000000..328f0c44f --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt @@ -0,0 +1,25 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.dao.AccountDao +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import org.michaelbel.movies.persistence.database.ktx.accountDb + +actual class AccountPersistence internal constructor( + private val accountDao: AccountDao +) { + + actual fun accountById(accountId: Int): Flow { + return accountDao.accountById(accountId) + } + + actual suspend fun insert(account: AccountPojo) { + accountDao.insert(account.accountDb) + } + + actual suspend fun removeById(accountId: Int) { + accountDao.removeById(accountId) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt new file mode 100644 index 000000000..6f97980e7 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt @@ -0,0 +1,21 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.dao.ImageDao +import org.michaelbel.movies.persistence.database.entity.ImagePojo +import org.michaelbel.movies.persistence.database.ktx.imageDb + +actual class ImagePersistence internal constructor( + private val imageDao: ImageDao +) { + + actual fun imagesFlow(movieId: Int): Flow> { + return imageDao.imagesFlow(movieId) + } + + actual suspend fun insert(images: List) { + imageDao.insert(images.map(ImagePojo::imageDb)) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt new file mode 100644 index 000000000..9d1eccc21 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt @@ -0,0 +1,63 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.dao.MovieDao +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini +import org.michaelbel.movies.persistence.database.ktx.movieDb + +actual class MoviePersistence internal constructor( + private val movieDao: MovieDao +) { + + actual fun pagingSource(movieList: String): PagingSource { + return movieDao.pagingSource(movieList) + } + + actual fun moviesFlow(movieList: String, limit: Int): Flow> { + return movieDao.moviesFlow(movieList, limit) + } + + actual suspend fun movies(movieList: String, limit: Int): List { + return movieDao.movies(movieList, limit) + } + + actual suspend fun moviesMini(movieList: String, limit: Int): List { + return movieDao.moviesMini(movieList, limit) + } + + actual suspend fun insertMovies(movies: List) { + movieDao.insertMovies(movies.map(MoviePojo::movieDb)) + } + + actual suspend fun insertMovie(movie: MoviePojo) { + movieDao.insertMovie(movie.movieDb) + } + + actual suspend fun removeMovies(movieList: String) { + movieDao.removeMovies(movieList) + } + + actual suspend fun removeMovie(movieList: String, movieId: Int) { + movieDao.removeMovie(movieList, movieId) + } + + actual suspend fun movieById(pagingKey: String, movieId: Int): MoviePojo? { + return movieDao.movieById(pagingKey, movieId) + } + + actual suspend fun maxPosition(movieList: String): Int? { + return movieDao.maxPosition(movieList) + } + + actual suspend fun isEmpty(movieList: String): Boolean { + return movieDao.isEmpty(movieList) + } + + actual suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) { + movieDao.updateMovieColors(movieId, containerColor, onContainerColor) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt new file mode 100644 index 000000000..e6591212e --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt @@ -0,0 +1,15 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import androidx.room.withTransaction +import org.michaelbel.movies.persistence.database.db.AppDatabase + +actual class MoviesDatabase internal constructor( + private val database: AppDatabase +) { + + actual suspend fun withTransaction(block: suspend () -> R): R { + return database.withTransaction(block) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt new file mode 100644 index 000000000..20c705532 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt @@ -0,0 +1,28 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import org.michaelbel.movies.persistence.database.dao.PagingKeyDao +import org.michaelbel.movies.persistence.database.entity.PagingKeyPojo +import org.michaelbel.movies.persistence.database.ktx.pagingKeyDb + +actual class PagingKeyPersistence internal constructor( + private val pagingKeyDao: PagingKeyDao +) { + + actual suspend fun page(pagingKey: String): Int? { + return pagingKeyDao.page(pagingKey) + } + + actual suspend fun totalPages(pagingKey: String): Int? { + return pagingKeyDao.totalPages(pagingKey) + } + + actual suspend fun removePagingKey(pagingKey: String) { + pagingKeyDao.removePagingKey(pagingKey) + } + + actual suspend fun insertPagingKey(pagingKeyPojo: PagingKeyPojo) { + pagingKeyDao.insertPagingKey(pagingKeyPojo.pagingKeyDb) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt new file mode 100644 index 000000000..080fd4f14 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt @@ -0,0 +1,25 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.dao.SuggestionDao +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo +import org.michaelbel.movies.persistence.database.ktx.suggestionDb + +actual class SuggestionPersistence internal constructor( + private val suggestionDao: SuggestionDao +) { + + actual fun suggestionsFlow(): Flow> { + return suggestionDao.suggestionsFlow() + } + + actual suspend fun insert(suggestions: List) { + suggestionDao.insert(suggestions.map(SuggestionPojo::suggestionDb)) + } + + actual suspend fun removeAll() { + suggestionDao.removeAll() + } +} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/converter/CalendarConverter.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/converter/CalendarConverter.kt similarity index 100% rename from core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/converter/CalendarConverter.kt rename to core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/converter/CalendarConverter.kt diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/AccountDao.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/AccountDao.kt new file mode 100644 index 000000000..5848501e1 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/AccountDao.kt @@ -0,0 +1,25 @@ +package org.michaelbel.movies.persistence.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.AccountDb +import org.michaelbel.movies.persistence.database.entity.AccountPojo + +/** + * The Data Access Object for the [AccountDb] class. + */ +@Dao +internal interface AccountDao { + + @Query("SELECT * FROM accounts WHERE accountId = :accountId") + fun accountById(accountId: Int): Flow + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(account: AccountDb) + + @Query("DELETE FROM accounts WHERE accountId = :accountId") + suspend fun removeById(accountId: Int) +} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt similarity index 77% rename from core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt rename to core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt index 32aa91b53..1ea1d04ec 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt @@ -6,15 +6,16 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import kotlinx.coroutines.flow.Flow import org.michaelbel.movies.persistence.database.entity.ImageDb +import org.michaelbel.movies.persistence.database.entity.ImagePojo /** * The Data Access Object for the [ImageDb] class. */ @Dao -interface ImageDao { +internal interface ImageDao { @Query("SELECT * FROM images WHERE movieId = :movieId ORDER BY position ASC") - fun imagesFlow(movieId: Int): Flow> + fun imagesFlow(movieId: Int): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(images: List) diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt similarity index 76% rename from core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt rename to core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt index 4aba9921c..e06e4139e 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt @@ -5,27 +5,33 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.RewriteQueriesToDropUnusedColumns import androidx.room.Transaction import kotlinx.coroutines.flow.Flow import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini /** * The Data Access Object for the [MovieDb] class. */ @Dao -interface MovieDao { +internal interface MovieDao { @Transaction @Query("SELECT * FROM movies WHERE movieList = :movieList ORDER BY position ASC") - fun pagingSource(movieList: String): PagingSource + fun pagingSource(movieList: String): PagingSource @Query("SELECT * FROM movies WHERE movieList = :movieList ORDER BY position DESC LIMIT :limit") - fun moviesFlow(movieList: String, limit: Int): Flow> + fun moviesFlow(movieList: String, limit: Int): Flow> @Query("SELECT * FROM movies WHERE movieList = :movieList ORDER BY position ASC LIMIT :limit") - suspend fun movies(movieList: String, limit: Int): List + suspend fun movies(movieList: String, limit: Int): List + + @RewriteQueriesToDropUnusedColumns + @Query("SELECT * FROM movies WHERE movieList = :movieList ORDER BY position ASC LIMIT :limit") + suspend fun moviesMini(movieList: String, limit: Int): List - @Transaction @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertMovies(movies: List) @@ -36,11 +42,11 @@ interface MovieDao { @Query("DELETE FROM movies WHERE movieList = :movieList") suspend fun removeMovies(movieList: String) - @Query("DELETE FROM movies WHERE movieList = :movieList AND id = :movieId") + @Query("DELETE FROM movies WHERE movieList = :movieList AND movieId = :movieId") suspend fun removeMovie(movieList: String, movieId: Int) - @Query("SELECT * FROM movies WHERE movieList = :pagingKey AND id = :movieId") - suspend fun movieById(pagingKey: String, movieId: Int): MovieDb? + @Query("SELECT * FROM movies WHERE movieList = :pagingKey AND movieId = :movieId") + suspend fun movieById(pagingKey: String, movieId: Int): MoviePojo? @Transaction @Query("SELECT MAX(position) FROM movies WHERE movieList = :movieList") @@ -49,6 +55,6 @@ interface MovieDao { @Query("SELECT (SELECT COUNT(*) FROM movies WHERE movieList = :movieList) == 0") suspend fun isEmpty(movieList: String): Boolean - @Query("UPDATE movies SET containerColor = :containerColor, onContainerColor = :onContainerColor WHERE id = :movieId") + @Query("UPDATE movies SET containerColor = :containerColor, onContainerColor = :onContainerColor WHERE movieId = :movieId") suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) } \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/PagingKeyDao.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/PagingKeyDao.kt similarity index 96% rename from core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/PagingKeyDao.kt rename to core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/PagingKeyDao.kt index 92545ec28..969cbd868 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/PagingKeyDao.kt +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/PagingKeyDao.kt @@ -11,7 +11,7 @@ import org.michaelbel.movies.persistence.database.entity.PagingKeyDb * The Data Access Object for the [PagingKeyDb] class. */ @Dao -interface PagingKeyDao { +internal interface PagingKeyDao { @Transaction @Query("SELECT page FROM pagingkeys WHERE pagingKey = :pagingKey") diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/SuggestionDao.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/SuggestionDao.kt similarity index 78% rename from core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/SuggestionDao.kt rename to core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/SuggestionDao.kt index fa76d7d58..9d440cb05 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/SuggestionDao.kt +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/dao/SuggestionDao.kt @@ -6,15 +6,16 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import kotlinx.coroutines.flow.Flow import org.michaelbel.movies.persistence.database.entity.SuggestionDb +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo /** * The Data Access Object for the [SuggestionDb] class. */ @Dao -interface SuggestionDao { +internal interface SuggestionDao { @Query("SELECT * FROM suggestions") - fun suggestionsFlow(): Flow> + fun suggestionsFlow(): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(suggestions: List) diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/AppDatabase.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/db/AppDatabase.kt similarity index 86% rename from core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/AppDatabase.kt rename to core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/db/AppDatabase.kt index 022985f24..143a88070 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/AppDatabase.kt +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/db/AppDatabase.kt @@ -1,11 +1,10 @@ -package org.michaelbel.movies.persistence.database +package org.michaelbel.movies.persistence.database.db import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters -import org.michaelbel.movies.persistence.BuildConfig import org.michaelbel.movies.persistence.database.converter.CalendarConverter import org.michaelbel.movies.persistence.database.dao.AccountDao import org.michaelbel.movies.persistence.database.dao.ImageDao @@ -17,6 +16,7 @@ import org.michaelbel.movies.persistence.database.entity.ImageDb import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.persistence.database.entity.PagingKeyDb import org.michaelbel.movies.persistence.database.entity.SuggestionDb +import org.michaelbel.movies.persistence_kmp.BuildConfig /** * The Room database for this app. @@ -33,7 +33,7 @@ import org.michaelbel.movies.persistence.database.entity.SuggestionDb exportSchema = false ) @TypeConverters(CalendarConverter::class) -abstract class AppDatabase: RoomDatabase() { +internal abstract class AppDatabase: RoomDatabase() { abstract fun movieDao(): MovieDao abstract fun imageDao(): ImageDao @@ -42,8 +42,8 @@ abstract class AppDatabase: RoomDatabase() { abstract fun suggestionDao(): SuggestionDao companion object { - private val DATABASE_NAME: String = if (BuildConfig.DEBUG) "movies-db-debug" else "movies-db" - const val DATABASE_VERSION = 21 + private val DATABASE_NAME = if (BuildConfig.DEBUG) "movies-db-debug" else "movies-db" + const val DATABASE_VERSION = 23 @Volatile private var instance: AppDatabase? = null diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt new file mode 100644 index 000000000..15f7e6a7a --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.dsl.module +import org.michaelbel.movies.persistence.database.db.AppDatabase + +actual val daoKoinModule = module { + includes( + databaseKoinModule + ) + single { get().movieDao() } + single { get().imageDao() } + single { get().accountDao() } + single { get().pagingKeyDao() } + single { get().suggestionDao() } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt new file mode 100644 index 000000000..c5caab83d --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module +import org.michaelbel.movies.persistence.database.db.AppDatabase + +actual val databaseKoinModule = module { + single { AppDatabase.getInstance(androidContext()) } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt new file mode 100644 index 000000000..171a1394a --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.dsl.module +import org.michaelbel.movies.persistence.database.MoviesDatabase + +actual val moviesDatabaseKoinModule = module { + includes( + databaseKoinModule + ) + single { MoviesDatabase(get()) } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt new file mode 100644 index 000000000..1ab9864ae --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt @@ -0,0 +1,19 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.dsl.module +import org.michaelbel.movies.persistence.database.AccountPersistence +import org.michaelbel.movies.persistence.database.ImagePersistence +import org.michaelbel.movies.persistence.database.MoviePersistence +import org.michaelbel.movies.persistence.database.PagingKeyPersistence +import org.michaelbel.movies.persistence.database.SuggestionPersistence + +actual val persistenceKoinModule = module { + includes( + daoKoinModule + ) + single { AccountPersistence(get()) } + single { ImagePersistence(get()) } + single { MoviePersistence(get()) } + single { PagingKeyPersistence(get()) } + single { SuggestionPersistence(get()) } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/AccountDb.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/AccountDb.kt new file mode 100644 index 000000000..e696dd581 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/AccountDb.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.persistence.database.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "accounts") +internal data class AccountDb( + @PrimaryKey val accountId: Int, + val avatarUrl: String, + val language: String, + val country: String, + val name: String, + val adult: Boolean, + val username: String +) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt new file mode 100644 index 000000000..e2c3ae2b0 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt @@ -0,0 +1,17 @@ +package org.michaelbel.movies.persistence.database.entity + +import androidx.room.Entity + +@Entity(tableName = "images", primaryKeys = ["movieId", "filePath"]) +internal data class ImageDb( + val movieId: Int, + val filePath: String, + val type: ImageType, + val width: Int, + val height: Int, + val aspectRatio: Float, + val voteAverage: Float, + val voteCount: Int, + val lang: String?, + val position: Int +) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/MovieDb.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/MovieDb.kt new file mode 100644 index 000000000..3c7e30482 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/MovieDb.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.persistence.database.entity + +import androidx.room.Entity + +@Entity(tableName = "movies", primaryKeys = ["movieList", "movieId"]) +internal data class MovieDb( + val movieList: String, + val dateAdded: Long, + val page: Int?, + val position: Int, + val movieId: Int, + val overview: String, + val posterPath: String, + val backdropPath: String, + val releaseDate: String, + val title: String, + val voteAverage: Float, + val containerColor: Int?, + val onContainerColor: Int? +) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt new file mode 100644 index 000000000..b8e1e5ace --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt @@ -0,0 +1,10 @@ +package org.michaelbel.movies.persistence.database.entity + +import androidx.room.Entity + +@Entity(tableName = "pagingkeys", primaryKeys = ["pagingKey"]) +internal data class PagingKeyDb( + val pagingKey: String, + val page: Int?, + val totalPages: Int? +) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionDb.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionDb.kt new file mode 100644 index 000000000..75e794fcb --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionDb.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.persistence.database.entity + +import androidx.room.Entity + +@Entity(tableName = "suggestions", primaryKeys = ["title"]) +internal data class SuggestionDb( + val title: String +) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountPojoKtx.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountPojoKtx.kt new file mode 100644 index 000000000..843eeb182 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountPojoKtx.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.persistence.database.entity.AccountDb +import org.michaelbel.movies.persistence.database.entity.AccountPojo + +internal val AccountPojo.accountDb: AccountDb + get() = AccountDb( + accountId = accountId, + avatarUrl = avatarUrl, + language = language, + country = country, + name = name, + adult = adult, + username = username + ) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/ImagePojoKtx.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/ImagePojoKtx.kt new file mode 100644 index 000000000..69364624d --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/ImagePojoKtx.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.persistence.database.entity.ImageDb +import org.michaelbel.movies.persistence.database.entity.ImagePojo + +internal val ImagePojo.imageDb: ImageDb + get() = ImageDb( + movieId = movieId, + filePath = filePath, + type = type, + width = width, + height = height, + aspectRatio = aspectRatio, + voteAverage = voteAverage, + voteCount = voteCount, + lang = lang, + position = position + ) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MoviePojoKtx.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MoviePojoKtx.kt new file mode 100644 index 000000000..898ee6d75 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MoviePojoKtx.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.entity.MoviePojo + +internal val MoviePojo.movieDb: MovieDb + get() = MovieDb( + movieList = movieList, + dateAdded = dateAdded, + page = page, + position = position, + movieId = movieId, + overview = overview, + posterPath = posterPath, + backdropPath = backdropPath, + releaseDate = releaseDate, + title = title, + voteAverage = voteAverage, + containerColor = containerColor, + onContainerColor = onContainerColor + ) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/PagingKeyPojoKtx.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/PagingKeyPojoKtx.kt new file mode 100644 index 000000000..e69a1fc69 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/PagingKeyPojoKtx.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.persistence.database.entity.PagingKeyDb +import org.michaelbel.movies.persistence.database.entity.PagingKeyPojo + +internal val PagingKeyPojo.pagingKeyDb: PagingKeyDb + get() = PagingKeyDb( + pagingKey = pagingKey, + page = page, + totalPages = totalPages + ) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/SuggestionPojoKtx.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/SuggestionPojoKtx.kt new file mode 100644 index 000000000..25f2c5820 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/database/ktx/SuggestionPojoKtx.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.persistence.database.entity.SuggestionDb +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo + +internal val SuggestionPojo.suggestionDb: SuggestionDb + get() = SuggestionDb( + title = title + ) \ No newline at end of file diff --git a/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt new file mode 100644 index 000000000..74efd3af0 --- /dev/null +++ b/core/persistence-kmp/src/androidMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.persistence.datastore.di + +import androidx.datastore.preferences.SharedPreferencesMigration +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import okio.Path.Companion.toPath +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module +import org.michaelbel.movies.persistence.datastore.DATA_STORE_NAME +import org.michaelbel.movies.persistence.datastore.SHARED_PREFERENCES_NAME + +internal actual val dataStoreKoinModule = module { + single { + PreferenceDataStoreFactory.createWithPath( + migrations = listOf(SharedPreferencesMigration(androidContext(), SHARED_PREFERENCES_NAME)), + produceFile = { androidContext().filesDir.resolve(DATA_STORE_NAME).absolutePath.toPath() } + ) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt new file mode 100644 index 000000000..09a579550 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt @@ -0,0 +1,15 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.AccountPojo + +expect class AccountPersistence { + + fun accountById(accountId: Int): Flow + + suspend fun insert(account: AccountPojo) + + suspend fun removeById(accountId: Int) +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt new file mode 100644 index 000000000..971871243 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt @@ -0,0 +1,13 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.ImagePojo + +expect class ImagePersistence { + + fun imagesFlow(movieId: Int): Flow> + + suspend fun insert(images: List) +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt new file mode 100644 index 000000000..82aa95432 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt @@ -0,0 +1,35 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini + +expect class MoviePersistence { + + fun pagingSource(movieList: String): PagingSource + + fun moviesFlow(movieList: String, limit: Int): Flow> + + suspend fun movies(movieList: String, limit: Int): List + + suspend fun moviesMini(movieList: String, limit: Int): List + + suspend fun insertMovies(movies: List) + + suspend fun insertMovie(movie: MoviePojo) + + suspend fun removeMovies(movieList: String) + + suspend fun removeMovie(movieList: String, movieId: Int) + + suspend fun movieById(pagingKey: String, movieId: Int): MoviePojo? + + suspend fun maxPosition(movieList: String): Int? + + suspend fun isEmpty(movieList: String): Boolean + + suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt new file mode 100644 index 000000000..2079074d6 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt @@ -0,0 +1,8 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +expect class MoviesDatabase { + + suspend fun withTransaction(block: suspend () -> R): R +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt new file mode 100644 index 000000000..0a38ca943 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt @@ -0,0 +1,16 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import org.michaelbel.movies.persistence.database.entity.PagingKeyPojo + +expect class PagingKeyPersistence { + + suspend fun page(pagingKey: String): Int? + + suspend fun totalPages(pagingKey: String): Int? + + suspend fun removePagingKey(pagingKey: String) + + suspend fun insertPagingKey(pagingKeyPojo: PagingKeyPojo) +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt new file mode 100644 index 000000000..87e5dea5f --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt @@ -0,0 +1,15 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo + +expect class SuggestionPersistence { + + fun suggestionsFlow(): Flow> + + suspend fun insert(suggestions: List) + + suspend fun removeAll() +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt new file mode 100644 index 000000000..4c43a4014 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.core.module.Module + +expect val daoKoinModule: Module \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt new file mode 100644 index 000000000..5a491822f --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.core.module.Module + +expect val databaseKoinModule: Module \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt new file mode 100644 index 000000000..e7f54a675 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.core.module.Module + +expect val moviesDatabaseKoinModule: Module \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt new file mode 100644 index 000000000..49769ba24 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.core.module.Module + +expect val persistenceKoinModule: Module \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/AccountPojo.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/AccountPojo.kt new file mode 100644 index 000000000..19b7b4527 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/AccountPojo.kt @@ -0,0 +1,23 @@ +package org.michaelbel.movies.persistence.database.entity + +data class AccountPojo( + val accountId: Int, + val avatarUrl: String, + val language: String, + val country: String, + val name: String, + val adult: Boolean, + val username: String +) { + companion object { + val Empty = AccountPojo( + accountId = 0, + avatarUrl = "", + language = "", + country = "", + name = "", + adult = false, + username = "" + ) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImagePojo.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImagePojo.kt new file mode 100644 index 000000000..14c053bad --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImagePojo.kt @@ -0,0 +1,29 @@ +package org.michaelbel.movies.persistence.database.entity + +data class ImagePojo( + val movieId: Int, + val filePath: String, + val type: ImageType, + val width: Int, + val height: Int, + val aspectRatio: Float, + val voteAverage: Float, + val voteCount: Int, + val lang: String?, + val position: Int +) { + companion object { + val Empty = ImagePojo( + movieId = 0, + filePath = "", + type = ImageType.BACKDROP, + width = 0, + height = 0, + aspectRatio = 0F, + voteAverage = 0F, + voteCount = 0, + lang = null, + position = 0 + ) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImageType.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImageType.kt new file mode 100644 index 000000000..b9ef887fe --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/ImageType.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.persistence.database.entity + +enum class ImageType { + BACKDROP, + POSTER, + LOGO +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/MoviePojo.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/MoviePojo.kt new file mode 100644 index 000000000..135459e3d --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/MoviePojo.kt @@ -0,0 +1,39 @@ +package org.michaelbel.movies.persistence.database.entity + +data class MoviePojo( + val movieList: String, + val dateAdded: Long, + val page: Int?, + val position: Int, + val movieId: Int, + val overview: String, + val posterPath: String, + val backdropPath: String, + val releaseDate: String, + val title: String, + val voteAverage: Float, + val containerColor: Int?, + val onContainerColor: Int? +) { + companion object { + const val MOVIES_LOCAL_LIST = "movies_local" + const val MOVIES_SEARCH_HISTORY = "movies_search_history" + const val MOVIES_WIDGET = "movies_widget" + + val Empty = MoviePojo( + movieList = "", + dateAdded = 0L, + page = null, + position = 0, + movieId = 0, + overview = "", + posterPath = "", + backdropPath = "", + releaseDate = "", + title = "", + voteAverage = 0F, + containerColor = null, + onContainerColor = null + ) + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyPojo.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyPojo.kt new file mode 100644 index 000000000..b8e6a8c32 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyPojo.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.persistence.database.entity + +data class PagingKeyPojo( + val pagingKey: String, + val page: Int?, + val totalPages: Int? +) \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionPojo.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionPojo.kt new file mode 100644 index 000000000..3e805812d --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionPojo.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.entity + +data class SuggestionPojo( + val title: String +) \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/mini/MovieDbMini.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/mini/MovieDbMini.kt new file mode 100644 index 000000000..fcb4e3245 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/entity/mini/MovieDbMini.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.persistence.database.entity.mini + +data class MovieDbMini( + val movieList: String, + val movieId: Int, + val title: String, +) \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountKtx.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountKtx.kt new file mode 100644 index 000000000..749e4a8da --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountKtx.kt @@ -0,0 +1,22 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.config.GRAVATAR_URL +import org.michaelbel.movies.network.config.formatProfileImage +import org.michaelbel.movies.network.model.Account +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import java.util.Locale + +val Account.accountPojo: AccountPojo + get() = AccountPojo( + accountId = id, + avatarUrl = when { + avatar.tmdbAvatar != null -> avatar.tmdbAvatar?.avatarPath.orEmpty().formatProfileImage + avatar.grAvatar != null -> String.format(Locale.US, GRAVATAR_URL, avatar.grAvatar?.hash) + else -> "" + }, + language = lang, + country = country, + name = name, + adult = includeAdult, + username = username + ) \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonAccountPojoKtx.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonAccountPojoKtx.kt new file mode 100644 index 000000000..5817c1b48 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonAccountPojoKtx.kt @@ -0,0 +1,30 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.persistence.database.entity.AccountPojo + +private const val ACCOUNT_DEFAULT_LETTER = "A" +private const val SPACE_UNICODE = "\u0020" +private const val LETTERS_LIMIT = 2 +private const val FIRST_LETTER_INDEX = 1 + +val AccountPojo.isEmpty: Boolean + get() = this == AccountPojo.Empty + +val AccountPojo?.orEmpty: AccountPojo + get() = this ?: AccountPojo.Empty + +val AccountPojo.letters: String + get() = when { + name.isNotEmpty() -> { + val words = name.split(SPACE_UNICODE) + val letters = words.subList(0, LETTERS_LIMIT).map { word -> + word.substring(0, FIRST_LETTER_INDEX) + } + letters.joinToString( + separator = "", + limit = LETTERS_LIMIT + ) + } + username.isNotEmpty() -> username.substring(0, FIRST_LETTER_INDEX) + else -> ACCOUNT_DEFAULT_LETTER + } \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonImagePojoKtx.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonImagePojoKtx.kt new file mode 100644 index 000000000..c96be8e36 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonImagePojoKtx.kt @@ -0,0 +1,22 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.config.formatImage +import org.michaelbel.movies.network.model.image.BackdropSize +import org.michaelbel.movies.network.model.image.LogoSize +import org.michaelbel.movies.network.model.image.PosterSize +import org.michaelbel.movies.persistence.database.entity.ImagePojo +import org.michaelbel.movies.persistence.database.entity.ImageType + +val ImagePojo.image: String + get() = when (type) { + ImageType.BACKDROP -> filePath.formatImage(BackdropSize.W300.size) + ImageType.POSTER -> filePath.formatImage(PosterSize.W92.size) + ImageType.LOGO -> filePath.formatImage(LogoSize.W45.size) + } + +val ImagePojo.original: String + get() = when (type) { + ImageType.BACKDROP -> filePath.formatImage(BackdropSize.ORIGINAL.size) + ImageType.POSTER -> filePath.formatImage(PosterSize.ORIGINAL.size) + ImageType.LOGO -> filePath.formatImage(LogoSize.ORIGINAL.size) + } \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonMoviePojoKtx.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonMoviePojoKtx.kt new file mode 100644 index 000000000..4b9766198 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/CommonMoviePojoKtx.kt @@ -0,0 +1,14 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.config.TMDB_MOVIE_URL +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import java.util.Locale + +val MoviePojo.isNotEmpty: Boolean + get() = this != MoviePojo.Empty + +val MoviePojo.url: String + get() = String.format(Locale.US, TMDB_MOVIE_URL, movieId) + +val MoviePojo?.orEmpty: MoviePojo + get() = this ?: MoviePojo.Empty \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt new file mode 100644 index 000000000..99a75ccd9 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt @@ -0,0 +1,24 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.model.Image +import org.michaelbel.movies.persistence.database.entity.ImagePojo +import org.michaelbel.movies.persistence.database.entity.ImageType + +fun Image.imagePojo( + movieId: Int, + type: ImageType, + position: Int +): ImagePojo { + return ImagePojo( + movieId = movieId, + filePath = filePath, + type = type, + width = width, + height = height, + aspectRatio = aspectRatio, + voteAverage = voteAverage, + voteCount = voteCount, + lang = lang, + position = position + ) +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieKtx.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieKtx.kt new file mode 100644 index 000000000..b60354cf5 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieKtx.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.model.Movie +import org.michaelbel.movies.persistence.database.entity.MoviePojo + +val Movie.moviePojo: MoviePojo + get() = MoviePojo( + movieList = "", + dateAdded = 0L, + page = null, + position = 0, + movieId = id, + overview = overview.orEmpty(), + posterPath = posterPath.orEmpty(), + backdropPath = backdropPath.orEmpty(), + releaseDate = releaseDate.orEmpty(), + title = title.orEmpty(), + voteAverage = voteAverage, + containerColor = null, + onContainerColor = null + ) \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt new file mode 100644 index 000000000..ef01a2eff --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.persistence.database.entity.MoviePojo + +fun MovieResponse.moviePojo( + movieList: String, + position: Int, + page: Int? = null +): MoviePojo { + return MoviePojo( + movieList = movieList, + dateAdded = System.currentTimeMillis(), + page = page, + position = position, + movieId = id, + overview = overview.orEmpty(), + posterPath = posterPath.orEmpty(), + backdropPath = backdropPath.orEmpty(), + releaseDate = releaseDate, + title = title, + voteAverage = voteAverage, + containerColor = null, + onContainerColor = null + ) +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/DataStoreName.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/DataStoreName.kt new file mode 100644 index 000000000..3cf9393e6 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/DataStoreName.kt @@ -0,0 +1,4 @@ +package org.michaelbel.movies.persistence.datastore + +internal const val DATA_STORE_NAME = "user_preferences.preferences_pb" +internal const val SHARED_PREFERENCES_NAME = "user_preferences" \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/MoviesPreferences.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/MoviesPreferences.kt new file mode 100644 index 000000000..a716ea215 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/MoviesPreferences.kt @@ -0,0 +1,153 @@ +package org.michaelbel.movies.persistence.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map + +class MoviesPreferences( + private val dataStore: DataStore +) { + + val themeFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_THEME_KEY] } + + val feedViewFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_FEED_VIEW_KEY] } + + val movieListFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_MOVIE_LIST_KEY] } + + val isDynamicColorsFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_DYNAMIC_COLORS_KEY] } + + val isBiometricEnabledFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_BIOMETRIC_KEY] } + + val paletteKeyFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_PALETTE_KEY] } + + val seedColorFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_SEED_COLOR_KEY] } + + val accountIdFlow: Flow + get() = dataStore.data.map { preferences -> preferences[PREFERENCE_ACCOUNT_ID_KEY] } + + suspend fun isBiometricEnabledAsync(): Boolean { + return dataStore.data.first()[PREFERENCE_BIOMETRIC_KEY] ?: false + } + + suspend fun setTheme(theme: String) { + dataStore.edit { preferences -> + preferences[PREFERENCE_THEME_KEY] = theme + } + } + + suspend fun setFeedView(feedView: String) { + dataStore.edit { preferences -> + preferences[PREFERENCE_FEED_VIEW_KEY] = feedView + } + } + + suspend fun setMovieList(movieList: String) { + dataStore.edit { preferences -> + preferences[PREFERENCE_MOVIE_LIST_KEY] = movieList + } + } + + suspend fun setDynamicColors(isDynamicColors: Boolean) { + dataStore.edit { preferences -> + preferences[PREFERENCE_DYNAMIC_COLORS_KEY] = isDynamicColors + } + } + + suspend fun sessionId(): String? { + return dataStore.data.first()[PREFERENCE_SESSION_ID_KEY] + } + + suspend fun setSessionId(sessionId: String) { + dataStore.edit { preferences -> + preferences[PREFERENCE_SESSION_ID_KEY] = sessionId + } + } + + suspend fun removeSessionId() { + dataStore.edit { preferences -> + preferences.remove(PREFERENCE_SESSION_ID_KEY) + } + } + + suspend fun accountId(): Int { + return dataStore.data.first()[PREFERENCE_ACCOUNT_ID_KEY] ?: 0 + } + + suspend fun setAccountId(accountId: Int) { + dataStore.edit { preferences -> + preferences[PREFERENCE_ACCOUNT_ID_KEY] = accountId + } + } + + suspend fun removeAccountId() { + dataStore.edit { preferences -> + preferences.remove(PREFERENCE_ACCOUNT_ID_KEY) + } + } + + suspend fun accountExpireTime(): Long? { + return dataStore.data.first()[PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY] + } + + suspend fun setAccountExpireTime(expireTime: Long) { + dataStore.edit { preferences -> + preferences[PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY] = expireTime + } + } + + suspend fun notificationExpireTime(): Long? { + return dataStore.data.first()[PREFERENCE_NOTIFICATION_EXPIRE_TIME_KEY] + } + + suspend fun setNotificationExpireTime(expireTime: Long) { + dataStore.edit { preferences -> + preferences[PREFERENCE_NOTIFICATION_EXPIRE_TIME_KEY] = expireTime + } + } + + suspend fun setBiometricEnabled(enabled: Boolean) { + dataStore.edit { preferences -> + preferences[PREFERENCE_BIOMETRIC_KEY] = enabled + } + } + + suspend fun setPaletteKey(paletteKey: Int) { + dataStore.edit { preferences -> + preferences[PREFERENCE_PALETTE_KEY] = paletteKey + } + } + + suspend fun setSeedColor(seedColor: Int) { + dataStore.edit { preferences -> + preferences[PREFERENCE_SEED_COLOR_KEY] = seedColor + } + } + + private companion object { + private val PREFERENCE_THEME_KEY = stringPreferencesKey("theme") + private val PREFERENCE_FEED_VIEW_KEY = stringPreferencesKey("feed_view") + private val PREFERENCE_MOVIE_LIST_KEY = stringPreferencesKey("movie_list") + private val PREFERENCE_DYNAMIC_COLORS_KEY = booleanPreferencesKey("dynamic_colors") + private val PREFERENCE_SESSION_ID_KEY = stringPreferencesKey("session_id") + private val PREFERENCE_ACCOUNT_ID_KEY = intPreferencesKey("account_id") + private val PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY = longPreferencesKey("account_expire_time") + private val PREFERENCE_NOTIFICATION_EXPIRE_TIME_KEY = longPreferencesKey("notification_expire_time") + private val PREFERENCE_BIOMETRIC_KEY = booleanPreferencesKey("biometric") + private val PREFERENCE_PALETTE_KEY = intPreferencesKey("palette") + private val PREFERENCE_SEED_COLOR_KEY = intPreferencesKey("seed_color") + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt new file mode 100644 index 000000000..ed2227fb0 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.datastore.di + +import org.koin.core.module.Module + +internal expect val dataStoreKoinModule: Module \ No newline at end of file diff --git a/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/di/MoviesPreferencesKoinModule.kt b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/di/MoviesPreferencesKoinModule.kt new file mode 100644 index 000000000..9c4542609 --- /dev/null +++ b/core/persistence-kmp/src/commonMain/kotlin/org/michaelbel/movies/persistence/datastore/di/MoviesPreferencesKoinModule.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.persistence.datastore.di + +import org.koin.dsl.module +import org.michaelbel.movies.persistence.datastore.MoviesPreferences + +val moviesPreferencesKoinModule = module { + includes( + dataStoreKoinModule + ) + single { MoviesPreferences(get()) } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt new file mode 100644 index 000000000..4f6afb7d1 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/AccountPersistence.kt @@ -0,0 +1,18 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import org.michaelbel.movies.persistence.database.entity.AccountPojo + +actual class AccountPersistence internal constructor() { + + actual fun accountById(accountId: Int): Flow { + return emptyFlow() + } + + actual suspend fun insert(account: AccountPojo) {} + + actual suspend fun removeById(accountId: Int) {} +} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt new file mode 100644 index 000000000..cf00f0d25 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/ImagePersistence.kt @@ -0,0 +1,16 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import org.michaelbel.movies.persistence.database.entity.ImagePojo + +actual class ImagePersistence internal constructor() { + + actual fun imagesFlow(movieId: Int): Flow> { + return emptyFlow() + } + + actual suspend fun insert(images: List) {} +} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt new file mode 100644 index 000000000..edbb45481 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/MoviePersistence.kt @@ -0,0 +1,50 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini + +actual class MoviePersistence internal constructor() { + + actual fun pagingSource(movieList: String): PagingSource { + TODO("Not Implemented") + } + + actual fun moviesFlow(movieList: String, limit: Int): Flow> { + return emptyFlow() + } + + actual suspend fun movies(movieList: String, limit: Int): List { + return emptyList() + } + + actual suspend fun moviesMini(movieList: String, limit: Int): List { + return emptyList() + } + + actual suspend fun insertMovies(movies: List) {} + + actual suspend fun insertMovie(movie: MoviePojo) {} + + actual suspend fun removeMovies(movieList: String) {} + + actual suspend fun removeMovie(movieList: String, movieId: Int) {} + + actual suspend fun movieById(pagingKey: String, movieId: Int): MoviePojo? { + return null + } + + actual suspend fun maxPosition(movieList: String): Int? { + return null + } + + actual suspend fun isEmpty(movieList: String): Boolean { + return true + } + + actual suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) {} +} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt new file mode 100644 index 000000000..cb0288326 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/MoviesDatabase.kt @@ -0,0 +1,10 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +actual class MoviesDatabase internal constructor() { + + actual suspend fun withTransaction(block: suspend () -> R): R { + return block.invoke() + } +} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt new file mode 100644 index 000000000..fa25a75ac --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/PagingKeyPersistence.kt @@ -0,0 +1,20 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import org.michaelbel.movies.persistence.database.entity.PagingKeyPojo + +actual class PagingKeyPersistence internal constructor() { + + actual suspend fun page(pagingKey: String): Int? { + return null + } + + actual suspend fun totalPages(pagingKey: String): Int? { + return null + } + + actual suspend fun removePagingKey(pagingKey: String) {} + + actual suspend fun insertPagingKey(pagingKeyPojo: PagingKeyPojo) {} +} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt new file mode 100644 index 000000000..4033afe73 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/SuggestionPersistence.kt @@ -0,0 +1,18 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.persistence.database + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo + +actual class SuggestionPersistence internal constructor() { + + actual fun suggestionsFlow(): Flow> { + return emptyFlow() + } + + actual suspend fun insert(suggestions: List) {} + + actual suspend fun removeAll() {} +} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt new file mode 100644 index 000000000..f0cc6ce2f --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/DaoKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.dsl.module + +actual val daoKoinModule = module {} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt new file mode 100644 index 000000000..47469217c --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.dsl.module + +actual val databaseKoinModule = module {} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt new file mode 100644 index 000000000..825d064c6 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/MoviesDatabaseKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.dsl.module + +actual val moviesDatabaseKoinModule = module {} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt new file mode 100644 index 000000000..cea9dc0f1 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/database/di/PersistenceKoinModule.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.persistence.database.di + +import org.koin.dsl.module + +actual val persistenceKoinModule = module {} \ No newline at end of file diff --git a/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt new file mode 100644 index 000000000..71b617977 --- /dev/null +++ b/core/persistence-kmp/src/desktopMain/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreKoinModule.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.persistence.datastore.di + +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import okio.Path.Companion.toPath +import org.koin.dsl.module +import org.michaelbel.movies.persistence.datastore.DATA_STORE_NAME + +internal actual val dataStoreKoinModule = module { + single { + PreferenceDataStoreFactory.createWithPath( + migrations = emptyList(), + produceFile = { DATA_STORE_NAME.toPath() } + ) + } +} \ No newline at end of file diff --git a/core/persistence/build.gradle.kts b/core/persistence/build.gradle.kts deleted file mode 100644 index 5b8911327..000000000 --- a/core/persistence/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.persistence" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - buildConfig = true - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:network")) - implementation(libs.bundles.datastore) - api(libs.bundles.room) - ksp(libs.androidx.room.compiler) -} \ No newline at end of file diff --git a/core/persistence/src/main/AndroidManifest.xml b/core/persistence/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/persistence/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/AccountDao.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/AccountDao.kt deleted file mode 100644 index 3e5a856c5..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/AccountDao.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.michaelbel.movies.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.persistence.database.entity.AccountDb - -/** - * The Data Access Object for the [AccountDb] class. - */ -@Dao -interface AccountDao { - - @Query("SELECT * FROM accounts WHERE id = :accountId") - fun accountById(accountId: Int): Flow - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(account: AccountDb) - - @Query("DELETE FROM accounts WHERE id = :accountId") - suspend fun removeById(accountId: Int) -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseModule.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseModule.kt deleted file mode 100644 index de6c2f295..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseModule.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.michaelbel.movies.persistence.database.di - -import android.content.Context -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import org.michaelbel.movies.persistence.database.AppDatabase -import org.michaelbel.movies.persistence.database.dao.AccountDao -import org.michaelbel.movies.persistence.database.dao.ImageDao -import org.michaelbel.movies.persistence.database.dao.MovieDao -import org.michaelbel.movies.persistence.database.dao.PagingKeyDao -import org.michaelbel.movies.persistence.database.dao.SuggestionDao - -@Module -@InstallIn(SingletonComponent::class) -internal object DatabaseModule { - - @Provides - @Singleton - fun provideAppDatabase( - @ApplicationContext context: Context - ): AppDatabase = AppDatabase.getInstance(context) - - @Provides - fun provideMovieDao(appDatabase: AppDatabase): MovieDao = appDatabase.movieDao() - - @Provides - fun provideImagesDao(appDatabase: AppDatabase): ImageDao = appDatabase.imageDao() - - @Provides - fun provideAccountDao(appDatabase: AppDatabase): AccountDao = appDatabase.accountDao() - - @Provides - fun providePagingKeyDao(appDatabase: AppDatabase): PagingKeyDao = appDatabase.pagingKeyDao() - - @Provides - fun provideSuggestionDao(appDatabase: AppDatabase): SuggestionDao = appDatabase.suggestionDao() -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/AccountDb.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/AccountDb.kt deleted file mode 100644 index 2f7cf6e56..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/AccountDb.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.michaelbel.movies.persistence.database.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import org.jetbrains.annotations.NotNull - -@Entity(tableName = "accounts") -data class AccountDb( - @NotNull @PrimaryKey @ColumnInfo(name = "id") val accountId: Int, - @NotNull val avatarUrl: String, - @NotNull val language: String, - @NotNull val country: String, - @NotNull val name: String, - @NotNull val adult: Boolean, - @NotNull val username: String -) { - companion object { - val Empty: AccountDb = AccountDb( - accountId = 0, - avatarUrl = "", - language = "", - country = "", - name = "", - adult = false, - username = "" - ) - } -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt deleted file mode 100644 index f0864920e..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.michaelbel.movies.persistence.database.entity - -import androidx.room.Entity -import org.jetbrains.annotations.NotNull -import org.jetbrains.annotations.Nullable - -@Entity(tableName = "images", primaryKeys = ["movieId", "filePath"]) -data class ImageDb( - @NotNull val movieId: Int, - @NotNull val filePath: String, - @NotNull val type: Type, - @NotNull val width: Int, - @NotNull val height: Int, - @NotNull val aspectRatio: Float, - @NotNull val voteAverage: Float, - @NotNull val voteCount: Int, - @Nullable val lang: String?, - @NotNull val position: Int -) { - companion object { - val Empty: ImageDb = ImageDb( - movieId = 0, - filePath = "", - type = Type.BACKDROP, - width = 0, - height = 0, - aspectRatio = 0F, - voteAverage = 0F, - voteCount = 0, - lang = null, - position = 0 - ) - } - - enum class Type { - BACKDROP, - POSTER, - LOGO - } -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/MovieDb.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/MovieDb.kt deleted file mode 100644 index 9db4d43f3..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/MovieDb.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.michaelbel.movies.persistence.database.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import org.jetbrains.annotations.NotNull -import org.jetbrains.annotations.Nullable - -@Entity(tableName = "movies", primaryKeys = ["movieList", "id"]) -data class MovieDb( - @NotNull val movieList: String, - @NotNull val dateAdded: Long, - @Nullable val page: Int?, - @NotNull val position: Int, - @NotNull @ColumnInfo(name = "id") val movieId: Int, - @NotNull val overview: String, - @NotNull val posterPath: String, - @NotNull val backdropPath: String, - @NotNull val releaseDate: String, - @NotNull val title: String, - @NotNull val voteAverage: Float, - @Nullable val containerColor: Int?, - @Nullable val onContainerColor: Int? -) { - companion object { - const val MOVIES_LOCAL_LIST = "movies_local" - const val MOVIES_SEARCH_HISTORY = "movies_search_history" - - val Empty: MovieDb = MovieDb( - movieList = "", - dateAdded = 0L, - page = null, - position = 0, - movieId = 0, - overview = "", - posterPath = "", - backdropPath = "", - releaseDate = "", - title = "", - voteAverage = 0F, - containerColor = null, - onContainerColor = null - ) - } -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt deleted file mode 100644 index 833ca1525..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.michaelbel.movies.persistence.database.entity - -import androidx.room.Entity -import org.jetbrains.annotations.NotNull -import org.jetbrains.annotations.Nullable - -@Entity(tableName = "pagingkeys", primaryKeys = ["pagingKey"]) -data class PagingKeyDb( - @NotNull val pagingKey: String, - @Nullable val page: Int?, - @Nullable val totalPages: Int? -) \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionDb.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionDb.kt deleted file mode 100644 index 218a85328..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/SuggestionDb.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.persistence.database.entity - -import androidx.room.Entity -import org.jetbrains.annotations.NotNull - -@Entity(tableName = "suggestions", primaryKeys = ["title"]) -data class SuggestionDb( - @NotNull val title: String -) \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountDbKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountDbKtx.kt deleted file mode 100644 index a7d0bb70e..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/AccountDbKtx.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.michaelbel.movies.persistence.database.ktx - -import org.michaelbel.movies.persistence.database.entity.AccountDb - -private const val ACCOUNT_DEFAULT_LETTER = "A" -private const val SPACE_UNICODE = "\u0020" -private const val LETTERS_LIMIT = 2 -private const val FIRST_LETTER_INDEX = 1 - -val AccountDb.isEmpty: Boolean - get() = this == AccountDb.Empty - -val AccountDb?.orEmpty: AccountDb - get() = this ?: AccountDb.Empty - -val AccountDb.letters: String - get() = when { - name.isNotEmpty() -> { - val words: List = name.split(SPACE_UNICODE) - val letters: List = words.subList(0, LETTERS_LIMIT).map { word -> - word.substring(0, FIRST_LETTER_INDEX) - } - letters.joinToString( - separator = "", - limit = LETTERS_LIMIT - ) - } - username.isNotEmpty() -> username.substring(0, FIRST_LETTER_INDEX) - else -> ACCOUNT_DEFAULT_LETTER - } \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageDbKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageDbKtx.kt deleted file mode 100644 index 4f84f036b..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageDbKtx.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.michaelbel.movies.persistence.database.ktx - -import org.michaelbel.movies.network.formatImage -import org.michaelbel.movies.network.model.image.BackdropSize -import org.michaelbel.movies.network.model.image.LogoSize -import org.michaelbel.movies.network.model.image.PosterSize -import org.michaelbel.movies.persistence.database.entity.ImageDb - -val ImageDb.image: String - get() = when (type) { - ImageDb.Type.BACKDROP -> filePath.formatImage(BackdropSize.W300.size) - ImageDb.Type.POSTER -> filePath.formatImage(PosterSize.W92.size) - ImageDb.Type.LOGO -> filePath.formatImage(LogoSize.W45.size) - } - -val ImageDb.original: String - get() = when (type) { - ImageDb.Type.BACKDROP -> filePath.formatImage(BackdropSize.ORIGINAL.size) - ImageDb.Type.POSTER -> filePath.formatImage(PosterSize.ORIGINAL.size) - ImageDb.Type.LOGO -> filePath.formatImage(LogoSize.ORIGINAL.size) - } \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt deleted file mode 100644 index 92cbc6895..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.michaelbel.movies.persistence.database.ktx - -import org.michaelbel.movies.network.model.Image -import org.michaelbel.movies.persistence.database.entity.ImageDb - -fun Image.imageDb( - movieId: Int, - type: ImageDb.Type, - position: Int -): ImageDb { - return ImageDb( - movieId = movieId, - filePath = filePath, - type = type, - width = width, - height = height, - aspectRatio = aspectRatio, - voteAverage = voteAverage, - voteCount = voteCount, - lang = lang, - position = position - ) -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieDbKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieDbKtx.kt deleted file mode 100644 index ea8f8bf36..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieDbKtx.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.persistence.database.ktx - -import java.util.Locale -import org.michaelbel.movies.network.TMDB_MOVIE_URL -import org.michaelbel.movies.persistence.database.entity.MovieDb - -val MovieDb.isNotEmpty: Boolean - get() = this != MovieDb.Empty - -val MovieDb.url: String - get() = String.format(Locale.US, TMDB_MOVIE_URL, movieId) - -val MovieDb?.orEmpty: MovieDb - get() = this ?: MovieDb.Empty \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt deleted file mode 100644 index cd1dbd333..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.michaelbel.movies.persistence.database.ktx - -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.persistence.database.entity.MovieDb - -fun MovieResponse.movieDb(movieList: String, position: Int): MovieDb { - return MovieDb( - movieList = movieList, - dateAdded = System.currentTimeMillis(), - page = null, - position = position, - movieId = id, - overview = overview.orEmpty(), - posterPath = posterPath.orEmpty(), - backdropPath = backdropPath.orEmpty(), - releaseDate = releaseDate, - title = title, - voteAverage = voteAverage, - containerColor = null, - onContainerColor = null - ) -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/MoviesPreferences.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/MoviesPreferences.kt deleted file mode 100644 index b3ff92863..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/MoviesPreferences.kt +++ /dev/null @@ -1,120 +0,0 @@ -package org.michaelbel.movies.persistence.datastore - -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.intPreferencesKey -import androidx.datastore.preferences.core.longPreferencesKey -import androidx.datastore.preferences.core.stringPreferencesKey -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import javax.inject.Inject - -class MoviesPreferences @Inject constructor( - private val dataStore: DataStore -) { - - val themeFlow: Flow - get() = dataStore.data.map { preferences -> preferences[PREFERENCE_THEME_KEY] } - - val feedViewFlow: Flow - get() = dataStore.data.map { preferences -> preferences[PREFERENCE_FEED_VIEW_KEY] } - - val movieListFlow: Flow - get() = dataStore.data.map { preferences -> preferences[PREFERENCE_MOVIE_LIST_KEY] } - - val isDynamicColorsFlow: Flow - get() = dataStore.data.map { preferences -> preferences[PREFERENCE_DYNAMIC_COLORS_KEY] } - - val accountIdFlow: Flow - get() = dataStore.data.map { preferences -> preferences[PREFERENCE_ACCOUNT_ID_KEY] } - - suspend fun setTheme(theme: String) { - dataStore.edit { preferences -> - preferences[PREFERENCE_THEME_KEY] = theme - } - } - - suspend fun setFeedView(feedView: String) { - dataStore.edit { preferences -> - preferences[PREFERENCE_FEED_VIEW_KEY] = feedView - } - } - - suspend fun setMovieList(movieList: String) { - dataStore.edit { preferences -> - preferences[PREFERENCE_MOVIE_LIST_KEY] = movieList - } - } - - suspend fun setDynamicColors(isDynamicColors: Boolean) { - dataStore.edit { preferences -> - preferences[PREFERENCE_DYNAMIC_COLORS_KEY] = isDynamicColors - } - } - - suspend fun sessionId(): String? { - return dataStore.data.first()[PREFERENCE_SESSION_ID_KEY] - } - - suspend fun setSessionId(sessionId: String) { - dataStore.edit { preferences -> - preferences[PREFERENCE_SESSION_ID_KEY] = sessionId - } - } - - suspend fun removeSessionId() { - dataStore.edit { preferences -> - preferences.remove(PREFERENCE_SESSION_ID_KEY) - } - } - - suspend fun accountId(): Int? { - return dataStore.data.first()[PREFERENCE_ACCOUNT_ID_KEY] - } - - suspend fun setAccountId(accountId: Int) { - dataStore.edit { preferences -> - preferences[PREFERENCE_ACCOUNT_ID_KEY] = accountId - } - } - - suspend fun removeAccountId() { - dataStore.edit { preferences -> - preferences.remove(PREFERENCE_ACCOUNT_ID_KEY) - } - } - - suspend fun accountExpireTime(): Long? { - return dataStore.data.first()[PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY] - } - - suspend fun setAccountExpireTime(expireTime: Long) { - dataStore.edit { preferences -> - preferences[PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY] = expireTime - } - } - - suspend fun notificationExpireTime(): Long? { - return dataStore.data.first()[PREFERENCE_NOTIFICATION_EXPIRE_TIME_KEY] - } - - suspend fun setNotificationExpireTime(expireTime: Long) { - dataStore.edit { preferences -> - preferences[PREFERENCE_NOTIFICATION_EXPIRE_TIME_KEY] = expireTime - } - } - - private companion object { - private val PREFERENCE_THEME_KEY: Preferences.Key = stringPreferencesKey("theme") - private val PREFERENCE_FEED_VIEW_KEY: Preferences.Key = stringPreferencesKey("feed_view") - private val PREFERENCE_MOVIE_LIST_KEY: Preferences.Key = stringPreferencesKey("movie_list") - private val PREFERENCE_DYNAMIC_COLORS_KEY: Preferences.Key = booleanPreferencesKey("dynamic_colors") - private val PREFERENCE_SESSION_ID_KEY: Preferences.Key = stringPreferencesKey("session_id") - private val PREFERENCE_ACCOUNT_ID_KEY: Preferences.Key = intPreferencesKey("account_id") - private val PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY: Preferences.Key = longPreferencesKey("account_expire_time") - private val PREFERENCE_NOTIFICATION_EXPIRE_TIME_KEY: Preferences.Key = longPreferencesKey("notification_expire_time") - } -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreModule.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreModule.kt deleted file mode 100644 index 2582bcfaf..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/di/DataStoreModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.michaelbel.movies.persistence.datastore.di - -import android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import org.michaelbel.movies.persistence.datastore.ktx.dataStore -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object DataStoreModule { - - @Provides - @Singleton - fun provideDataStore( - @ApplicationContext context: Context - ): DataStore = context.dataStore -} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/ktx/DataStoreKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/ktx/DataStoreKtx.kt deleted file mode 100644 index 1173162ce..000000000 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/datastore/ktx/DataStoreKtx.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.persistence.datastore.ktx - -import android.content.Context -import androidx.datastore.preferences.SharedPreferencesMigration -import androidx.datastore.preferences.preferencesDataStore - -private const val USER_PREFERENCES_NAME = "user_preferences" - -internal val Context.dataStore by preferencesDataStore( - name = USER_PREFERENCES_NAME, - produceMigrations = { context -> - listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME)) - } -) \ No newline at end of file diff --git a/core/platform-services/gms/.gitignore b/core/platform-services/foss-kmp/.gitignore similarity index 100% rename from core/platform-services/gms/.gitignore rename to core/platform-services/foss-kmp/.gitignore diff --git a/core/platform-services/foss-kmp/build.gradle.kts b/core/platform-services/foss-kmp/build.gradle.kts new file mode 100644 index 000000000..bba21e9c0 --- /dev/null +++ b/core/platform-services/foss-kmp/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:platform-services:interactor-kmp")) + } + } +} + +android { + namespace = "org.michaelbel.movies.platform.foss_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt new file mode 100644 index 000000000..e637b3812 --- /dev/null +++ b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.platform.impl.analytics + +import android.os.Bundle +import org.michaelbel.movies.platform.analytics.AnalyticsService + +class AnalyticsServiceImpl: AnalyticsService { + + override val screenView: String = "" + + override val screenName: String = "" + + override fun logEvent(name: String, params: Bundle) {} +} \ No newline at end of file diff --git a/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt new file mode 100644 index 000000000..a24bfd8bc --- /dev/null +++ b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.platform.impl.app + +import org.michaelbel.movies.platform.Flavor +import org.michaelbel.movies.platform.app.AppService + +class AppServiceImpl: AppService { + + override val flavor: Flavor = Flavor.Foss + + override val isPlayServicesAvailable: Boolean = false + + override fun installApp() {} +} \ No newline at end of file diff --git a/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt new file mode 100644 index 000000000..dcac7f0cb --- /dev/null +++ b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt @@ -0,0 +1,14 @@ +package org.michaelbel.movies.platform.impl.config + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.platform.config.ConfigService + +class ConfigServiceImpl: ConfigService { + + override fun fetchAndActivate() {} + + override fun getBooleanFlow(name: String): Flow { + return flowOf(false) + } +} \ No newline at end of file diff --git a/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt new file mode 100644 index 000000000..258d9cd28 --- /dev/null +++ b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.platform.impl.crashlytics + +import org.michaelbel.movies.platform.crashlytics.CrashlyticsService + +class CrashlyticsServiceImpl: CrashlyticsService { + + override fun recordException(priority: Int, tag: String, message: String, exception: Throwable) {} +} \ No newline at end of file diff --git a/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt new file mode 100644 index 000000000..b1df8ef26 --- /dev/null +++ b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.platform.impl.messaging + +import org.michaelbel.movies.platform.messaging.MessagingService +import org.michaelbel.movies.platform.messaging.TokenListener + +class MessagingServiceImpl: MessagingService { + + override fun setTokenListener(listener: TokenListener) {} +} \ No newline at end of file diff --git a/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt new file mode 100644 index 000000000..1b6bb8f3e --- /dev/null +++ b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt @@ -0,0 +1,10 @@ +package org.michaelbel.movies.platform.impl.review + +import android.app.Activity +import javax.inject.Inject +import org.michaelbel.movies.platform.review.ReviewService + +class ReviewServiceImpl: ReviewService { + + override fun requestReview(activity: Activity) {} +} \ No newline at end of file diff --git a/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt new file mode 100644 index 000000000..a66b96504 --- /dev/null +++ b/core/platform-services/foss-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt @@ -0,0 +1,12 @@ +package org.michaelbel.movies.platform.impl.update + +import android.app.Activity +import org.michaelbel.movies.platform.update.UpdateListener +import org.michaelbel.movies.platform.update.UpdateService + +class UpdateServiceImpl: UpdateService { + + override fun setUpdateAvailableListener(listener: UpdateListener) {} + + override fun startUpdate(activity: Activity) {} +} \ No newline at end of file diff --git a/core/platform-services/foss/build.gradle.kts b/core/platform-services/foss/build.gradle.kts deleted file mode 100644 index 68e31693b..000000000 --- a/core/platform-services/foss/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.platform.foss" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:platform-services:interactor")) -} \ No newline at end of file diff --git a/core/platform-services/foss/src/main/AndroidManifest.xml b/core/platform-services/foss/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/platform-services/foss/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt b/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt deleted file mode 100644 index 9013c61aa..000000000 --- a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.platform.impl.analytics - -import android.os.Bundle -import javax.inject.Inject -import org.michaelbel.movies.platform.analytics.AnalyticsService - -class AnalyticsServiceImpl @Inject constructor(): AnalyticsService { - - override val screenView: String = "" - - override val screenName: String = "" - - override fun logEvent(name: String, params: Bundle) {} -} \ No newline at end of file diff --git a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt b/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt deleted file mode 100644 index ca63aa1e4..000000000 --- a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.platform.impl.app - -import javax.inject.Inject -import org.michaelbel.movies.platform.Flavor -import org.michaelbel.movies.platform.app.AppService - -class AppServiceImpl @Inject constructor(): AppService { - - override val flavor: Flavor = Flavor.Foss - - override val isPlayServicesAvailable: Boolean = false - - override fun installApp() {} -} \ No newline at end of file diff --git a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt b/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt deleted file mode 100644 index be0732db5..000000000 --- a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.michaelbel.movies.platform.impl.config - -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import org.michaelbel.movies.platform.config.ConfigService - -class ConfigServiceImpl @Inject constructor(): ConfigService { - - override fun fetchAndActivate() {} - - override fun getBooleanFlow(name: String): Flow { - return flowOf(false) - } -} \ No newline at end of file diff --git a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt b/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt deleted file mode 100644 index eaaebcc5b..000000000 --- a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.platform.impl.crashlytics - -import javax.inject.Inject -import org.michaelbel.movies.platform.crashlytics.CrashlyticsService - -class CrashlyticsServiceImpl @Inject constructor(): CrashlyticsService { - - override fun recordException(priority: Int, tag: String, message: String, exception: Throwable) {} -} \ No newline at end of file diff --git a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt b/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt deleted file mode 100644 index 59b158bb2..000000000 --- a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.michaelbel.movies.platform.impl.messaging - -import javax.inject.Inject -import org.michaelbel.movies.platform.messaging.MessagingService -import org.michaelbel.movies.platform.messaging.TokenListener - -class MessagingServiceImpl @Inject constructor(): MessagingService { - - override fun setTokenListener(listener: TokenListener) {} -} \ No newline at end of file diff --git a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt deleted file mode 100644 index 9da6ffa79..000000000 --- a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.michaelbel.movies.platform.impl.review - -import android.app.Activity -import javax.inject.Inject -import org.michaelbel.movies.platform.review.ReviewService - -class ReviewServiceImpl @Inject constructor(): ReviewService { - - override fun requestReview(activity: Activity) {} -} \ No newline at end of file diff --git a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt deleted file mode 100644 index c3d3163db..000000000 --- a/core/platform-services/foss/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.michaelbel.movies.platform.impl.update - -import android.app.Activity -import javax.inject.Inject -import org.michaelbel.movies.platform.update.UpdateListener -import org.michaelbel.movies.platform.update.UpdateService - -class UpdateServiceImpl @Inject constructor(): UpdateService { - - override fun setUpdateAvailableListener(listener: UpdateListener) {} - - override fun startUpdate(activity: Activity) {} -} \ No newline at end of file diff --git a/core/platform-services/hms/.gitignore b/core/platform-services/gms-kmp/.gitignore similarity index 100% rename from core/platform-services/hms/.gitignore rename to core/platform-services/gms-kmp/.gitignore diff --git a/core/platform-services/gms-kmp/build.gradle.kts b/core/platform-services/gms-kmp/build.gradle.kts new file mode 100644 index 000000000..b919b269b --- /dev/null +++ b/core/platform-services/gms-kmp/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:platform-services:interactor-kmp")) + implementation(project(":core:notifications-kmp")) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + api(libs.bundles.google.firebase) + api(libs.bundles.google.services) + api(libs.google.play.core.ktx) + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.platform.gms_kmp" + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/AndroidManifest.xml b/core/platform-services/gms-kmp/src/androidMain/AndroidManifest.xml new file mode 100644 index 000000000..491544b88 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/FirebaseKoinModule.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/FirebaseKoinModule.kt new file mode 100644 index 000000000..4f61b2292 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/FirebaseKoinModule.kt @@ -0,0 +1,33 @@ +package org.michaelbel.movies.platform.impl + +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.google.firebase.ktx.Firebase +import com.google.firebase.messaging.FirebaseMessaging +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import org.koin.dsl.module + +private const val FETCH_INTERVAL_IN_SECONDS = 5L + +val firebaseKoinModule = module { + single { Firebase.analytics } + single { FirebaseCrashlytics.getInstance() } + single { FirebaseMessaging.getInstance() } + single { + val configSettings: FirebaseRemoteConfigSettings = remoteConfigSettings { + fetchTimeoutInSeconds = FETCH_INTERVAL_IN_SECONDS + minimumFetchIntervalInSeconds = FETCH_INTERVAL_IN_SECONDS + } + val defaults: Map = mapOf( + "param_settings_icon_visible" to true + ) + val firebaseRemoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig.apply { + setConfigSettingsAsync(configSettings) + setDefaultsAsync(defaults) + } + firebaseRemoteConfig + } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/GoogleApiKoinModule.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/GoogleApiKoinModule.kt new file mode 100644 index 000000000..7113dffc3 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/GoogleApiKoinModule.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.platform.impl + +import com.google.android.gms.common.GoogleApiAvailability +import org.koin.dsl.module + +val googleApiKoinModule = module { + single { GoogleApiAvailability.getInstance() } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/PlayKoinModule.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/PlayKoinModule.kt new file mode 100644 index 000000000..e11fe3d32 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/PlayKoinModule.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.platform.impl + +import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.review.ReviewManagerFactory +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module +import org.michaelbel.movies.platform.impl.update.InAppUpdate + +val playKoinModule = module { + single { ReviewManagerFactory.create(androidContext()) } + single { AppUpdateManagerFactory.create(androidContext()) } + single { InAppUpdate(get(), get()) } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt new file mode 100644 index 000000000..96334586a --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.platform.impl.analytics + +import android.os.Bundle +import com.google.firebase.analytics.FirebaseAnalytics +import org.michaelbel.movies.platform.analytics.AnalyticsService + +class AnalyticsServiceImpl( + private val firebaseAnalytics: FirebaseAnalytics +): AnalyticsService { + + override val screenView: String = FirebaseAnalytics.Event.SCREEN_VIEW + + override val screenName: String = FirebaseAnalytics.Param.SCREEN_NAME + + override fun logEvent(name: String, params: Bundle) { + firebaseAnalytics.logEvent(name, params) + } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt new file mode 100644 index 000000000..df66fe6e7 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.platform.impl.app + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.firebase.FirebaseApp +import org.michaelbel.movies.platform.Flavor +import org.michaelbel.movies.platform.app.AppService + +class AppServiceImpl( + private val context: Context, + private val googleApiAvailability: GoogleApiAvailability +): AppService { + + override val flavor: Flavor = Flavor.Gms + + override val isPlayServicesAvailable: Boolean + get() { + val status: Int = googleApiAvailability.isGooglePlayServicesAvailable(context) + return status == ConnectionResult.SUCCESS + } + + override fun installApp() { + FirebaseApp.initializeApp(context) + } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt new file mode 100644 index 000000000..7f9c37a5d --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt @@ -0,0 +1,19 @@ +package org.michaelbel.movies.platform.impl.config + +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.platform.config.ConfigService + +class ConfigServiceImpl( + private val firebaseRemoteConfig: FirebaseRemoteConfig +): ConfigService { + + override fun fetchAndActivate() { + firebaseRemoteConfig.fetchAndActivate() + } + + override fun getBooleanFlow(name: String): Flow { + return flowOf(firebaseRemoteConfig.getBoolean(name)) + } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt new file mode 100644 index 000000000..c542b3979 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt @@ -0,0 +1,24 @@ +package org.michaelbel.movies.platform.impl.crashlytics + +import com.google.firebase.crashlytics.FirebaseCrashlytics +import org.michaelbel.movies.platform.crashlytics.CrashlyticsService + +class CrashlyticsServiceImpl( + private val firebaseCrashlytics: FirebaseCrashlytics +): CrashlyticsService { + + override fun recordException(priority: Int, tag: String, message: String, exception: Throwable) { + firebaseCrashlytics.run { + setCustomKey(CRASHLYTICS_KEY_PRIORITY, priority) + setCustomKey(CRASHLYTICS_KEY_TAG, tag) + setCustomKey(CRASHLYTICS_KEY_MESSAGE, message) + recordException(exception) + } + } + + private companion object { + private const val CRASHLYTICS_KEY_PRIORITY = "priority" + private const val CRASHLYTICS_KEY_TAG = "tag" + private const val CRASHLYTICS_KEY_MESSAGE = "message" + } +} \ No newline at end of file diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt new file mode 100644 index 000000000..2cb356b2e --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt @@ -0,0 +1,17 @@ +package org.michaelbel.movies.platform.impl.messaging + +import com.google.firebase.messaging.FirebaseMessaging +import org.michaelbel.movies.platform.messaging.MessagingService +import org.michaelbel.movies.platform.messaging.TokenListener + +class MessagingServiceImpl( + private val firebaseMessaging: FirebaseMessaging +): MessagingService { + + override fun setTokenListener(listener: TokenListener) { + firebaseMessaging.token.addOnCompleteListener { task -> + val token: String = task.result + listener.onNewToken(token) + } + } +} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/ktx/RemoteMessageKtx.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/ktx/RemoteMessageKtx.kt similarity index 100% rename from core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/ktx/RemoteMessageKtx.kt rename to core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/ktx/RemoteMessageKtx.kt diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/service/MoviesMessagingService.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/service/MoviesMessagingService.kt similarity index 79% rename from core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/service/MoviesMessagingService.kt rename to core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/service/MoviesMessagingService.kt index 1b10195a7..523dfe9b2 100644 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/service/MoviesMessagingService.kt +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/service/MoviesMessagingService.kt @@ -2,19 +2,16 @@ package org.michaelbel.movies.platform.impl.messaging.service import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject +import org.koin.android.ext.android.inject import org.michaelbel.movies.notifications.NotificationClient import org.michaelbel.movies.platform.impl.messaging.ktx.mapToMoviesPush /** * See [Receive messages in an Android app](https://firebase.google.com/docs/cloud-messaging/android/receive) */ -@AndroidEntryPoint internal class MoviesMessagingService: FirebaseMessagingService() { - @Inject - lateinit var notificationClient: NotificationClient + private val notificationClient: NotificationClient by inject() override fun onMessageReceived(message: RemoteMessage) { notificationClient.send(message.mapToMoviesPush) diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt new file mode 100644 index 000000000..7dd13a636 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.platform.impl.review + +import android.app.Activity +import com.google.android.play.core.review.ReviewManager +import org.michaelbel.movies.platform.review.ReviewService + +class ReviewServiceImpl( + private val reviewManager: ReviewManager +): ReviewService { + + override fun requestReview(activity: Activity) { + reviewManager.requestReviewFlow().addOnCompleteListener { task -> + if (task.isSuccessful) { + reviewManager.launchReviewFlow(activity, task.result) + } + } + } +} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/InAppUpdate.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/InAppUpdate.kt similarity index 96% rename from core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/InAppUpdate.kt rename to core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/InAppUpdate.kt index a174266b7..cb0f7bc69 100644 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/InAppUpdate.kt +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/InAppUpdate.kt @@ -9,10 +9,9 @@ import com.google.android.play.core.install.model.AppUpdateType import com.google.android.play.core.install.model.InstallErrorCode import com.google.android.play.core.install.model.UpdateAvailability import com.google.android.play.core.tasks.Task -import javax.inject.Inject import org.michaelbel.movies.platform.app.AppService -class InAppUpdate @Inject constructor( +class InAppUpdate( private val appUpdateManager: AppUpdateManager, appService: AppService ) { diff --git a/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt new file mode 100644 index 000000000..f8a9949e0 --- /dev/null +++ b/core/platform-services/gms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.platform.impl.update + +import android.app.Activity +import org.michaelbel.movies.platform.update.UpdateListener +import org.michaelbel.movies.platform.update.UpdateService + +class UpdateServiceImpl( + private val inAppUpdate: InAppUpdate +): UpdateService { + + override fun setUpdateAvailableListener(listener: UpdateListener) { + inAppUpdate.onUpdateAvailableListener = { updateAvailable -> + listener.onAvailable(updateAvailable) + } + } + + override fun startUpdate(activity: Activity) { + inAppUpdate.startUpdateFlow(activity) + } +} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/res/drawable/ic_movie_filter_24.xml b/core/platform-services/gms-kmp/src/androidMain/res/drawable/ic_movie_filter_24.xml similarity index 100% rename from core/platform-services/gms/src/main/res/drawable/ic_movie_filter_24.xml rename to core/platform-services/gms-kmp/src/androidMain/res/drawable/ic_movie_filter_24.xml diff --git a/core/notifications/src/main/res/values-night/colors.xml b/core/platform-services/gms-kmp/src/androidMain/res/values-night/colors.xml similarity index 100% rename from core/notifications/src/main/res/values-night/colors.xml rename to core/platform-services/gms-kmp/src/androidMain/res/values-night/colors.xml diff --git a/core/platform-services/gms/src/main/res/values-ru/strings.xml b/core/platform-services/gms-kmp/src/androidMain/res/values-ru/strings.xml similarity index 100% rename from core/platform-services/gms/src/main/res/values-ru/strings.xml rename to core/platform-services/gms-kmp/src/androidMain/res/values-ru/strings.xml diff --git a/core/notifications/src/main/res/values/colors.xml b/core/platform-services/gms-kmp/src/androidMain/res/values/colors.xml similarity index 100% rename from core/notifications/src/main/res/values/colors.xml rename to core/platform-services/gms-kmp/src/androidMain/res/values/colors.xml diff --git a/core/platform-services/gms/src/main/res/values/strings.xml b/core/platform-services/gms-kmp/src/androidMain/res/values/strings.xml similarity index 100% rename from core/platform-services/gms/src/main/res/values/strings.xml rename to core/platform-services/gms-kmp/src/androidMain/res/values/strings.xml diff --git a/core/platform-services/gms/build.gradle.kts b/core/platform-services/gms/build.gradle.kts deleted file mode 100644 index f62c39ba2..000000000 --- a/core/platform-services/gms/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.platform.gms" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:platform-services:interactor")) - implementation(project(":core:notifications")) - api(libs.bundles.firebase) - api(libs.bundles.gms) - api(libs.play.core.ktx) -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/AndroidManifest.xml b/core/platform-services/gms/src/main/AndroidManifest.xml deleted file mode 100644 index 0dbca8775..000000000 --- a/core/platform-services/gms/src/main/AndroidManifest.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/FirebaseModule.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/FirebaseModule.kt deleted file mode 100644 index f5e0ebc2d..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/FirebaseModule.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.michaelbel.movies.platform.impl - -import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.analytics.ktx.analytics -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.google.firebase.ktx.Firebase -import com.google.firebase.messaging.FirebaseMessaging -import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings -import com.google.firebase.remoteconfig.ktx.remoteConfig -import com.google.firebase.remoteconfig.ktx.remoteConfigSettings -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object FirebaseModule { - - @Provides - @Singleton - fun provideFirebaseAnalytics(): FirebaseAnalytics = Firebase.analytics - - @Provides - @Singleton - fun provideFirebaseCrashlytics(): FirebaseCrashlytics = FirebaseCrashlytics.getInstance() - - @Provides - @Singleton - fun provideFirebaseMessaging(): FirebaseMessaging = FirebaseMessaging.getInstance() - - @Provides - fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig { - val configSettings: FirebaseRemoteConfigSettings = remoteConfigSettings { - fetchTimeoutInSeconds = FETCH_INTERVAL_IN_SECONDS - minimumFetchIntervalInSeconds = FETCH_INTERVAL_IN_SECONDS - } - val defaults: Map = mapOf( - "param_settings_icon_visible" to true - ) - val firebaseRemoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig.apply { - setConfigSettingsAsync(configSettings) - setDefaultsAsync(defaults) - } - return firebaseRemoteConfig - } - - private const val FETCH_INTERVAL_IN_SECONDS = 5L -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/GoogleApiModule.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/GoogleApiModule.kt deleted file mode 100644 index 6571e89e6..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/GoogleApiModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.michaelbel.movies.platform.impl - -import com.google.android.gms.common.GoogleApiAvailability -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object GoogleApiModule { - - @Provides - @Singleton - fun provideGoogleApiAvailability(): GoogleApiAvailability = GoogleApiAvailability.getInstance() -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/PlayModule.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/PlayModule.kt deleted file mode 100644 index 818954cec..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/PlayModule.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.michaelbel.movies.platform.impl - -import android.content.Context -import com.google.android.play.core.appupdate.AppUpdateManager -import com.google.android.play.core.appupdate.AppUpdateManagerFactory -import com.google.android.play.core.review.ReviewManager -import com.google.android.play.core.review.ReviewManagerFactory -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -internal object PlayModule { - - @Provides - fun provideReviewManager( - @ApplicationContext context: Context - ): ReviewManager = ReviewManagerFactory.create(context) - - @Provides - fun provideAppUpdateManager( - @ApplicationContext context: Context - ): AppUpdateManager = AppUpdateManagerFactory.create(context) -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt deleted file mode 100644 index 2c2b3f4fd..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.michaelbel.movies.platform.impl.analytics - -import android.os.Bundle -import com.google.firebase.analytics.FirebaseAnalytics -import javax.inject.Inject -import org.michaelbel.movies.platform.analytics.AnalyticsService - -class AnalyticsServiceImpl @Inject constructor( - private val firebaseAnalytics: FirebaseAnalytics -): AnalyticsService { - - override val screenView: String = FirebaseAnalytics.Event.SCREEN_VIEW - - override val screenName: String = FirebaseAnalytics.Param.SCREEN_NAME - - override fun logEvent(name: String, params: Bundle) { - firebaseAnalytics.logEvent(name, params) - } -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt deleted file mode 100644 index 031eb7234..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.michaelbel.movies.platform.impl.app - -import android.content.Context -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability -import com.google.firebase.FirebaseApp -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject -import org.michaelbel.movies.platform.Flavor -import org.michaelbel.movies.platform.app.AppService - -class AppServiceImpl @Inject constructor( - @ApplicationContext private val context: Context, - private val googleApiAvailability: GoogleApiAvailability -): AppService { - - override val flavor: Flavor = Flavor.Gms - - override val isPlayServicesAvailable: Boolean - get() { - val status: Int = googleApiAvailability.isGooglePlayServicesAvailable(context) - return status == ConnectionResult.SUCCESS - } - - override fun installApp() { - FirebaseApp.initializeApp(context) - } -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt deleted file mode 100644 index 604a31883..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.michaelbel.movies.platform.impl.config - -import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import org.michaelbel.movies.platform.config.ConfigService - -class ConfigServiceImpl @Inject constructor( - private val firebaseRemoteConfig: FirebaseRemoteConfig -): ConfigService { - - override fun fetchAndActivate() { - firebaseRemoteConfig.fetchAndActivate() - } - - override fun getBooleanFlow(name: String): Flow { - return flowOf(firebaseRemoteConfig.getBoolean(name)) - } -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt deleted file mode 100644 index b875ef51e..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.michaelbel.movies.platform.impl.crashlytics - -import com.google.firebase.crashlytics.FirebaseCrashlytics -import javax.inject.Inject -import org.michaelbel.movies.platform.crashlytics.CrashlyticsService - -class CrashlyticsServiceImpl @Inject constructor( - private val firebaseCrashlytics: FirebaseCrashlytics -): CrashlyticsService { - - override fun recordException(priority: Int, tag: String, message: String, exception: Throwable) { - firebaseCrashlytics.run { - setCustomKey(CRASHLYTICS_KEY_PRIORITY, priority) - setCustomKey(CRASHLYTICS_KEY_TAG, tag) - setCustomKey(CRASHLYTICS_KEY_MESSAGE, message) - recordException(exception) - } - } - - private companion object { - private const val CRASHLYTICS_KEY_PRIORITY = "priority" - private const val CRASHLYTICS_KEY_TAG = "tag" - private const val CRASHLYTICS_KEY_MESSAGE = "message" - } -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt deleted file mode 100644 index 7630a4d3d..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.platform.impl.messaging - -import com.google.firebase.messaging.FirebaseMessaging -import javax.inject.Inject -import org.michaelbel.movies.platform.messaging.MessagingService -import org.michaelbel.movies.platform.messaging.TokenListener - -class MessagingServiceImpl @Inject constructor( - private val firebaseMessaging: FirebaseMessaging -): MessagingService { - - override fun setTokenListener(listener: TokenListener) { - firebaseMessaging.token.addOnCompleteListener { task -> - val token: String = task.result - listener.onNewToken(token) - } - } -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt deleted file mode 100644 index fac1765fb..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.michaelbel.movies.platform.impl.review - -import android.app.Activity -import com.google.android.play.core.review.ReviewManager -import javax.inject.Inject -import org.michaelbel.movies.platform.review.ReviewService - -class ReviewServiceImpl @Inject constructor( - private val reviewManager: ReviewManager -): ReviewService { - - override fun requestReview(activity: Activity) { - reviewManager.requestReviewFlow().addOnCompleteListener { task -> - if (task.isSuccessful) { - reviewManager.launchReviewFlow(activity, task.result) - } - } - } -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt deleted file mode 100644 index 518d07915..000000000 --- a/core/platform-services/gms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.michaelbel.movies.platform.impl.update - -import android.app.Activity -import javax.inject.Inject -import org.michaelbel.movies.platform.update.UpdateListener -import org.michaelbel.movies.platform.update.UpdateService - -class UpdateServiceImpl @Inject constructor( - private val inAppUpdate: InAppUpdate -): UpdateService { - - override fun setUpdateAvailableListener(listener: UpdateListener) { - inAppUpdate.onUpdateAvailableListener = { updateAvailable -> - listener.onAvailable(updateAvailable) - } - } - - override fun startUpdate(activity: Activity) { - inAppUpdate.startUpdateFlow(activity) - } -} \ No newline at end of file diff --git a/core/platform-services/gms/src/main/res/values-night/colors.xml b/core/platform-services/gms/src/main/res/values-night/colors.xml deleted file mode 100644 index 3ef9e0855..000000000 --- a/core/platform-services/gms/src/main/res/values-night/colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #D0BCFF - \ No newline at end of file diff --git a/core/platform-services/gms/src/main/res/values/colors.xml b/core/platform-services/gms/src/main/res/values/colors.xml deleted file mode 100644 index edecc4ddc..000000000 --- a/core/platform-services/gms/src/main/res/values/colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #6750A4 - \ No newline at end of file diff --git a/core/platform-services/inject/.gitignore b/core/platform-services/hms-kmp/.gitignore similarity index 100% rename from core/platform-services/inject/.gitignore rename to core/platform-services/hms-kmp/.gitignore diff --git a/core/platform-services/hms-kmp/build.gradle.kts b/core/platform-services/hms-kmp/build.gradle.kts new file mode 100644 index 000000000..c24efe12c --- /dev/null +++ b/core/platform-services/hms-kmp/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:platform-services:interactor-kmp")) + } + } +} + +android { + namespace = "org.michaelbel.movies.platform.hms_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt new file mode 100644 index 000000000..e637b3812 --- /dev/null +++ b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.platform.impl.analytics + +import android.os.Bundle +import org.michaelbel.movies.platform.analytics.AnalyticsService + +class AnalyticsServiceImpl: AnalyticsService { + + override val screenView: String = "" + + override val screenName: String = "" + + override fun logEvent(name: String, params: Bundle) {} +} \ No newline at end of file diff --git a/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt new file mode 100644 index 000000000..3d5ee2513 --- /dev/null +++ b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt @@ -0,0 +1,14 @@ +package org.michaelbel.movies.platform.impl.app + +import org.michaelbel.movies.platform.Flavor +import org.michaelbel.movies.platform.app.AppService + +class AppServiceImpl: AppService { + + override val flavor: Flavor = Flavor.Hms + + override val isPlayServicesAvailable: Boolean + get() = false + + override fun installApp() {} +} \ No newline at end of file diff --git a/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt new file mode 100644 index 000000000..dcac7f0cb --- /dev/null +++ b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt @@ -0,0 +1,14 @@ +package org.michaelbel.movies.platform.impl.config + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.platform.config.ConfigService + +class ConfigServiceImpl: ConfigService { + + override fun fetchAndActivate() {} + + override fun getBooleanFlow(name: String): Flow { + return flowOf(false) + } +} \ No newline at end of file diff --git a/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt new file mode 100644 index 000000000..258d9cd28 --- /dev/null +++ b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.platform.impl.crashlytics + +import org.michaelbel.movies.platform.crashlytics.CrashlyticsService + +class CrashlyticsServiceImpl: CrashlyticsService { + + override fun recordException(priority: Int, tag: String, message: String, exception: Throwable) {} +} \ No newline at end of file diff --git a/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt new file mode 100644 index 000000000..b1df8ef26 --- /dev/null +++ b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.platform.impl.messaging + +import org.michaelbel.movies.platform.messaging.MessagingService +import org.michaelbel.movies.platform.messaging.TokenListener + +class MessagingServiceImpl: MessagingService { + + override fun setTokenListener(listener: TokenListener) {} +} \ No newline at end of file diff --git a/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt new file mode 100644 index 000000000..7bfaa913f --- /dev/null +++ b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.platform.impl.review + +import android.app.Activity +import org.michaelbel.movies.platform.review.ReviewService + +class ReviewServiceImpl: ReviewService { + + override fun requestReview(activity: Activity) {} +} \ No newline at end of file diff --git a/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt new file mode 100644 index 000000000..a66b96504 --- /dev/null +++ b/core/platform-services/hms-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt @@ -0,0 +1,12 @@ +package org.michaelbel.movies.platform.impl.update + +import android.app.Activity +import org.michaelbel.movies.platform.update.UpdateListener +import org.michaelbel.movies.platform.update.UpdateService + +class UpdateServiceImpl: UpdateService { + + override fun setUpdateAvailableListener(listener: UpdateListener) {} + + override fun startUpdate(activity: Activity) {} +} \ No newline at end of file diff --git a/core/platform-services/hms/build.gradle.kts b/core/platform-services/hms/build.gradle.kts deleted file mode 100644 index 7b473a4f6..000000000 --- a/core/platform-services/hms/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.platform.hms" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:platform-services:interactor")) -} \ No newline at end of file diff --git a/core/platform-services/hms/src/main/AndroidManifest.xml b/core/platform-services/hms/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/platform-services/hms/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt b/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt deleted file mode 100644 index 9013c61aa..000000000 --- a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/analytics/AnalyticsServiceImpl.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.platform.impl.analytics - -import android.os.Bundle -import javax.inject.Inject -import org.michaelbel.movies.platform.analytics.AnalyticsService - -class AnalyticsServiceImpl @Inject constructor(): AnalyticsService { - - override val screenView: String = "" - - override val screenName: String = "" - - override fun logEvent(name: String, params: Bundle) {} -} \ No newline at end of file diff --git a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt b/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt deleted file mode 100644 index f6a881c95..000000000 --- a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/app/AppServiceImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.michaelbel.movies.platform.impl.app - -import javax.inject.Inject -import org.michaelbel.movies.platform.Flavor -import org.michaelbel.movies.platform.app.AppService - -class AppServiceImpl @Inject constructor(): AppService { - - override val flavor: Flavor = Flavor.Hms - - override val isPlayServicesAvailable: Boolean - get() = false - - override fun installApp() {} -} \ No newline at end of file diff --git a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt b/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt deleted file mode 100644 index be0732db5..000000000 --- a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/config/ConfigServiceImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.michaelbel.movies.platform.impl.config - -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import org.michaelbel.movies.platform.config.ConfigService - -class ConfigServiceImpl @Inject constructor(): ConfigService { - - override fun fetchAndActivate() {} - - override fun getBooleanFlow(name: String): Flow { - return flowOf(false) - } -} \ No newline at end of file diff --git a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt b/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt deleted file mode 100644 index eaaebcc5b..000000000 --- a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/crashlytics/CrashlyticsServiceImpl.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.platform.impl.crashlytics - -import javax.inject.Inject -import org.michaelbel.movies.platform.crashlytics.CrashlyticsService - -class CrashlyticsServiceImpl @Inject constructor(): CrashlyticsService { - - override fun recordException(priority: Int, tag: String, message: String, exception: Throwable) {} -} \ No newline at end of file diff --git a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt b/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt deleted file mode 100644 index 59b158bb2..000000000 --- a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/messaging/MessagingServiceImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.michaelbel.movies.platform.impl.messaging - -import javax.inject.Inject -import org.michaelbel.movies.platform.messaging.MessagingService -import org.michaelbel.movies.platform.messaging.TokenListener - -class MessagingServiceImpl @Inject constructor(): MessagingService { - - override fun setTokenListener(listener: TokenListener) {} -} \ No newline at end of file diff --git a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt deleted file mode 100644 index 9da6ffa79..000000000 --- a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.michaelbel.movies.platform.impl.review - -import android.app.Activity -import javax.inject.Inject -import org.michaelbel.movies.platform.review.ReviewService - -class ReviewServiceImpl @Inject constructor(): ReviewService { - - override fun requestReview(activity: Activity) {} -} \ No newline at end of file diff --git a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt deleted file mode 100644 index c3d3163db..000000000 --- a/core/platform-services/hms/src/main/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.michaelbel.movies.platform.impl.update - -import android.app.Activity -import javax.inject.Inject -import org.michaelbel.movies.platform.update.UpdateListener -import org.michaelbel.movies.platform.update.UpdateService - -class UpdateServiceImpl @Inject constructor(): UpdateService { - - override fun setUpdateAvailableListener(listener: UpdateListener) {} - - override fun startUpdate(activity: Activity) {} -} \ No newline at end of file diff --git a/core/platform-services/interactor/.gitignore b/core/platform-services/inject-kmp/.gitignore similarity index 100% rename from core/platform-services/interactor/.gitignore rename to core/platform-services/inject-kmp/.gitignore diff --git a/core/platform-services/inject-kmp/build.gradle.kts b/core/platform-services/inject-kmp/build.gradle.kts new file mode 100644 index 000000000..609a8bc37 --- /dev/null +++ b/core/platform-services/inject-kmp/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:platform-services:interactor-kmp")) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.platform.inject_kmp" + flavorDimensions += "version" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + productFlavors { + create("gms") { + dimension = "version" + isDefault = true + } + create("hms") { + dimension = "version" + } + create("foss") { + dimension = "version" + } + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } + + val gmsImplementation by configurations + val hmsImplementation by configurations + val fossImplementation by configurations + dependencies { + gmsImplementation(project(":core:platform-services:gms-kmp")) + hmsImplementation(project(":core:platform-services:hms-kmp")) + fossImplementation(project(":core:platform-services:foss-kmp")) + } +} \ No newline at end of file diff --git a/core/platform-services/inject-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/inject/FlavorServiceKtorModule.kt b/core/platform-services/inject-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/inject/FlavorServiceKtorModule.kt new file mode 100644 index 000000000..49428b432 --- /dev/null +++ b/core/platform-services/inject-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/inject/FlavorServiceKtorModule.kt @@ -0,0 +1,37 @@ +package org.michaelbel.movies.platform.inject + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.platform.analytics.AnalyticsService +import org.michaelbel.movies.platform.app.AppService +import org.michaelbel.movies.platform.config.ConfigService +import org.michaelbel.movies.platform.crashlytics.CrashlyticsService +import org.michaelbel.movies.platform.impl.analytics.AnalyticsServiceImpl +import org.michaelbel.movies.platform.impl.app.AppServiceImpl +import org.michaelbel.movies.platform.impl.config.ConfigServiceImpl +import org.michaelbel.movies.platform.impl.crashlytics.CrashlyticsServiceImpl +import org.michaelbel.movies.platform.impl.firebaseKoinModule +import org.michaelbel.movies.platform.impl.googleApiKoinModule +import org.michaelbel.movies.platform.impl.messaging.MessagingServiceImpl +import org.michaelbel.movies.platform.impl.playKoinModule +import org.michaelbel.movies.platform.impl.review.ReviewServiceImpl +import org.michaelbel.movies.platform.impl.update.UpdateServiceImpl +import org.michaelbel.movies.platform.messaging.MessagingService +import org.michaelbel.movies.platform.review.ReviewService +import org.michaelbel.movies.platform.update.UpdateService + +val flavorServiceKtorModule = module { + includes( + firebaseKoinModule, + googleApiKoinModule, + playKoinModule + ) + singleOf(::AnalyticsServiceImpl) { bind() } + singleOf(::AppServiceImpl) { bind() } + singleOf(::ConfigServiceImpl) { bind() } + singleOf(::CrashlyticsServiceImpl) { bind() } + singleOf(::MessagingServiceImpl) { bind() } + singleOf(::ReviewServiceImpl) { bind() } + singleOf(::UpdateServiceImpl) { bind() } +} \ No newline at end of file diff --git a/core/platform-services/inject/build.gradle.kts b/core/platform-services/inject/build.gradle.kts deleted file mode 100644 index 6babe6fbf..000000000 --- a/core/platform-services/inject/build.gradle.kts +++ /dev/null @@ -1,60 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.platform.inject" - flavorDimensions += "version" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - productFlavors { - create("gms") { - dimension = "version" - isDefault = true - } - create("hms") { - dimension = "version" - } - create("foss") { - dimension = "version" - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -val gmsImplementation: Configuration by configurations -val hmsImplementation: Configuration by configurations -val fossImplementation: Configuration by configurations -dependencies { - gmsImplementation(project(":core:platform-services:gms")) - hmsImplementation(project(":core:platform-services:hms")) - fossImplementation(project(":core:platform-services:foss")) - implementation(project(":core:platform-services:interactor")) -} \ No newline at end of file diff --git a/core/platform-services/inject/src/main/AndroidManifest.xml b/core/platform-services/inject/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/platform-services/inject/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/platform-services/inject/src/main/kotlin/org/michaelbel/movies/platform/inject/FlavorServiceModule.kt b/core/platform-services/inject/src/main/kotlin/org/michaelbel/movies/platform/inject/FlavorServiceModule.kt deleted file mode 100644 index 98300fb03..000000000 --- a/core/platform-services/inject/src/main/kotlin/org/michaelbel/movies/platform/inject/FlavorServiceModule.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.michaelbel.movies.platform.inject - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import org.michaelbel.movies.platform.analytics.AnalyticsService -import org.michaelbel.movies.platform.app.AppService -import org.michaelbel.movies.platform.config.ConfigService -import org.michaelbel.movies.platform.crashlytics.CrashlyticsService -import org.michaelbel.movies.platform.impl.analytics.AnalyticsServiceImpl -import org.michaelbel.movies.platform.impl.app.AppServiceImpl -import org.michaelbel.movies.platform.impl.config.ConfigServiceImpl -import org.michaelbel.movies.platform.impl.crashlytics.CrashlyticsServiceImpl -import org.michaelbel.movies.platform.impl.messaging.MessagingServiceImpl -import org.michaelbel.movies.platform.impl.review.ReviewServiceImpl -import org.michaelbel.movies.platform.impl.update.UpdateServiceImpl -import org.michaelbel.movies.platform.messaging.MessagingService -import org.michaelbel.movies.platform.review.ReviewService -import org.michaelbel.movies.platform.update.UpdateService - -@Module -@InstallIn(SingletonComponent::class) -internal interface FlavorServiceModule { - - @Binds - @Singleton - fun provideAnalyticsService(service: AnalyticsServiceImpl): AnalyticsService - - @Binds - @Singleton - fun provideAppService(service: AppServiceImpl): AppService - - @Binds - @Singleton - fun provideConfigService(service: ConfigServiceImpl): ConfigService - - @Binds - @Singleton - fun provideCrashlyticsService(service: CrashlyticsServiceImpl): CrashlyticsService - - @Binds - @Singleton - fun provideMessagingService(service: MessagingServiceImpl): MessagingService - - @Binds - @Singleton - fun provideReviewService(service: ReviewServiceImpl): ReviewService - - @Binds - @Singleton - fun provideUpdateService(service: UpdateServiceImpl): UpdateService -} \ No newline at end of file diff --git a/core/repository/.gitignore b/core/platform-services/interactor-kmp/.gitignore similarity index 100% rename from core/repository/.gitignore rename to core/platform-services/interactor-kmp/.gitignore diff --git a/core/platform-services/interactor-kmp/build.gradle.kts b/core/platform-services/interactor-kmp/build.gradle.kts new file mode 100644 index 000000000..6bfd32d81 --- /dev/null +++ b/core/platform-services/interactor-kmp/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(libs.bundles.kotlinx.coroutines.common) + } + } +} + +android { + namespace = "org.michaelbel.movies.platform.interactor_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/Flavor.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/Flavor.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/Flavor.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/Flavor.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/analytics/AnalyticsService.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/analytics/AnalyticsService.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/analytics/AnalyticsService.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/analytics/AnalyticsService.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/app/AppService.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/app/AppService.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/app/AppService.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/app/AppService.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/config/ConfigService.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/config/ConfigService.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/config/ConfigService.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/config/ConfigService.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/crashlytics/CrashlyticsService.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/crashlytics/CrashlyticsService.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/crashlytics/CrashlyticsService.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/crashlytics/CrashlyticsService.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/messaging/MessagingService.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/messaging/MessagingService.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/messaging/MessagingService.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/messaging/MessagingService.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/messaging/TokenListener.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/messaging/TokenListener.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/messaging/TokenListener.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/messaging/TokenListener.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/update/UpdateListener.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/update/UpdateListener.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/update/UpdateListener.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/update/UpdateListener.kt diff --git a/core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt b/core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt similarity index 100% rename from core/platform-services/interactor/src/main/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt rename to core/platform-services/interactor-kmp/src/androidMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt diff --git a/core/platform-services/interactor/build.gradle.kts b/core/platform-services/interactor/build.gradle.kts deleted file mode 100644 index 19e7b1206..000000000 --- a/core/platform-services/interactor/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.platform.interactor" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - -} \ No newline at end of file diff --git a/core/platform-services/interactor/src/main/AndroidManifest.xml b/core/platform-services/interactor/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/platform-services/interactor/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/ui/.gitignore b/core/repository-kmp/.gitignore similarity index 100% rename from core/ui/.gitignore rename to core/repository-kmp/.gitignore diff --git a/core/repository-kmp/build.gradle.kts b/core/repository-kmp/build.gradle.kts new file mode 100644 index 000000000..bc5bd348d --- /dev/null +++ b/core/repository-kmp/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + api(project(":core:common-kmp")) + api(project(":core:network-kmp")) + api(project(":core:persistence-kmp")) + implementation(libs.bundles.kotlinx.coroutines.common) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + } + } +} + +android { + namespace = "org.michaelbel.movies.repository_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/androidMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt b/core/repository-kmp/src/androidMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt new file mode 100644 index 000000000..c11a3b793 --- /dev/null +++ b/core/repository-kmp/src/androidMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.repository.ktx + +import android.os.Build + +actual val defaultDynamicColorsEnabled: Boolean + get() = Build.VERSION.SDK_INT >= 31 \ No newline at end of file diff --git a/core/repository-kmp/src/androidMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt b/core/repository-kmp/src/androidMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt new file mode 100644 index 000000000..b3300a0b3 --- /dev/null +++ b/core/repository-kmp/src/androidMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.repository.ktx + +import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException +import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty + +internal actual fun checkApiKeyNotNullException() { + if (isTmdbApiKeyEmpty) throw ApiKeyNotNullException +} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/AccountRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/AccountRepository.kt similarity index 77% rename from core/repository/src/main/kotlin/org/michaelbel/movies/repository/AccountRepository.kt rename to core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/AccountRepository.kt index ded6f03a2..a755c0f3a 100644 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/AccountRepository.kt +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/AccountRepository.kt @@ -1,13 +1,13 @@ package org.michaelbel.movies.repository import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.persistence.database.entity.AccountDb +import org.michaelbel.movies.persistence.database.entity.AccountPojo interface AccountRepository { - val account: Flow + val account: Flow - suspend fun accountId(): Int? + suspend fun accountId(): Int suspend fun accountExpireTime(): Long? diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/AuthenticationRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/AuthenticationRepository.kt new file mode 100644 index 000000000..74d733e08 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/AuthenticationRepository.kt @@ -0,0 +1,23 @@ +package org.michaelbel.movies.repository + +import org.michaelbel.movies.network.model.Session +import org.michaelbel.movies.network.model.Token + +interface AuthenticationRepository { + + suspend fun createRequestToken( + loginViaTmdb: Boolean + ): Token + + suspend fun createSessionWithLogin( + username: String, + password: String, + requestToken: String + ): Token + + suspend fun createSession( + token: String + ): Session + + suspend fun deleteSession() +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ImageRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ImageRepository.kt new file mode 100644 index 000000000..9febca940 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ImageRepository.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.repository + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.ImagePojo + +interface ImageRepository { + + fun imagesFlow( + movieId: Int + ): Flow> + + suspend fun images( + movieId: Int + ) +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/MovieRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/MovieRepository.kt new file mode 100644 index 000000000..8033c8432 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/MovieRepository.kt @@ -0,0 +1,63 @@ +package org.michaelbel.movies.repository + +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini + +interface MovieRepository { + + fun moviesPagingSource( + pagingKey: String + ): PagingSource + + fun moviesFlow( + pagingKey: String, + limit: Int + ): Flow> + + suspend fun moviesResult( + movieList: String, + page: Int + ): Result + + suspend fun movie( + pagingKey: String, + movieId: Int + ): MoviePojo + + suspend fun movieDetails( + pagingKey: String, + movieId: Int + ): MoviePojo + + suspend fun moviesWidget(): List + + suspend fun removeMovies( + pagingKey: String + ) + + suspend fun removeMovie( + pagingKey: String, + movieId: Int + ) + + suspend fun insertMovies( + pagingKey: String, + page: Int, + movies: List + ) + + suspend fun insertMovie( + pagingKey: String, + movie: MoviePojo + ) + + suspend fun updateMovieColors( + movieId: Int, + containerColor: Int, + onContainerColor: Int + ) +} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/NotificationRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/NotificationRepository.kt similarity index 100% rename from core/repository/src/main/kotlin/org/michaelbel/movies/repository/NotificationRepository.kt rename to core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/NotificationRepository.kt diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/PagingKeyRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/PagingKeyRepository.kt new file mode 100644 index 000000000..6e1b397d0 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/PagingKeyRepository.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.repository + +interface PagingKeyRepository { + + suspend fun page( + pagingKey: String + ): Int? + + suspend fun totalPages( + pagingKey: String + ): Int? + + suspend fun prevPage( + pagingKey: String + ): Int? + + suspend fun removePagingKey( + pagingKey: String + ) + + suspend fun insertPagingKey( + pagingKey: String, + page: Int, + totalPages: Int + ) +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SearchRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SearchRepository.kt new file mode 100644 index 000000000..93ffe6939 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SearchRepository.kt @@ -0,0 +1,12 @@ +package org.michaelbel.movies.repository + +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result + +interface SearchRepository { + + suspend fun searchMoviesResult( + query: String, + page: Int + ): Result +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SettingsRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SettingsRepository.kt new file mode 100644 index 000000000..218cda752 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SettingsRepository.kt @@ -0,0 +1,50 @@ +package org.michaelbel.movies.repository + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.theme.AppTheme + +interface SettingsRepository { + + val currentTheme: Flow + + val currentFeedView: Flow + + val currentMovieList: Flow + + val themeData: Flow + + val isBiometricEnabled: Flow + + suspend fun isBiometricEnabledAsync(): Boolean + + suspend fun selectTheme( + appTheme: AppTheme + ) + + suspend fun selectFeedView( + feedView: FeedView + ) + + suspend fun selectMovieList( + movieList: MovieList + ) + + suspend fun setDynamicColors( + value: Boolean + ) + + suspend fun setPaletteKey( + paletteKey: Int + ) + + suspend fun setSeedColor( + seedColor: Int + ) + + suspend fun setBiometricEnabled( + enabled: Boolean + ) +} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SuggestionRepository.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SuggestionRepository.kt similarity index 78% rename from core/repository/src/main/kotlin/org/michaelbel/movies/repository/SuggestionRepository.kt rename to core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SuggestionRepository.kt index 43d45226e..31b5ffc97 100644 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SuggestionRepository.kt +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/SuggestionRepository.kt @@ -1,11 +1,11 @@ package org.michaelbel.movies.repository import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.persistence.database.entity.SuggestionDb +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo interface SuggestionRepository { - fun suggestions(): Flow> + fun suggestions(): Flow> suspend fun updateSuggestions() } \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/di/RepositoryKoinModule.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/di/RepositoryKoinModule.kt new file mode 100644 index 000000000..0e02451b9 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/di/RepositoryKoinModule.kt @@ -0,0 +1,45 @@ +package org.michaelbel.movies.repository.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.localization.di.localeKoinModule +import org.michaelbel.movies.network.di.networkKoinModule +import org.michaelbel.movies.persistence.database.di.persistenceKoinModule +import org.michaelbel.movies.persistence.datastore.di.moviesPreferencesKoinModule +import org.michaelbel.movies.repository.AccountRepository +import org.michaelbel.movies.repository.AuthenticationRepository +import org.michaelbel.movies.repository.ImageRepository +import org.michaelbel.movies.repository.MovieRepository +import org.michaelbel.movies.repository.NotificationRepository +import org.michaelbel.movies.repository.PagingKeyRepository +import org.michaelbel.movies.repository.SearchRepository +import org.michaelbel.movies.repository.SettingsRepository +import org.michaelbel.movies.repository.SuggestionRepository +import org.michaelbel.movies.repository.impl.AccountRepositoryImpl +import org.michaelbel.movies.repository.impl.AuthenticationRepositoryImpl +import org.michaelbel.movies.repository.impl.ImageRepositoryImpl +import org.michaelbel.movies.repository.impl.MovieRepositoryImpl +import org.michaelbel.movies.repository.impl.NotificationRepositoryImpl +import org.michaelbel.movies.repository.impl.PagingKeyRepositoryImpl +import org.michaelbel.movies.repository.impl.SearchRepositoryImpl +import org.michaelbel.movies.repository.impl.SettingsRepositoryImpl +import org.michaelbel.movies.repository.impl.SuggestionRepositoryImpl + +val repositoryKoinModule = module { + includes( + networkKoinModule, + persistenceKoinModule, + moviesPreferencesKoinModule, + localeKoinModule + ) + singleOf(::AccountRepositoryImpl) { bind() } + singleOf(::AuthenticationRepositoryImpl) { bind() } + singleOf(::ImageRepositoryImpl) { bind() } + singleOf(::MovieRepositoryImpl) { bind() } + singleOf(::NotificationRepositoryImpl) { bind() } + singleOf(::PagingKeyRepositoryImpl) { bind() } + singleOf(::SearchRepositoryImpl) { bind() } + singleOf(::SettingsRepositoryImpl) { bind() } + singleOf(::SuggestionRepositoryImpl) { bind() } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.kt new file mode 100644 index 000000000..ce835b5e1 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.kt @@ -0,0 +1,48 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package org.michaelbel.movies.repository.impl + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import org.michaelbel.movies.common.exceptions.AccountDetailsException +import org.michaelbel.movies.network.AccountNetworkService +import org.michaelbel.movies.persistence.database.AccountPersistence +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import org.michaelbel.movies.persistence.database.ktx.accountPojo +import org.michaelbel.movies.persistence.datastore.MoviesPreferences +import org.michaelbel.movies.repository.AccountRepository + +internal class AccountRepositoryImpl( + private val accountNetworkService: AccountNetworkService, + private val accountPersistence: AccountPersistence, + private val preferences: MoviesPreferences +): AccountRepository { + + override val account: Flow = preferences.accountIdFlow + .map { accountId -> accountId ?: 0 } + .flatMapLatest(accountPersistence::accountById) + + override suspend fun accountId(): Int { + return preferences.accountId() + } + + override suspend fun accountExpireTime(): Long? { + return preferences.accountExpireTime() + } + + override suspend fun accountDetails() { + runCatching { + val sessionId = preferences.sessionId().orEmpty() + val account = accountNetworkService.accountDetails(sessionId) + preferences.run { + setAccountId(account.id) + setAccountExpireTime(System.currentTimeMillis()) + } + accountPersistence.insert(account.accountPojo) + }.onFailure { + throw AccountDetailsException + } + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.kt new file mode 100644 index 000000000..3d76b017f --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.kt @@ -0,0 +1,93 @@ +package org.michaelbel.movies.repository.impl + +import org.michaelbel.movies.common.exceptions.CreateRequestTokenException +import org.michaelbel.movies.common.exceptions.CreateSessionException +import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException +import org.michaelbel.movies.common.exceptions.DeleteSessionException +import org.michaelbel.movies.network.AuthenticationNetworkService +import org.michaelbel.movies.network.model.RequestToken +import org.michaelbel.movies.network.model.Session +import org.michaelbel.movies.network.model.SessionRequest +import org.michaelbel.movies.network.model.Token +import org.michaelbel.movies.network.model.Username +import org.michaelbel.movies.persistence.database.AccountPersistence +import org.michaelbel.movies.persistence.datastore.MoviesPreferences +import org.michaelbel.movies.repository.AuthenticationRepository + +internal class AuthenticationRepositoryImpl( + private val authenticationNetworkService: AuthenticationNetworkService, + private val accountPersistence: AccountPersistence, + private val preferences: MoviesPreferences +): AuthenticationRepository { + + override suspend fun createRequestToken( + loginViaTmdb: Boolean + ): Token { + return try { + val token = authenticationNetworkService.createRequestToken() + if (!token.success) { + throw CreateRequestTokenException(loginViaTmdb) + } + token + } catch (ignored: Exception) { + throw CreateRequestTokenException(loginViaTmdb) + } + } + + override suspend fun createSessionWithLogin( + username: String, + password: String, + requestToken: String + ): Token { + return try { + val token = authenticationNetworkService.createSessionWithLogin( + username = Username( + username = username, + password = password, + requestToken = requestToken + ) + ) + if (!token.success) { + throw CreateSessionWithLoginException + } + token + } catch (ignored: Exception) { + throw CreateSessionWithLoginException + } + } + + override suspend fun createSession( + token: String + ): Session { + return try { + val session = authenticationNetworkService.createSession(RequestToken(token)) + if (session.success) { + preferences.setSessionId(session.sessionId) + } else { + throw CreateSessionException + } + session + } catch (ignored: Exception) { + throw CreateSessionException + } + } + + override suspend fun deleteSession() { + runCatching { + val sessionId = preferences.sessionId().orEmpty() + val deletedSession = authenticationNetworkService.deleteSession(SessionRequest(sessionId)) + if (deletedSession.success) { + val accountId = preferences.accountId() + accountPersistence.removeById(accountId) + preferences.run { + removeSessionId() + removeAccountId() + } + } else { + throw DeleteSessionException + } + }.onFailure { + throw DeleteSessionException + } + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.kt new file mode 100644 index 000000000..2dde60f6f --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.kt @@ -0,0 +1,49 @@ +package org.michaelbel.movies.repository.impl + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.network.MovieNetworkService +import org.michaelbel.movies.persistence.database.ImagePersistence +import org.michaelbel.movies.persistence.database.entity.ImagePojo +import org.michaelbel.movies.persistence.database.entity.ImageType +import org.michaelbel.movies.persistence.database.ktx.imagePojo +import org.michaelbel.movies.repository.ImageRepository + +internal class ImageRepositoryImpl( + private val movieNetworkService: MovieNetworkService, + private val imagePersistence: ImagePersistence +): ImageRepository { + + override fun imagesFlow( + movieId: Int + ): Flow> { + return imagePersistence.imagesFlow(movieId) + } + + override suspend fun images( + movieId: Int + ) { + val imageResponse = movieNetworkService.images(movieId) + val posters = imageResponse.posters.mapIndexed { index, image -> + image.imagePojo( + movieId = movieId, + type = ImageType.POSTER, + position = index + ) + } + val backdrops = imageResponse.backdrops.mapIndexed { index, image -> + image.imagePojo( + movieId = movieId, + type = ImageType.BACKDROP, + position = posters.count().plus(index) + ) + } + val logos = imageResponse.logos.mapIndexed { index, image -> + image.imagePojo( + movieId = movieId, + type = ImageType.LOGO, + position = posters.count().plus(backdrops.count()).plus(index) + ) + } + imagePersistence.insert(posters + backdrops + logos) + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.kt new file mode 100644 index 000000000..be47ec775 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.kt @@ -0,0 +1,116 @@ +package org.michaelbel.movies.repository.impl + +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.common.exceptions.MovieDetailsException +import org.michaelbel.movies.common.exceptions.MoviesUpcomingException +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.localization.LocaleController +import org.michaelbel.movies.network.MovieNetworkService +import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result +import org.michaelbel.movies.persistence.database.MoviePersistence +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini +import org.michaelbel.movies.persistence.database.ktx.moviePojo +import org.michaelbel.movies.persistence.database.ktx.orEmpty +import org.michaelbel.movies.repository.MovieRepository +import org.michaelbel.movies.repository.ktx.checkApiKeyNotNullException + +internal class MovieRepositoryImpl( + private val movieNetworkService: MovieNetworkService, + private val moviePersistence: MoviePersistence, + private val localeController: LocaleController +): MovieRepository { + + override fun moviesPagingSource(pagingKey: String): PagingSource { + return moviePersistence.pagingSource(pagingKey) + } + + override fun moviesFlow(pagingKey: String, limit: Int): Flow> { + return moviePersistence.moviesFlow(pagingKey, limit) + } + + override suspend fun moviesResult(movieList: String, page: Int): Result { + if (isTmdbApiKeyEmpty && moviePersistence.isEmpty(MoviePojo.MOVIES_LOCAL_LIST)) { + checkApiKeyNotNullException() + } + + return movieNetworkService.movies( + list = movieList, + language = localeController.language, + page = page + ) + } + + override suspend fun movie(pagingKey: String, movieId: Int): MoviePojo { + return moviePersistence.movieById(pagingKey, movieId).orEmpty + } + + override suspend fun movieDetails(pagingKey: String, movieId: Int): MoviePojo { + return try { + moviePersistence.movieById(pagingKey, movieId) ?: movieNetworkService.movie(movieId, localeController.language).moviePojo + } catch (ignored: Exception) { + throw MovieDetailsException + } + } + + override suspend fun moviesWidget(): List { + return try { + val movieResult = movieNetworkService.movies( + list = MovieList.Upcoming().name, + language = localeController.language, + page = 1 + ) + val moviesDb = movieResult.results.mapIndexed { index, movieResponse -> + movieResponse.moviePojo( + movieList = MoviePojo.MOVIES_WIDGET, + position = index.plus(1) + ) + } + moviePersistence.removeMovies(MoviePojo.MOVIES_WIDGET) + moviePersistence.insertMovies(moviesDb) + moviePersistence.moviesMini(MoviePojo.MOVIES_WIDGET, MovieResponse.DEFAULT_PAGE_SIZE) + } catch (ignored: Exception) { + moviePersistence.moviesMini(MoviePojo.MOVIES_WIDGET, MovieResponse.DEFAULT_PAGE_SIZE).ifEmpty { + throw MoviesUpcomingException + } + } + } + + override suspend fun removeMovies(pagingKey: String) { + moviePersistence.removeMovies(pagingKey) + } + + override suspend fun removeMovie(pagingKey: String, movieId: Int) { + moviePersistence.removeMovie(pagingKey, movieId) + } + + override suspend fun insertMovies(pagingKey: String, page: Int, movies: List) { + val maxPosition = moviePersistence.maxPosition(pagingKey) ?: 0 + val moviesDb = movies.mapIndexed { index, movieResponse -> + movieResponse.moviePojo( + movieList = pagingKey, + page = page, + position = if (maxPosition == 0) index else maxPosition.plus(index).plus(1) + ) + } + moviePersistence.insertMovies(moviesDb) + } + + override suspend fun insertMovie(pagingKey: String, movie: MoviePojo) { + val maxPosition = moviePersistence.maxPosition(pagingKey) ?: 0 + moviePersistence.insertMovie( + movie.copy( + movieList = pagingKey, + dateAdded = System.currentTimeMillis(), + position = maxPosition.plus(1) + ) + ) + } + + override suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) { + moviePersistence.updateMovieColors(movieId, containerColor, onContainerColor) + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.kt new file mode 100644 index 000000000..dbf102eb0 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.repository.impl + +import org.michaelbel.movies.persistence.datastore.MoviesPreferences +import org.michaelbel.movies.repository.NotificationRepository + +internal class NotificationRepositoryImpl( + private val preferences: MoviesPreferences +): NotificationRepository { + + override suspend fun notificationExpireTime(): Long { + return preferences.notificationExpireTime() ?: 0L + } + + override suspend fun updateNotificationExpireTime() { + val currentTime = System.currentTimeMillis() + preferences.setNotificationExpireTime(currentTime) + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.kt new file mode 100644 index 000000000..d1635cae5 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.kt @@ -0,0 +1,36 @@ +package org.michaelbel.movies.repository.impl + +import org.michaelbel.movies.persistence.database.PagingKeyPersistence +import org.michaelbel.movies.persistence.database.entity.PagingKeyPojo +import org.michaelbel.movies.repository.PagingKeyRepository + +internal class PagingKeyRepositoryImpl( + private val pagingKeyPersistence: PagingKeyPersistence +): PagingKeyRepository { + + override suspend fun page(pagingKey: String): Int? { + return pagingKeyPersistence.page(pagingKey) + } + + override suspend fun totalPages(pagingKey: String): Int? { + return pagingKeyPersistence.totalPages(pagingKey) + } + + override suspend fun prevPage(pagingKey: String): Int? { + return null + } + + override suspend fun removePagingKey(pagingKey: String) { + pagingKeyPersistence.removePagingKey(pagingKey) + } + + override suspend fun insertPagingKey(pagingKey: String, page: Int, totalPages: Int) { + pagingKeyPersistence.insertPagingKey( + PagingKeyPojo( + pagingKey = pagingKey, + page = page, + totalPages = totalPages + ) + ) + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.kt new file mode 100644 index 000000000..2c804e118 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.kt @@ -0,0 +1,27 @@ +package org.michaelbel.movies.repository.impl + +import org.michaelbel.movies.common.localization.LocaleController +import org.michaelbel.movies.network.SearchNetworkService +import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.network.model.Result +import org.michaelbel.movies.repository.SearchRepository +import org.michaelbel.movies.repository.ktx.checkApiKeyNotNullException + +internal class SearchRepositoryImpl( + private val searchNetworkService: SearchNetworkService, + private val localeController: LocaleController +): SearchRepository { + + override suspend fun searchMoviesResult(query: String, page: Int): Result { + if (isTmdbApiKeyEmpty) { + checkApiKeyNotNullException() + } + + return searchNetworkService.searchMovies( + query = query, + language = localeController.language, + page = page + ) + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.kt new file mode 100644 index 000000000..5d32cdc94 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.kt @@ -0,0 +1,82 @@ +package org.michaelbel.movies.repository.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.persistence.datastore.MoviesPreferences +import org.michaelbel.movies.repository.SettingsRepository +import org.michaelbel.movies.repository.ktx.defaultDynamicColorsEnabled + +internal class SettingsRepositoryImpl( + private val preferences: MoviesPreferences +): SettingsRepository { + + override val currentTheme: Flow = preferences.themeFlow.map { name -> + AppTheme.transform(name ?: AppTheme.FollowSystem.toString()) + } + + override val currentFeedView: Flow = preferences.feedViewFlow.map { name -> + FeedView.transform(name ?: FeedView.FeedList.toString()) + } + + override val currentMovieList: Flow = preferences.movieListFlow.map { className -> + MovieList.transform(className ?: MovieList.NowPlaying().toString()) + } + + override val themeData: Flow + get() { + return combine( + preferences.themeFlow, + preferences.isDynamicColorsFlow, + preferences.paletteKeyFlow, + preferences.seedColorFlow + ) { name, dynamicColors, paletteKey, seedColor -> + ThemeData( + appTheme = AppTheme.transform(name ?: AppTheme.FollowSystem.toString()), + dynamicColors = dynamicColors ?: defaultDynamicColorsEnabled, + paletteKey = paletteKey ?: ThemeData.STYLE_TONAL_SPOT, + seedColor = seedColor ?: ThemeData.DEFAULT_SEED_COLOR + ) + } + } + + override val isBiometricEnabled: Flow = preferences.isBiometricEnabledFlow.map { enabled -> + enabled ?: false + } + + override suspend fun isBiometricEnabledAsync(): Boolean { + return preferences.isBiometricEnabledAsync() + } + + override suspend fun selectTheme(appTheme: AppTheme) { + preferences.setTheme(appTheme.toString()) + } + + override suspend fun selectFeedView(feedView: FeedView) { + preferences.setFeedView(feedView.toString()) + } + + override suspend fun selectMovieList(movieList: MovieList) { + preferences.setMovieList(movieList.toString()) + } + + override suspend fun setDynamicColors(value: Boolean) { + preferences.setDynamicColors(value) + } + + override suspend fun setPaletteKey(paletteKey: Int) { + preferences.setPaletteKey(paletteKey) + } + + override suspend fun setSeedColor(seedColor: Int) { + preferences.setSeedColor(seedColor) + } + + override suspend fun setBiometricEnabled(enabled: Boolean) { + preferences.setBiometricEnabled(enabled) + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.kt new file mode 100644 index 000000000..e017e931f --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.kt @@ -0,0 +1,38 @@ +package org.michaelbel.movies.repository.impl + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.common.localization.LocaleController +import org.michaelbel.movies.network.MovieNetworkService +import org.michaelbel.movies.network.model.Movie +import org.michaelbel.movies.persistence.database.MoviePersistence +import org.michaelbel.movies.persistence.database.SuggestionPersistence +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo +import org.michaelbel.movies.repository.SuggestionRepository + +internal class SuggestionRepositoryImpl( + private val movieNetworkService: MovieNetworkService, + private val moviePersistence: MoviePersistence, + private val suggestionPersistence: SuggestionPersistence, + private val localeController: LocaleController +): SuggestionRepository { + + override fun suggestions(): Flow> { + return suggestionPersistence.suggestionsFlow() + } + + override suspend fun updateSuggestions() { + suggestionPersistence.removeAll() + + val nowPlayingMovies = moviePersistence.movies(Movie.NOW_PLAYING, 5) + if (nowPlayingMovies.isNotEmpty()) { + suggestionPersistence.insert(nowPlayingMovies.map { movie -> SuggestionPojo(movie.title) }) + } else { + val movieResponse = movieNetworkService.movies( + list = Movie.NOW_PLAYING, + language = localeController.language, + page = 1 + ).results.take(5) + suggestionPersistence.insert(movieResponse.map { movie -> SuggestionPojo(movie.title) }) + } + } +} \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt new file mode 100644 index 000000000..373e658e6 --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.repository.ktx + +internal expect val defaultDynamicColorsEnabled: Boolean \ No newline at end of file diff --git a/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt new file mode 100644 index 000000000..a00953eec --- /dev/null +++ b/core/repository-kmp/src/commonMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.repository.ktx + +internal expect fun checkApiKeyNotNullException() \ No newline at end of file diff --git a/core/repository-kmp/src/desktopMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt b/core/repository-kmp/src/desktopMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt new file mode 100644 index 000000000..1d5f87f3d --- /dev/null +++ b/core/repository-kmp/src/desktopMain/kotlin/org/michaelbel/movies/repository/ktx/BuildKtx.kt @@ -0,0 +1,4 @@ +package org.michaelbel.movies.repository.ktx + +actual val defaultDynamicColorsEnabled: Boolean + get() = false \ No newline at end of file diff --git a/core/repository-kmp/src/desktopMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt b/core/repository-kmp/src/desktopMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt new file mode 100644 index 000000000..b3300a0b3 --- /dev/null +++ b/core/repository-kmp/src/desktopMain/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.repository.ktx + +import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException +import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty + +internal actual fun checkApiKeyNotNullException() { + if (isTmdbApiKeyEmpty) throw ApiKeyNotNullException +} \ No newline at end of file diff --git a/core/repository/build.gradle.kts b/core/repository/build.gradle.kts deleted file mode 100644 index 6b7717fab..000000000 --- a/core/repository/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.repository" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(project(":core:common")) - api(project(":core:network")) - api(project(":core:persistence")) -} \ No newline at end of file diff --git a/core/repository/src/main/AndroidManifest.xml b/core/repository/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/repository/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/AuthenticationRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/AuthenticationRepository.kt deleted file mode 100644 index 3f844cc1a..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/AuthenticationRepository.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.michaelbel.movies.repository - -import org.michaelbel.movies.network.model.Session -import org.michaelbel.movies.network.model.Token - -interface AuthenticationRepository { - - suspend fun createRequestToken(loginViaTmdb: Boolean): Token - - suspend fun createSessionWithLogin( - username: String, - password: String, - requestToken: String - ): Token - - suspend fun createSession(token: String): Session - - suspend fun deleteSession() -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ImageRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ImageRepository.kt deleted file mode 100644 index f80a81280..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ImageRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.michaelbel.movies.repository - -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.persistence.database.entity.ImageDb - -interface ImageRepository { - - fun imagesFlow(movieId: Int): Flow> - - suspend fun images(movieId: Int) -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/MovieRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/MovieRepository.kt deleted file mode 100644 index f05227882..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/MovieRepository.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.michaelbel.movies.repository - -import androidx.paging.PagingSource -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result -import org.michaelbel.movies.persistence.database.entity.MovieDb - -interface MovieRepository { - - fun moviesPagingSource(pagingKey: String): PagingSource - - fun moviesFlow(pagingKey: String, limit: Int): Flow> - - suspend fun moviesResult(movieList: String, page: Int): Result - - suspend fun movie(pagingKey: String, movieId: Int): MovieDb - - suspend fun movieDetails(pagingKey: String, movieId: Int): MovieDb - - suspend fun removeMovies(pagingKey: String) - - suspend fun removeMovie(pagingKey: String, movieId: Int) - - suspend fun insertMovies(pagingKey: String, page: Int, movies: List) - - suspend fun insertMovie(pagingKey: String, movie: MovieDb) - - suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/PagingKeyRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/PagingKeyRepository.kt deleted file mode 100644 index e65d742a4..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/PagingKeyRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.repository - -interface PagingKeyRepository { - - suspend fun page(pagingKey: String): Int? - - suspend fun totalPages(pagingKey: String): Int? - - suspend fun prevPage(pagingKey: String): Int? - - suspend fun removePagingKey(pagingKey: String) - - suspend fun insertPagingKey(pagingKey: String, page: Int, totalPages: Int) -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SearchRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SearchRepository.kt deleted file mode 100644 index 09aeec36b..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SearchRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.repository - -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result - -interface SearchRepository { - - suspend fun searchMoviesResult(query: String, page: Int): Result -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SettingsRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SettingsRepository.kt deleted file mode 100644 index a0761f976..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/SettingsRepository.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.michaelbel.movies.repository - -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.version.AppVersionData - -interface SettingsRepository { - - val currentTheme: Flow - - val currentFeedView: Flow - - val currentMovieList: Flow - - val dynamicColors: Flow - - val appVersionData: Flow - - suspend fun selectTheme(appTheme: AppTheme) - - suspend fun selectFeedView(feedView: FeedView) - - suspend fun selectMovieList(movieList: MovieList) - - suspend fun setDynamicColors(value: Boolean) -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/di/RepositoryModule.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/di/RepositoryModule.kt deleted file mode 100644 index 905b603e0..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/di/RepositoryModule.kt +++ /dev/null @@ -1,84 +0,0 @@ -package org.michaelbel.movies.repository.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton -import org.michaelbel.movies.repository.AccountRepository -import org.michaelbel.movies.repository.AuthenticationRepository -import org.michaelbel.movies.repository.ImageRepository -import org.michaelbel.movies.repository.MovieRepository -import org.michaelbel.movies.repository.NotificationRepository -import org.michaelbel.movies.repository.PagingKeyRepository -import org.michaelbel.movies.repository.SearchRepository -import org.michaelbel.movies.repository.SettingsRepository -import org.michaelbel.movies.repository.SuggestionRepository -import org.michaelbel.movies.repository.impl.AccountRepositoryImpl -import org.michaelbel.movies.repository.impl.AuthenticationRepositoryImpl -import org.michaelbel.movies.repository.impl.ImageRepositoryImpl -import org.michaelbel.movies.repository.impl.MovieRepositoryImpl -import org.michaelbel.movies.repository.impl.NotificationRepositoryImpl -import org.michaelbel.movies.repository.impl.PagingKeyRepositoryImpl -import org.michaelbel.movies.repository.impl.SearchRepositoryImpl -import org.michaelbel.movies.repository.impl.SettingsRepositoryImpl -import org.michaelbel.movies.repository.impl.SuggestionRepositoryImpl - -@Module -@InstallIn(SingletonComponent::class) -internal interface RepositoryModule { - - @Binds - @Singleton - fun provideAccountRepository( - repository: AccountRepositoryImpl - ): AccountRepository - - @Binds - @Singleton - fun provideAuthenticationRepository( - repository: AuthenticationRepositoryImpl - ): AuthenticationRepository - - @Binds - @Singleton - fun provideImageRepository( - repository: ImageRepositoryImpl - ): ImageRepository - - @Binds - @Singleton - fun provideMovieRepository( - repository: MovieRepositoryImpl - ): MovieRepository - - @Binds - @Singleton - fun provideNotificationRepository( - repository: NotificationRepositoryImpl - ): NotificationRepository - - @Binds - @Singleton - fun providePagingKeyRepository( - repository: PagingKeyRepositoryImpl - ): PagingKeyRepository - - @Binds - @Singleton - fun provideSearchRepository( - repository: SearchRepositoryImpl - ): SearchRepository - - @Binds - @Singleton - fun provideSettingsRepository( - repository: SettingsRepositoryImpl - ): SettingsRepository - - @Binds - @Singleton - fun provideSuggestionRepository( - repository: SuggestionRepositoryImpl - ): SuggestionRepository -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.kt deleted file mode 100644 index 4f4686f7b..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/AccountRepositoryImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import org.michaelbel.movies.common.exceptions.AccountDetailsException -import org.michaelbel.movies.network.model.Account -import org.michaelbel.movies.network.service.account.AccountService -import org.michaelbel.movies.persistence.database.dao.AccountDao -import org.michaelbel.movies.persistence.database.entity.AccountDb -import org.michaelbel.movies.persistence.datastore.MoviesPreferences -import org.michaelbel.movies.repository.AccountRepository -import org.michaelbel.movies.repository.ktx.mapToAccountDb - -@Singleton -internal class AccountRepositoryImpl @Inject constructor( - private val accountService: AccountService, - private val accountDao: AccountDao, - private val preferences: MoviesPreferences -): AccountRepository { - - override val account: Flow = preferences.accountIdFlow - .map { accountId -> accountId ?: 0 } - .flatMapLatest(accountDao::accountById) - - override suspend fun accountId(): Int? { - return preferences.accountId() - } - - override suspend fun accountExpireTime(): Long? { - return preferences.accountExpireTime() - } - - override suspend fun accountDetails() { - try { - val sessionId: String = preferences.sessionId().orEmpty() - val account: Account = accountService.accountDetails(sessionId) - preferences.run { - setAccountId(account.id) - setAccountExpireTime(System.currentTimeMillis()) - } - accountDao.insert(account.mapToAccountDb) - } catch (ignored: Exception) { - throw AccountDetailsException - } - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.kt deleted file mode 100644 index 25885f663..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/AuthenticationRepositoryImpl.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import javax.inject.Inject -import javax.inject.Singleton -import org.michaelbel.movies.common.exceptions.CreateRequestTokenException -import org.michaelbel.movies.common.exceptions.CreateSessionException -import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException -import org.michaelbel.movies.common.exceptions.DeleteSessionException -import org.michaelbel.movies.network.model.DeletedSession -import org.michaelbel.movies.network.model.RequestToken -import org.michaelbel.movies.network.model.Session -import org.michaelbel.movies.network.model.SessionRequest -import org.michaelbel.movies.network.model.Token -import org.michaelbel.movies.network.model.Username -import org.michaelbel.movies.network.service.authentication.AuthenticationService -import org.michaelbel.movies.persistence.database.dao.AccountDao -import org.michaelbel.movies.persistence.datastore.MoviesPreferences -import org.michaelbel.movies.repository.AuthenticationRepository - -@Singleton -internal class AuthenticationRepositoryImpl @Inject constructor( - private val authenticationService: AuthenticationService, - private val accountDao: AccountDao, - private val preferences: MoviesPreferences -): AuthenticationRepository { - - override suspend fun createRequestToken(loginViaTmdb: Boolean): Token { - return try { - val token: Token = authenticationService.createRequestToken() - if (!token.success) { - throw CreateRequestTokenException(loginViaTmdb) - } - token - } catch (ignored: Exception) { - throw CreateRequestTokenException(loginViaTmdb) - } - } - - override suspend fun createSessionWithLogin( - username: String, - password: String, - requestToken: String - ): Token { - return try { - val token: Token = authenticationService.createSessionWithLogin( - username = Username( - username = username, - password = password, - requestToken = requestToken - ) - ) - if (!token.success) { - throw CreateSessionWithLoginException - } - token - } catch (ignored: Exception) { - throw CreateSessionWithLoginException - } - } - - override suspend fun createSession(token: String): Session { - return try { - val session: Session = authenticationService.createSession( - authToken = RequestToken(token) - ) - if (session.success) { - preferences.setSessionId(session.sessionId) - } else { - throw CreateSessionException - } - session - } catch (ignored: Exception) { - throw CreateSessionException - } - } - - override suspend fun deleteSession() { - try { - val sessionId: String = preferences.sessionId().orEmpty() - val sessionRequest = SessionRequest(sessionId) - val deletedSession: DeletedSession = authenticationService.deleteSession( - sessionRequest = sessionRequest - ) - if (deletedSession.success) { - val accountId: Int = preferences.accountId() ?: 0 - accountDao.removeById(accountId) - preferences.run { - removeSessionId() - removeAccountId() - } - } else { - throw DeleteSessionException - } - } catch (ignored: Exception) { - throw DeleteSessionException - } - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.kt deleted file mode 100644 index 3aaa6380b..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/ImageRepositoryImpl.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.network.model.ImagesResponse -import org.michaelbel.movies.network.service.movie.MovieService -import org.michaelbel.movies.persistence.database.dao.ImageDao -import org.michaelbel.movies.persistence.database.entity.ImageDb -import org.michaelbel.movies.persistence.database.ktx.imageDb -import org.michaelbel.movies.repository.ImageRepository - -@Singleton -internal class ImageRepositoryImpl @Inject constructor( - private val movieService: MovieService, - private val imageDao: ImageDao -): ImageRepository { - - override fun imagesFlow(movieId: Int): Flow> { - return imageDao.imagesFlow(movieId) - } - - override suspend fun images(movieId: Int) { - val imageResponse: ImagesResponse = movieService.images(movieId) - val posters: List = imageResponse.posters.mapIndexed { index, image -> - image.imageDb( - movieId = movieId, - type = ImageDb.Type.POSTER, - position = index - ) - } - val backdrops: List = imageResponse.backdrops.mapIndexed { index, image -> - image.imageDb( - movieId = movieId, - type = ImageDb.Type.BACKDROP, - position = posters.count().plus(index) - ) - } - val logos: List = imageResponse.logos.mapIndexed { index, image -> - image.imageDb( - movieId = movieId, - type = ImageDb.Type.LOGO, - position = posters.count().plus(backdrops.count()).plus(index) - ) - } - imageDao.insert(posters + backdrops + logos) - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.kt deleted file mode 100644 index 49c03c3e5..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/MovieRepositoryImpl.kt +++ /dev/null @@ -1,96 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import androidx.paging.PagingSource -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.common.exceptions.MovieDetailsException -import org.michaelbel.movies.common.localization.LocaleController -import org.michaelbel.movies.network.isTmdbApiKeyEmpty -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result -import org.michaelbel.movies.network.service.movie.MovieService -import org.michaelbel.movies.persistence.database.dao.MovieDao -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.ktx.orEmpty -import org.michaelbel.movies.repository.MovieRepository -import org.michaelbel.movies.repository.ktx.checkApiKeyNotNullException -import org.michaelbel.movies.repository.ktx.mapToMovieDb - -@Singleton -internal class MovieRepositoryImpl @Inject constructor( - private val movieService: MovieService, - private val movieDao: MovieDao, - private val localeController: LocaleController -): MovieRepository { - - override fun moviesPagingSource(pagingKey: String): PagingSource { - return movieDao.pagingSource(pagingKey) - } - - override fun moviesFlow(pagingKey: String, limit: Int): Flow> { - return movieDao.moviesFlow( - movieList = pagingKey, - limit = limit - ) - } - - override suspend fun moviesResult(movieList: String, page: Int): Result { - if (isTmdbApiKeyEmpty && movieDao.isEmpty(MovieDb.MOVIES_LOCAL_LIST)) { - checkApiKeyNotNullException() - } - - return movieService.movies( - list = movieList, - language = localeController.language, - page = page - ) - } - - override suspend fun movie(pagingKey: String, movieId: Int): MovieDb { - return movieDao.movieById(pagingKey, movieId).orEmpty - } - - override suspend fun movieDetails(pagingKey: String, movieId: Int): MovieDb { - return try { - movieDao.movieById(pagingKey, movieId) ?: movieService.movie(movieId, localeController.language).mapToMovieDb - } catch (e: Exception) { - throw MovieDetailsException - } - } - - override suspend fun removeMovies(pagingKey: String) { - movieDao.removeMovies(pagingKey) - } - - override suspend fun removeMovie(pagingKey: String, movieId: Int) { - movieDao.removeMovie(pagingKey, movieId) - } - - override suspend fun insertMovies(pagingKey: String, page: Int, movies: List) { - val maxPosition: Int = movieDao.maxPosition(pagingKey) ?: 0 - val moviesDb: List = movies.mapIndexed { index, movieResponse -> - movieResponse.mapToMovieDb( - movieList = pagingKey, - page = page, - position = if (maxPosition == 0) index else maxPosition.plus(index).plus(1) - ) - } - movieDao.insertMovies(moviesDb) - } - - override suspend fun insertMovie(pagingKey: String, movie: MovieDb) { - val maxPosition: Int = movieDao.maxPosition(pagingKey) ?: 0 - movieDao.insertMovie( - movie.copy( - movieList = pagingKey, - dateAdded = System.currentTimeMillis(), - position = maxPosition.plus(1) - ) - ) - } - - override suspend fun updateMovieColors(movieId: Int, containerColor: Int, onContainerColor: Int) { - movieDao.updateMovieColors(movieId, containerColor, onContainerColor) - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.kt deleted file mode 100644 index faa25582c..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/NotificationRepositoryImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import javax.inject.Inject -import javax.inject.Singleton -import org.michaelbel.movies.persistence.datastore.MoviesPreferences -import org.michaelbel.movies.repository.NotificationRepository - -@Singleton -internal class NotificationRepositoryImpl @Inject constructor( - private val preferences: MoviesPreferences -): NotificationRepository { - - override suspend fun notificationExpireTime(): Long { - return preferences.notificationExpireTime() ?: 0L - } - - override suspend fun updateNotificationExpireTime() { - val currentTime: Long = System.currentTimeMillis() - preferences.setNotificationExpireTime(currentTime) - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.kt deleted file mode 100644 index c0539bc8f..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/PagingKeyRepositoryImpl.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import javax.inject.Inject -import javax.inject.Singleton -import org.michaelbel.movies.persistence.database.dao.PagingKeyDao -import org.michaelbel.movies.persistence.database.entity.PagingKeyDb -import org.michaelbel.movies.repository.PagingKeyRepository - -@Singleton -internal class PagingKeyRepositoryImpl @Inject constructor( - private val pagingKeyDao: PagingKeyDao -): PagingKeyRepository { - - override suspend fun page(pagingKey: String): Int? { - return pagingKeyDao.page(pagingKey) - } - - override suspend fun totalPages(pagingKey: String): Int? { - return pagingKeyDao.totalPages(pagingKey) - } - - override suspend fun prevPage(pagingKey: String): Int? { - return null - } - - override suspend fun removePagingKey(pagingKey: String) { - pagingKeyDao.removePagingKey(pagingKey) - } - - override suspend fun insertPagingKey(pagingKey: String, page: Int, totalPages: Int) { - pagingKeyDao.insertPagingKey( - PagingKeyDb( - pagingKey = pagingKey, - page = page, - totalPages = totalPages - ) - ) - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.kt deleted file mode 100644 index 062c2a082..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SearchRepositoryImpl.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import javax.inject.Inject -import javax.inject.Singleton -import org.michaelbel.movies.common.localization.LocaleController -import org.michaelbel.movies.network.isTmdbApiKeyEmpty -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.model.Result -import org.michaelbel.movies.network.service.search.SearchService -import org.michaelbel.movies.repository.SearchRepository -import org.michaelbel.movies.repository.ktx.checkApiKeyNotNullException - -@Singleton -internal class SearchRepositoryImpl @Inject constructor( - private val searchService: SearchService, - private val localeController: LocaleController -): SearchRepository { - - override suspend fun searchMoviesResult(query: String, page: Int): Result { - if (isTmdbApiKeyEmpty) { - checkApiKeyNotNullException() - } - - return searchService.searchMovies( - query = query, - language = localeController.language, - page = page - ) - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.kt deleted file mode 100644 index c37b85b04..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SettingsRepositoryImpl.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import android.content.Context -import android.os.Build -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import org.michaelbel.movies.common.BuildConfig -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.version.AppVersionData -import org.michaelbel.movies.persistence.datastore.MoviesPreferences -import org.michaelbel.movies.platform.app.AppService -import org.michaelbel.movies.repository.SettingsRepository -import org.michaelbel.movies.repository.ktx.code -import org.michaelbel.movies.repository.ktx.packageInfo - -@Singleton -internal class SettingsRepositoryImpl @Inject constructor( - @ApplicationContext private val context: Context, - private val preferences: MoviesPreferences, - appService: AppService -): SettingsRepository { - - override val currentTheme: Flow = preferences.themeFlow.map { name -> - AppTheme.transform(name ?: AppTheme.FollowSystem.toString()) - } - - override val currentFeedView: Flow = preferences.feedViewFlow.map { name -> - FeedView.transform(name ?: FeedView.FeedList.toString()) - } - - override val currentMovieList: Flow = preferences.movieListFlow.map { className -> - MovieList.transform(className ?: MovieList.NowPlaying.toString()) - } - - override val dynamicColors: Flow = preferences.isDynamicColorsFlow.map { isDynamicColors -> - isDynamicColors ?: (Build.VERSION.SDK_INT >= 31) - } - - override val appVersionData: Flow = flowOf( - AppVersionData( - version = context.packageInfo.versionName, - code = context.packageInfo.code, - flavor = appService.flavor.name, - isDebug = BuildConfig.DEBUG - ) - ) - - override suspend fun selectTheme(appTheme: AppTheme) { - preferences.setTheme(appTheme.toString()) - } - - override suspend fun selectFeedView(feedView: FeedView) { - preferences.setFeedView(feedView.toString()) - } - - override suspend fun selectMovieList(movieList: MovieList) { - preferences.setMovieList(movieList.toString()) - } - - override suspend fun setDynamicColors(value: Boolean) { - preferences.setDynamicColors(value) - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.kt deleted file mode 100644 index 52eacd1f1..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/impl/SuggestionRepositoryImpl.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.michaelbel.movies.repository.impl - -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.common.localization.LocaleController -import org.michaelbel.movies.network.model.Movie -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.network.service.movie.MovieService -import org.michaelbel.movies.persistence.database.dao.MovieDao -import org.michaelbel.movies.persistence.database.dao.SuggestionDao -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.entity.SuggestionDb -import org.michaelbel.movies.repository.SuggestionRepository - -@Singleton -internal class SuggestionRepositoryImpl @Inject constructor( - private val movieService: MovieService, - private val movieDao: MovieDao, - private val suggestionDao: SuggestionDao, - private val localeController: LocaleController -): SuggestionRepository { - - override fun suggestions(): Flow> { - return suggestionDao.suggestionsFlow() - } - - override suspend fun updateSuggestions() { - suggestionDao.removeAll() - - val nowPlayingMovies: List = movieDao.movies(Movie.NOW_PLAYING, 5) - if (nowPlayingMovies.isNotEmpty()) { - suggestionDao.insert(nowPlayingMovies.map { movieDb -> SuggestionDb(movieDb.title) }) - } else { - val movieResponse: List = movieService.movies( - list = Movie.NOW_PLAYING, - language = localeController.language, - page = 1 - ).results.take(5) - suggestionDao.insert(movieResponse.map { movie -> SuggestionDb(movie.title) }) - } - } -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/AccountKtx.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/AccountKtx.kt deleted file mode 100644 index 5e7719059..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/AccountKtx.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.michaelbel.movies.repository.ktx - -import java.util.Locale -import org.michaelbel.movies.network.GRAVATAR_URL -import org.michaelbel.movies.network.formatProfileImage -import org.michaelbel.movies.network.model.Account -import org.michaelbel.movies.persistence.database.entity.AccountDb - -internal val Account.mapToAccountDb: AccountDb - get() = AccountDb( - accountId = id, - avatarUrl = when { - avatar.tmdbAvatar != null -> avatar.tmdbAvatar?.avatarPath.orEmpty().formatProfileImage - avatar.grAvatar != null -> String.format(Locale.US, GRAVATAR_URL, avatar.grAvatar?.hash) - else -> "" - }, - language = lang, - country = country, - name = name, - adult = includeAdult, - username = username - ) \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt deleted file mode 100644 index e79dc5063..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.michaelbel.movies.repository.ktx - -import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException -import org.michaelbel.movies.network.isTmdbApiKeyEmpty - -internal fun checkApiKeyNotNullException() { - if (isTmdbApiKeyEmpty) throw ApiKeyNotNullException -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieKtx.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieKtx.kt deleted file mode 100644 index 992250d1c..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieKtx.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.michaelbel.movies.repository.ktx - -import org.michaelbel.movies.network.model.Movie -import org.michaelbel.movies.persistence.database.entity.MovieDb - -internal val Movie.mapToMovieDb: MovieDb - get() = MovieDb( - movieList = "", - dateAdded = 0L, - page = null, - position = 0, - movieId = id, - overview = overview.orEmpty(), - posterPath = posterPath.orEmpty(), - backdropPath = backdropPath.orEmpty(), - releaseDate = releaseDate.orEmpty(), - title = title.orEmpty(), - voteAverage = voteAverage, - containerColor = null, - onContainerColor = null - ) \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieResponseKtx.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieResponseKtx.kt deleted file mode 100644 index 3cb68657f..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieResponseKtx.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.michaelbel.movies.repository.ktx - -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.persistence.database.entity.MovieDb - -internal fun MovieResponse.mapToMovieDb(movieList: String, page: Int, position: Int): MovieDb { - return MovieDb( - movieList = movieList, - dateAdded = System.currentTimeMillis(), - page = page, - position = position, - movieId = id, - overview = overview.orEmpty(), - posterPath = posterPath.orEmpty(), - backdropPath = backdropPath.orEmpty(), - releaseDate = releaseDate, - title = title, - voteAverage = voteAverage, - containerColor = null, - onContainerColor = null - ) -} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/PackageInfoKtx.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/PackageInfoKtx.kt deleted file mode 100644 index 674731c2b..000000000 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ktx/PackageInfoKtx.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.michaelbel.movies.repository.ktx - -import android.content.Context -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.os.Build - -internal val Context.packageInfo: PackageInfo - get() { - return if (Build.VERSION.SDK_INT >= 33) { - packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0L)) - } else { - packageManager.getPackageInfo(packageName, 0) - } - } - -@Suppress("Deprecation") -internal val PackageInfo.code: Long - get() = if (Build.VERSION.SDK_INT >= 28) longVersionCode else versionCode.toLong() \ No newline at end of file diff --git a/core/src/main/kotlin/org/michaelbel/movies/core/config/RemoteParams.kt b/core/src/main/kotlin/org/michaelbel/movies/core/config/RemoteParams.kt deleted file mode 100644 index ab78565e3..000000000 --- a/core/src/main/kotlin/org/michaelbel/movies/core/config/RemoteParams.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.michaelbel.movies.core.config - -object RemoteParams { - const val PARAM_SETTINGS_ICON_VISIBLE = "param_settings_icon_visible" -} \ No newline at end of file diff --git a/core/src/main/kotlin/org/michaelbel/movies/core/config/di/RemoteConfigModule.kt b/core/src/main/kotlin/org/michaelbel/movies/core/config/di/RemoteConfigModule.kt deleted file mode 100644 index 209d5b0ed..000000000 --- a/core/src/main/kotlin/org/michaelbel/movies/core/config/di/RemoteConfigModule.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.michaelbel.movies.core.config.di - -import com.google.firebase.ktx.Firebase -import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings -import com.google.firebase.remoteconfig.ktx.remoteConfig -import com.google.firebase.remoteconfig.ktx.remoteConfigSettings -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import org.michaelbel.movies.core.config.RemoteParams - -@Module -@InstallIn(SingletonComponent::class) -object RemoteConfigModule { - - private const val FETCH_INTERVAL_IN_SECONDS = 5L - - @Provides - fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig { - val configSettings: FirebaseRemoteConfigSettings = remoteConfigSettings { - fetchTimeoutInSeconds = FETCH_INTERVAL_IN_SECONDS - minimumFetchIntervalInSeconds = FETCH_INTERVAL_IN_SECONDS - } - val defaults: Map = mapOf( - RemoteParams.PARAM_SETTINGS_ICON_VISIBLE to true - ) - val firebaseRemoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig.apply { - setConfigSettingsAsync(configSettings) - setDefaultsAsync(defaults) - } - return firebaseRemoteConfig - } -} \ No newline at end of file diff --git a/core/work/.gitignore b/core/ui-kmp/.gitignore similarity index 100% rename from core/work/.gitignore rename to core/ui-kmp/.gitignore diff --git a/core/ui-kmp/build.gradle.kts b/core/ui-kmp/build.gradle.kts new file mode 100644 index 000000000..2291c304d --- /dev/null +++ b/core/ui-kmp/build.gradle.kts @@ -0,0 +1,74 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + implementation(project(":core:common-kmp")) + implementation(project(":core:network-kmp")) + implementation(project(":core:persistence-kmp")) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.coil.common) + implementation(compose.components.resources) + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.material3) + implementation(compose.runtime) + implementation(compose.runtimeSaveable) + implementation(compose.materialIconsExtended) + implementation(compose.preview) + implementation(compose.ui) + implementation(compose.uiTooling) + } + androidMain.dependencies { + api(libs.androidx.core.splashscreen) + api(libs.androidx.palette.ktx) + api(libs.coil.compose) + api(libs.bundles.androidx.compose) + api(libs.bundles.google.material) + } + } +} + +android { + namespace = "org.michaelbel.movies.ui_kmp" + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/ui-kmp/build/generated/compose/resourceGenerator/kotlin/movies/core/ui_kmp/generated/resources/Res.kt b/core/ui-kmp/build/generated/compose/resourceGenerator/kotlin/movies/core/ui_kmp/generated/resources/Res.kt new file mode 100644 index 000000000..579bdb01e --- /dev/null +++ b/core/ui-kmp/build/generated/compose/resourceGenerator/kotlin/movies/core/ui_kmp/generated/resources/Res.kt @@ -0,0 +1,31 @@ +@file:OptIn( + org.jetbrains.compose.resources.InternalResourceApi::class, + org.jetbrains.compose.resources.ExperimentalResourceApi::class, +) + +package movies.core.ui_kmp.generated.resources + +import kotlin.ByteArray +import kotlin.OptIn +import kotlin.String +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.readResourceBytes + +@ExperimentalResourceApi +internal object Res { + /** + * Reads the content of the resource file at the specified path and returns it as a byte array. + * + * Example: `val bytes = Res.readBytes("files/key.bin")` + * + * @param path The path of the file to read in the compose resource's directory. + * @return The content of the file as a byte array. + */ + public suspend fun readBytes(path: String): ByteArray = readResourceBytes(path) + + public object drawable + + public object string + + public object font +} diff --git a/core/ui-kmp/src/androidMain/AndroidManifest.xml b/core/ui-kmp/src/androidMain/AndroidManifest.xml new file mode 100644 index 000000000..ab25f9008 --- /dev/null +++ b/core/ui-kmp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.kt new file mode 100644 index 000000000..60f90c4d3 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.ui.accessibility + +import androidx.annotation.StringRes +import org.michaelbel.movies.ui_kmp.R + +object MoviesContentDescription { + @StringRes val AccountIcon = R.string.content_description_account_icon + @StringRes val AppIcon = R.string.content_description_app_icon + @StringRes val HistoryIcon = R.string.content_description_download_icon + @StringRes val MovieDetailsImage = R.string.content_description_movie_details_image + @StringRes val VoiceIcon = R.string.content_description_voice_icon + val None: String? = null +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/appicon/MoviesAlias.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/appicon/MoviesAlias.kt similarity index 84% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/appicon/MoviesAlias.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/appicon/MoviesAlias.kt index 1faccff33..af4333553 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/appicon/MoviesAlias.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/appicon/MoviesAlias.kt @@ -4,7 +4,7 @@ import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager import androidx.annotation.DrawableRes -import org.michaelbel.movies.ui.R +import org.michaelbel.movies.ui_kmp.R private fun Context.componentName(iconAlias: IconAlias): ComponentName { return ComponentName(packageName, "org.michaelbel.movies.${iconAlias.key}") @@ -34,6 +34,15 @@ fun Context.installLauncherIcon() { setIcon(IconAlias.Red) } +val Context.enabledIcon: IconAlias + get() = when { + isEnabled(IconAlias.Red) -> IconAlias.Red + isEnabled(IconAlias.Purple) -> IconAlias.Purple + isEnabled(IconAlias.Brown) -> IconAlias.Brown + isEnabled(IconAlias.Amoled) -> IconAlias.Amoled + else -> throw Exception("Icon not found") + } + internal val Context.shortcutSearchIconRes: Int @DrawableRes get() = when { isEnabled(IconAlias.Red) -> R.drawable.ic_shortcut_search_red_48 diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/NotificationBottomSheet.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/NotificationBottomSheet.kt new file mode 100644 index 000000000..9335cbed4 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/NotificationBottomSheet.kt @@ -0,0 +1,185 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package org.michaelbel.movies.ui.compose + +import android.Manifest +import android.app.Activity +import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.ktx.appNotificationSettingsIntent +import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.theme.MoviesTheme +import org.michaelbel.movies.ui_kmp.R + +@Composable +fun NotificationBottomSheet( + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val activityContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + + val permissionContract = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { granted -> + val shouldRequest = (context as Activity).shouldShowRequestPermissionRationale( + Manifest.permission.POST_NOTIFICATIONS + ) + if (!granted && !shouldRequest) { + activityContract.launch(context.appNotificationSettingsIntent) + } + } + + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + val value by rememberInfiniteTransition(label = "") + .animateFloat( + initialValue = 15F, + targetValue = -15F, + animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = 500, + easing = LinearEasing + ), + repeatMode = RepeatMode.Reverse + ), + label = "" + ) + + ModalBottomSheet( + onDismissRequest = onDismissRequest, + sheetState = sheetState, + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier + .size(56.dp) + .background( + color = MaterialTheme.colorScheme.inversePrimary, + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Image( + imageVector = MoviesIcons.Notifications, + contentDescription = MoviesContentDescription.None, + modifier = Modifier.graphicsLayer( + transformOrigin = TransformOrigin( + pivotFractionX = 0.5F, + pivotFractionY = 0.0F, + ), + rotationZ = value + ), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant) + ) + } + + Text( + text = stringResource(R.string.notification_enable_title), + modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + Text( + text = stringResource(R.string.notification_enable_subtitle), + modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + Button( + onClick = { + onDismissRequest() + when { + Build.VERSION.SDK_INT >= 33 -> { + permissionContract.launch(Manifest.permission.POST_NOTIFICATIONS) + } + else -> { + activityContract.launch(context.appNotificationSettingsIntent) + } + } + }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceTint + ), + modifier = Modifier.padding(top = 16.dp, bottom = 32.dp) + ) { + Text( + text = stringResource(if (Build.VERSION.SDK_INT >= 33) R.string.notification_continue else R.string.notification_go_to_settings) + ) + } + } + } +} + +@Composable +@DevicePreviews +private fun NotificationBottomSheetPreview() { + MoviesTheme { + NotificationBottomSheet( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.primaryContainer), + onDismissRequest = {} + ) + } +} + +@Composable +@Preview +private fun NotificationBottomSheetAmoledPreview() { + MoviesTheme( + theme = AppTheme.Amoled + ) { + NotificationBottomSheet( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.primaryContainer), + onDismissRequest = {} + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt similarity index 90% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt index 1a7b62175..351a17842 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt @@ -27,11 +27,13 @@ fun VoiceIcon( val speechRecognizeContract = rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult() ) { activityResult -> - val data: Intent? = activityResult.data - val spokenText: String? = data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.let { results -> + val data = activityResult.data + val spokenText = data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.let { results -> results[0] } - onInputText(spokenText.orEmpty()) + if (!spokenText.isNullOrEmpty()) { + onInputText(spokenText) + } } val onStartSpeechRecognize: () -> Unit = { @@ -42,7 +44,7 @@ fun VoiceIcon( RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM ) - }.also { intent: Intent -> + }.also { intent -> speechRecognizeContract.launch(intent) } } diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/movie/MovieColumn.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/movie/MovieColumn.kt similarity index 91% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/movie/MovieColumn.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/movie/MovieColumn.kt index 7e906ebed..ef06e856a 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/movie/MovieColumn.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/movie/MovieColumn.kt @@ -25,19 +25,19 @@ import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.network.formatPosterImage -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.ui.R +import org.michaelbel.movies.network.config.formatPosterImage +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.ui.accessibility.MoviesContentDescription import org.michaelbel.movies.ui.ktx.context import org.michaelbel.movies.ui.ktx.isErrorOrEmpty import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.preview.provider.MoviePreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme +import org.michaelbel.movies.ui_kmp.R @Composable -fun MovieColumn( - movie: MovieDb, +internal fun MovieColumn( + movie: MoviePojo, modifier: Modifier = Modifier ) { var isNoImageVisible by remember { mutableStateOf(false) } @@ -80,9 +80,7 @@ fun MovieColumn( ) { Text( text = stringResource(R.string.no_image), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.secondary - ) + style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.secondary) ) } @@ -98,9 +96,7 @@ fun MovieColumn( }, maxLines = 10, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) } } @@ -108,7 +104,7 @@ fun MovieColumn( @Composable @DevicePreviews private fun MovieColumnPreview( - @PreviewParameter(MoviePreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MoviePreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme { MovieColumn( @@ -125,7 +121,7 @@ private fun MovieColumnPreview( @Composable @Preview private fun MovieColumnAmoledPreview( - @PreviewParameter(MoviePreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MoviePreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme( theme = AppTheme.Amoled diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/movie/MovieRow.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/movie/MovieRow.kt similarity index 91% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/movie/MovieRow.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/movie/MovieRow.kt index dada6701f..bf857699f 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/movie/MovieRow.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/movie/MovieRow.kt @@ -25,19 +25,19 @@ import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.network.formatBackdropImage -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.ui.R +import org.michaelbel.movies.network.config.formatBackdropImage +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.ui.accessibility.MoviesContentDescription import org.michaelbel.movies.ui.ktx.context import org.michaelbel.movies.ui.ktx.isErrorOrEmpty import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.preview.provider.MoviePreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme +import org.michaelbel.movies.ui_kmp.R @Composable -fun MovieRow( - movie: MovieDb, +internal fun MovieRow( + movie: MoviePojo, modifier: Modifier = Modifier, maxLines: Int = 10 ) { @@ -81,9 +81,7 @@ fun MovieRow( ) { Text( text = stringResource(R.string.no_image), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.secondary - ) + style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.secondary) ) } @@ -99,9 +97,7 @@ fun MovieRow( }, maxLines = maxLines, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) } } @@ -109,7 +105,7 @@ fun MovieRow( @Composable @DevicePreviews private fun MovieRowPreview( - @PreviewParameter(MoviePreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MoviePreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme { MovieRow( @@ -126,7 +122,7 @@ private fun MovieRowPreview( @Composable @Preview private fun MovieRowAmoledPreview( - @PreviewParameter(MoviePreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MoviePreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme( theme = AppTheme.Amoled diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/page/PageContent.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageContent.kt similarity index 75% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/page/PageContent.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageContent.kt index 1200ac87f..6367f3d9a 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/page/PageContent.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageContent.kt @@ -1,6 +1,5 @@ package org.michaelbel.movies.ui.compose.page -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -24,13 +23,13 @@ import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.itemContentType import androidx.paging.compose.itemKey import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.network.isTmdbApiKeyEmpty -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.ui.compose.ApiKeyBox +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.ui.compose.movie.MovieColumn import org.michaelbel.movies.ui.compose.movie.MovieRow +import org.michaelbel.movies.ui.ktx.PageContentColumnModifier +import org.michaelbel.movies.ui.ktx.PageContentGridModifier +import org.michaelbel.movies.ui.ktx.PageContentStaggeredGridModifier import org.michaelbel.movies.ui.ktx.gridColumnsCount -import org.michaelbel.movies.ui.ktx.isNotEmpty import org.michaelbel.movies.ui.ktx.isPagingFailure import org.michaelbel.movies.ui.ktx.isPagingLoading import org.michaelbel.movies.ui.ktx.isPortrait @@ -41,10 +40,10 @@ fun PageContent( lazyListState: LazyListState, lazyGridState: LazyGridState, lazyStaggeredGridState: LazyStaggeredGridState, - pagingItems: LazyPagingItems, + pagingItems: LazyPagingItems, onMovieClick: (String, Int) -> Unit, - modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues() + modifier: Modifier, + contentPadding: PaddingValues ) { when (feedView) { is FeedView.FeedList -> { @@ -81,7 +80,7 @@ fun PageContent( @Composable private fun PageContentColumn( lazyListState: LazyListState, - pagingItems: LazyPagingItems, + pagingItems: LazyPagingItems, onMovieClick: (String, Int) -> Unit, contentPadding: PaddingValues, modifier: Modifier = Modifier @@ -96,25 +95,11 @@ private fun PageContentColumn( key = pagingItems.itemKey(), contentType = pagingItems.itemContentType() ) { index -> - val movieDb: MovieDb? = pagingItems[index] + val movieDb = pagingItems[index] if (movieDb != null) { MovieRow( movie = movieDb, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp) - .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.inversePrimary) - .clickable { onMovieClick(movieDb.movieList, movieDb.movieId) } - ) - } - } - if (isTmdbApiKeyEmpty && pagingItems.isNotEmpty) { - item { - ApiKeyBox( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp) + modifier = PageContentColumnModifier.then(Modifier.clickable { onMovieClick(movieDb.movieList, movieDb.movieId) }) ) } } @@ -123,9 +108,7 @@ private fun PageContentColumn( isPagingLoading -> { item { PagingLoadingBox( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) + modifier = Modifier.fillMaxWidth().height(80.dp) ) } } @@ -149,7 +132,7 @@ private fun PageContentColumn( @Composable private fun PageContentGrid( lazyGridState: LazyGridState, - pagingItems: LazyPagingItems, + pagingItems: LazyPagingItems, onMovieClick: (String, Int) -> Unit, contentPadding: PaddingValues, modifier: Modifier = Modifier @@ -166,17 +149,12 @@ private fun PageContentGrid( key = pagingItems.itemKey(), contentType = pagingItems.itemContentType() ) { index -> - val movieDb: MovieDb? = pagingItems[index] + val movieDb = pagingItems[index] if (movieDb != null) { MovieRow( movie = movieDb, maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) - .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.inversePrimary) - .clickable { onMovieClick(movieDb.movieList, movieDb.movieId) } + modifier = PageContentGridModifier.then(Modifier.clickable { onMovieClick(movieDb.movieList, movieDb.movieId) }) ) } } @@ -185,9 +163,7 @@ private fun PageContentGrid( isPagingLoading -> { item { PagingLoadingBox( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) + modifier = Modifier.fillMaxWidth().height(80.dp) ) } @@ -211,7 +187,7 @@ private fun PageContentGrid( @Composable private fun PageContentStaggeredGrid( lazyStaggeredGridState: LazyStaggeredGridState, - pagingItems: LazyPagingItems, + pagingItems: LazyPagingItems, onMovieClick: (String, Int) -> Unit, contentPadding: PaddingValues, modifier: Modifier = Modifier @@ -229,15 +205,11 @@ private fun PageContentStaggeredGrid( key = pagingItems.itemKey(), contentType = pagingItems.itemContentType() ) { index -> - val movieDb: MovieDb? = pagingItems[index] + val movieDb = pagingItems[index] if (movieDb != null) { MovieColumn( movie = movieDb, - modifier = Modifier - .fillMaxWidth() - .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.inversePrimary) - .clickable { onMovieClick(movieDb.movieList, movieDb.movieId) } + modifier = PageContentStaggeredGridModifier.then(Modifier.clickable { onMovieClick(movieDb.movieList, movieDb.movieId) }) ) } } @@ -246,9 +218,7 @@ private fun PageContentStaggeredGrid( isPagingLoading -> { item { PagingLoadingBox( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) + modifier = Modifier.fillMaxWidth().height(80.dp) ) } diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure.kt new file mode 100644 index 000000000..aad2eac95 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure.kt @@ -0,0 +1,106 @@ +package org.michaelbel.movies.ui.compose.page + +import android.content.Intent +import android.os.Build +import android.provider.Settings +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.theme.MoviesTheme +import org.michaelbel.movies.ui_kmp.R + +@Composable +fun PageFailure( + modifier: Modifier +) { + val settingsPanelContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + + Column( + modifier = modifier, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = MoviesIcons.Info, + contentDescription = MoviesContentDescription.None, + modifier = Modifier.size(36.dp), + tint = MaterialTheme.colorScheme.error + ) + + Text( + text = stringResource(R.string.error_loading), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 16.dp, top = 8.dp, end = 16.dp), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + if (Build.VERSION.SDK_INT >= 29) { + val onCheckConnectivityClick: () -> Unit = { + settingsPanelContract.launch(Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY)) + } + + OutlinedButton( + onClick = onCheckConnectivityClick, + modifier = Modifier + .wrapContentSize() + .padding(start = 16.dp, top = 8.dp, end = 16.dp) + ) { + Text( + text = stringResource(R.string.error_check_internet_connectivity) + ) + } + } + } +} + +@Composable +@DevicePreviews +private fun PageFailurePreview() { + MoviesTheme { + PageFailure( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} + +@Composable +@Preview +private fun PageFailureAmoledPreview() { + MoviesTheme( + theme = AppTheme.Amoled + ) { + PageFailure( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/page/PageLoading.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageLoading.kt similarity index 97% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/page/PageLoading.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageLoading.kt index a2c825ad6..20906acab 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/page/PageLoading.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageLoading.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp import org.michaelbel.movies.common.appearance.FeedView import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.ui.compose.movie.MovieColumn import org.michaelbel.movies.ui.compose.movie.MovieRow import org.michaelbel.movies.ui.ktx.gridColumnsCount @@ -75,7 +75,7 @@ private fun PageLoadingColumn( ) { items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { MovieRow( - movie = MovieDb.Empty, + movie = MoviePojo.Empty, modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 4.dp) @@ -104,7 +104,7 @@ private fun PageLoadingGrid( ) { items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { MovieRow( - movie = MovieDb.Empty, + movie = MoviePojo.Empty, modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp) @@ -134,7 +134,7 @@ private fun PageLoadingStaggeredGrid( ) { items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { MovieColumn( - movie = MovieDb.Empty, + movie = MoviePojo.Empty, modifier = Modifier .fillMaxWidth() .placeholder( diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/icons/MoviesAndroidIcons.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/icons/MoviesAndroidIcons.kt new file mode 100644 index 000000000..7b54dce77 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/icons/MoviesAndroidIcons.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.ui.icons + +import androidx.annotation.DrawableRes +import androidx.compose.ui.graphics.vector.ImageVector +import org.michaelbel.movies.ui_kmp.R + +/** + * Movies icons. Material icons are [ImageVector]s, custom icons are drawable resource IDs. + */ +object MoviesAndroidIcons { + @DrawableRes val MovieFilter24 = R.drawable.ic_movie_filter_24 + @DrawableRes val FileDownload24 = R.drawable.ic_file_download_24 + @DrawableRes val SettingsAccountBox24 = R.drawable.ic_settings_account_box_24 + @DrawableRes val SettingsCinematicBlur24 = R.drawable.ic_settings_cinematic_blur_24 +} \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AndroidConfigurationKtx.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AndroidConfigurationKtx.kt new file mode 100644 index 000000000..50664e36c --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AndroidConfigurationKtx.kt @@ -0,0 +1,63 @@ +package org.michaelbel.movies.ui.ktx + +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import android.util.DisplayMetrics +import android.view.WindowManager +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.core.content.ContextCompat + +val screenWidth: Dp + @Composable get() { + val context = LocalContext.current + val density = LocalDensity.current + return density.run { context.deviceWidth.toDp() } + } + +val screenHeight: Dp + @Composable get() { + val context = LocalContext.current + val density = LocalDensity.current + return density.run { context.deviceHeight.toDp() } + } + +val displayCutoutWindowInsets: WindowInsets + @Composable get() = if (isPortrait) WindowInsets(0, 0, 0, 0) else WindowInsets.displayCutout + +@Suppress("Deprecation") +private inline val Context.deviceWidth: Int + get() { + val windowManager = ContextCompat.getSystemService(this, WindowManager::class.java) as WindowManager + return if (Build.VERSION.SDK_INT >= 30) { + val windowMetrics = windowManager.currentWindowMetrics + val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility( + android.view.WindowInsets.Type.systemBars() + ) + windowMetrics.bounds.width() - insets.left - insets.right + } else { + val displayMetrics = DisplayMetrics() + windowManager.defaultDisplay.getMetrics(displayMetrics) + displayMetrics.widthPixels + } + } + +@Suppress("Deprecation") +private inline val Context.deviceHeight: Int + get() { + val windowManager = ContextCompat.getSystemService(this, WindowManager::class.java) as WindowManager + return if (Build.VERSION.SDK_INT >= 30) { + val windowMetrics = windowManager.currentWindowMetrics + windowMetrics.bounds.height() + } else { + val displayMetrics = DisplayMetrics() + windowManager.defaultDisplay.getMetrics(displayMetrics) + displayMetrics.heightPixels + } + } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtx.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtx.kt similarity index 100% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtx.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AsyncImagePainterStateKtx.kt diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/ktx/ComposableKtx.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/ComposableKtx.kt similarity index 100% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/ktx/ComposableKtx.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/ComposableKtx.kt diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/ConfigurationKtx.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/ConfigurationKtx.kt new file mode 100644 index 000000000..ba84e1e29 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/ConfigurationKtx.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.ui.ktx + +import android.content.res.Configuration +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration + +actual val isPortrait: Boolean + @Composable get() { + val configuration = LocalConfiguration.current + return configuration.orientation == Configuration.ORIENTATION_PORTRAIT + } \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/LazyPagingItemsKtx.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/LazyPagingItemsKtx.kt new file mode 100644 index 000000000..840674bdb --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/LazyPagingItemsKtx.kt @@ -0,0 +1,41 @@ +package org.michaelbel.movies.ui.ktx + +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import org.michaelbel.movies.common.exceptions.PageEmptyException + +internal val LazyPagingItems.isNotEmpty: Boolean + get() = itemCount > 0 + +internal val LazyPagingItems.isEmpty: Boolean + get() = itemCount == 0 + +val LazyPagingItems.isLoading: Boolean + get() = loadState.refresh is LoadState.Loading && isEmpty + +val LazyPagingItems.isFailure: Boolean + get() = loadState.refresh is LoadState.Error && isEmpty + +internal val LazyPagingItems.isPagingLoading: Boolean + get() = isNotEmpty && (isAppendLoading || isAppendRefresh) + +internal val LazyPagingItems.isPagingFailure: Boolean + get() = isNotEmpty && (isAppendError && appendThrowable !is PageEmptyException || isRefreshError && refreshThrowable !is PageEmptyException) + +internal val LazyPagingItems.isRefreshError: Boolean + get() = loadState.refresh is LoadState.Error + +internal val LazyPagingItems.isAppendError: Boolean + get() = loadState.append is LoadState.Error + +val LazyPagingItems.refreshThrowable: Throwable + get() = (loadState.refresh as LoadState.Error).error + +internal val LazyPagingItems.appendThrowable: Throwable + get() = (loadState.append as LoadState.Error).error + +internal val LazyPagingItems.isAppendLoading: Boolean + get() = loadState.append is LoadState.Loading + +internal val LazyPagingItems.isAppendRefresh: Boolean + get() = loadState.refresh is LoadState.Loading \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/SettingsKtx.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/SettingsKtx.kt new file mode 100644 index 000000000..bf70cc451 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/SettingsKtx.kt @@ -0,0 +1,36 @@ +package org.michaelbel.movies.ui.ktx + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.Settings +import androidx.core.net.toUri +import org.michaelbel.movies.ui.shortcuts.INTENT_ACTION_SETTINGS + +val Context.appNotificationSettingsIntent: Intent + get() { + val intent = Intent() + when { + Build.VERSION.SDK_INT >= 26 -> { + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName) + } + else -> { + intent.action = "android.settings.APP_NOTIFICATION_SETTINGS" + intent.putExtra("app_package", packageName) + intent.putExtra("app_uid", applicationInfo.uid) + } + } + return intent + } + +fun Activity.resolveNotificationPreferencesIntent() { + val categories = intent.categories + if (categories != null && categories.isNotEmpty()) { + val isCategoryNotificationPreferences = categories.first() == "android.intent.category.NOTIFICATION_PREFERENCES" + if (isCategoryNotificationPreferences) { + startActivity(Intent(Intent.ACTION_VIEW, INTENT_ACTION_SETTINGS.toUri())) + } + } +} \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/WindowInsetsKtx.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/WindowInsetsKtx.kt new file mode 100644 index 000000000..0baa65cc4 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/WindowInsetsKtx.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.ui.ktx + +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +actual val modifierDisplayCutoutWindowInsets: Modifier + @Composable get() = Modifier.windowInsetsPadding(displayCutoutWindowInsets) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/lifecycle/OnLifecycleEvent.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/lifecycle/OnLifecycleEvent.kt similarity index 94% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/lifecycle/OnLifecycleEvent.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/lifecycle/OnLifecycleEvent.kt index 2cbaddc10..76f9d7e24 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/lifecycle/OnLifecycleEvent.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/lifecycle/OnLifecycleEvent.kt @@ -8,6 +8,17 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner +@Composable +fun OnResume( + onResume: () -> Unit +) { + OnLifecycleEvent( + onEvent = { _, event -> + onResume().takeIf { event == Lifecycle.Event.ON_RESUME } + } + ) +} + @Composable private fun OnLifecycleEvent( onEvent: (owner: LifecycleOwner, event: Lifecycle.Event) -> Unit @@ -16,7 +27,7 @@ private fun OnLifecycleEvent( val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current) DisposableEffect(lifecycleOwner.value) { - val lifecycle: Lifecycle = lifecycleOwner.value.lifecycle + val lifecycle = lifecycleOwner.value.lifecycle val observer = LifecycleEventObserver { owner, event -> eventHandler.value(owner, event) } @@ -25,15 +36,4 @@ private fun OnLifecycleEvent( lifecycle.removeObserver(observer) } } -} - -@Composable -fun OnResume( - onResume: () -> Unit -) { - OnLifecycleEvent( - onEvent = { _, event -> - onResume().takeIf { event == Lifecycle.Event.ON_RESUME } - } - ) } \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/Placeholder.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/Placeholder.kt new file mode 100644 index 000000000..b3410180e --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/Placeholder.kt @@ -0,0 +1,261 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.michaelbel.movies.ui.placeholder + +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.InfiniteRepeatableSpec +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.node.Ref +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.unit.LayoutDirection + +/** + * Contains default values used by [Modifier.placeholder] and [PlaceholderHighlight]. + */ +internal object PlaceholderDefaults { + /** + * The default [InfiniteRepeatableSpec] to use for [fade]. + */ + val fadeAnimationSpec: InfiniteRepeatableSpec by lazy { + infiniteRepeatable( + animation = tween(delayMillis = 200, durationMillis = 600), + repeatMode = RepeatMode.Reverse, + ) + } + + /** + * The default [InfiniteRepeatableSpec] to use for [shimmer]. + */ + val shimmerAnimationSpec: InfiniteRepeatableSpec by lazy { + infiniteRepeatable( + animation = tween(durationMillis = 1700, delayMillis = 200), + repeatMode = RepeatMode.Restart + ) + } +} + +/** + * Draws some skeleton UI which is typically used whilst content is 'loading'. + * + * A version of this modifier which uses appropriate values for Material themed apps is available + * in the 'Placeholder Material' library. + * + * You can provide a [PlaceholderHighlight] which runs an highlight animation on the placeholder. + * The [shimmer] and [fade] implementations are provided for easy usage. + * + * A cross-fade transition will be applied to the content and placeholder UI when the [visible] + * value changes. The transition can be customized via the [contentFadeTransitionSpec] and + * [placeholderFadeTransitionSpec] parameters. + * + * You can find more information on the pattern at the Material Theming + * [Placeholder UI](https://material.io/design/communication/launch-screen.html#placeholder-ui) + * guidelines. + * + * @param visible whether the placeholder should be visible or not. + * @param color the color used to draw the placeholder UI. + * @param shape desired shape of the placeholder. Defaults to [RectangleShape]. + * @param highlight optional highlight animation. + * @param placeholderFadeTransitionSpec The transition spec to use when fading the placeholder + * on/off screen. The boolean parameter defined for the transition is [visible]. + * @param contentFadeTransitionSpec The transition spec to use when fading the content + * on/off screen. The boolean parameter defined for the transition is [visible]. + */ +fun Modifier.placeholder( + visible: Boolean, + color: Color, + shape: Shape = RectangleShape, + highlight: PlaceholderHighlight? = null, + placeholderFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() }, + contentFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() } +): Modifier = composed( + inspectorInfo = debugInspectorInfo { + name = "placeholder" + value = visible + properties["visible"] = visible + properties["color"] = color + properties["highlight"] = highlight + properties["shape"] = shape + } +) { + // Values used for caching purposes + val lastSize = remember { Ref() } + val lastLayoutDirection = remember { Ref() } + val lastOutline = remember { Ref() } + + // The current highlight animation progress + var highlightProgress: Float by remember { mutableFloatStateOf(0F) } + + // This is our crossfade transition + val transitionState = remember { MutableTransitionState(visible) }.apply { + targetState = visible + } + val transition = updateTransition(transitionState, "placeholder_crossfade") + + val placeholderAlpha by transition.animateFloat( + transitionSpec = placeholderFadeTransitionSpec, + label = "placeholder_fade", + targetValueByState = { placeholderVisible -> if (placeholderVisible) 1F else 0F } + ) + val contentAlpha by transition.animateFloat( + transitionSpec = contentFadeTransitionSpec, + label = "content_fade", + targetValueByState = { placeholderVisible -> if (placeholderVisible) 0F else 1F } + ) + + // Run the optional animation spec and update the progress if the placeholder is visible + val animationSpec = highlight?.animationSpec + if (animationSpec != null && (visible || placeholderAlpha >= 0.01F)) { + val infiniteTransition = rememberInfiniteTransition(label = "") + highlightProgress = infiniteTransition.animateFloat( + initialValue = 0F, + targetValue = 1F, + animationSpec = animationSpec, + label = "" + ).value + } + + val paint = remember { Paint() } + remember(color, shape, highlight) { + drawWithContent { + // Draw the composable content first + if (contentAlpha in 0.01F..0.99F) { + // If the content alpha is between 1% and 99%, draw it in a layer with + // the alpha applied + paint.alpha = contentAlpha + withLayer(paint) { + with(this@drawWithContent) { + drawContent() + } + } + } else if (contentAlpha >= 0.99F) { + // If the content alpha is > 99%, draw it with no alpha + drawContent() + } + + if (placeholderAlpha in 0.01F..0.99F) { + // If the placeholder alpha is between 1% and 99%, draw it in a layer with + // the alpha applied + paint.alpha = placeholderAlpha + withLayer(paint) { + lastOutline.value = drawPlaceholder( + shape = shape, + color = color, + highlight = highlight, + progress = highlightProgress, + lastOutline = lastOutline.value, + lastLayoutDirection = lastLayoutDirection.value, + lastSize = lastSize.value, + ) + } + } else if (placeholderAlpha >= 0.99F) { + // If the placeholder alpha is > 99%, draw it with no alpha + lastOutline.value = drawPlaceholder( + shape = shape, + color = color, + highlight = highlight, + progress = highlightProgress, + lastOutline = lastOutline.value, + lastLayoutDirection = lastLayoutDirection.value, + lastSize = lastSize.value, + ) + } + + // Keep track of the last size & layout direction + lastSize.value = size + lastLayoutDirection.value = layoutDirection + } + } +} + +private fun DrawScope.drawPlaceholder( + shape: Shape, + color: Color, + highlight: PlaceholderHighlight?, + progress: Float, + lastOutline: Outline?, + lastLayoutDirection: LayoutDirection?, + lastSize: Size?, +): Outline? { + // shortcut to avoid Outline calculation and allocation + if (shape === RectangleShape) { + // Draw the initial background color + drawRect(color = color) + + if (highlight != null) { + drawRect( + brush = highlight.brush(progress, size), + alpha = highlight.alpha(progress), + ) + } + // We didn't create an outline so return null + return null + } + + // Otherwise we need to create an outline from the shape + val outline = lastOutline.takeIf { + size == lastSize && layoutDirection == lastLayoutDirection + } ?: shape.createOutline(size, layoutDirection, this) + + // Draw the placeholder color + drawOutline(outline = outline, color = color) + + if (highlight != null) { + drawOutline( + outline = outline, + brush = highlight.brush(progress, size), + alpha = highlight.alpha(progress), + ) + } + + // Return the outline we used + return outline +} + +private inline fun DrawScope.withLayer( + paint: Paint, + drawBlock: DrawScope.() -> Unit, +) = drawIntoCanvas { canvas -> + canvas.saveLayer(size.toRect(), paint) + drawBlock() + canvas.restore() +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/placeholder/PlaceholderHightlight.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/PlaceholderHightlight.kt similarity index 92% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/placeholder/PlaceholderHightlight.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/PlaceholderHightlight.kt index 72ba90680..5de516f6b 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/placeholder/PlaceholderHightlight.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/PlaceholderHightlight.kt @@ -14,6 +14,12 @@ * limitations under the License. */ +@file:Suppress( + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING", + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", + "NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING" +) + package org.michaelbel.movies.ui.placeholder import androidx.annotation.FloatRange @@ -66,9 +72,9 @@ interface PlaceholderHighlight { * @param highlightColor the color of the highlight which is faded in/out. * @param animationSpec the [AnimationSpec] to configure the animation. */ -fun PlaceholderHighlight.Companion.fade( +internal fun PlaceholderHighlight.Companion.fade( highlightColor: Color, - animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.fadeAnimationSpec, + animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.fadeAnimationSpec ): PlaceholderHighlight = Fade( highlightColor = highlightColor, animationSpec = animationSpec, @@ -86,10 +92,10 @@ fun PlaceholderHighlight.Companion.fade( * @param progressForMaxAlpha The progress where the shimmer should be at it's peak opacity. * Defaults to 0.6f. */ -fun PlaceholderHighlight.Companion.shimmer( +internal fun PlaceholderHighlight.Companion.shimmer( highlightColor: Color, animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.shimmerAnimationSpec, - @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6F, + @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6F ): PlaceholderHighlight = Shimmer( highlightColor = highlightColor, animationSpec = animationSpec, diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/material3/Placeholder.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/material3/Placeholder.kt new file mode 100644 index 000000000..bb92da487 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/material3/Placeholder.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.michaelbel.movies.ui.placeholder.material3 + +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.Transition +import androidx.compose.animation.core.spring +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.isSpecified +import org.michaelbel.movies.ui.placeholder.PlaceholderDefaults +import org.michaelbel.movies.ui.placeholder.PlaceholderHighlight +import org.michaelbel.movies.ui.placeholder.placeholder + +/** + * Returns the value used as the the `color` parameter value on [Modifier.placeholder]. + * + * @param backgroundColor The current background color of the layout. Defaults to + * `MaterialTheme.colorScheme.surface`. + * @param contentColor The content color to be used on top of [backgroundColor]. + * @param contentAlpha The alpha component to set on [contentColor] when compositing the color + * on top of [backgroundColor]. Defaults to `0.1F`. + */ +@Composable +internal fun PlaceholderDefaults.color( + backgroundColor: Color = MaterialTheme.colorScheme.surface, + contentColor: Color = contentColorFor(backgroundColor), + contentAlpha: Float = 0.1F +): Color = contentColor.copy(contentAlpha).compositeOver(backgroundColor) + +/** + * Returns the value used as the the `highlightColor` parameter value of + * [PlaceholderHighlight.Companion.fade]. + * + * @param backgroundColor The current background color of the layout. Defaults to + * `MaterialTheme.colorScheme.surface`. + * @param alpha The alpha component to set on [backgroundColor]. Defaults to `0.3F`. + */ +@Composable +internal fun PlaceholderDefaults.fadeHighlightColor( + backgroundColor: Color = MaterialTheme.colorScheme.surface, + alpha: Float = 0.3F +): Color = backgroundColor.copy(alpha = alpha) + +/** + * Returns the value used as the the `highlightColor` parameter value of + * [PlaceholderHighlight.Companion.shimmer]. + * + * @param backgroundColor The current background color of the layout. Defaults to + * `MaterialTheme.colorScheme.inverseSurface`. + * @param alpha The alpha component to set on [backgroundColor]. Defaults to `0.75F`. + */ +@Composable +internal fun PlaceholderDefaults.shimmerHighlightColor( + backgroundColor: Color = MaterialTheme.colorScheme.inverseSurface, + alpha: Float = 0.75F +): Color { + return backgroundColor.copy(alpha = alpha) +} + +/** + * Draws some skeleton UI which is typically used whilst content is 'loading'. + * + * To customize the color and shape of the placeholder, you can use the foundation version of + * [Modifier.placeholder], along with the values provided by [PlaceholderDefaults]. + * + * A cross-fade transition will be applied to the content and placeholder UI when the [visible] + * value changes. The transition can be customized via the [contentFadeTransitionSpec] and + * [placeholderFadeTransitionSpec] parameters. + * + * You can provide a [PlaceholderHighlight] which runs an highlight animation on the placeholder. + * The [shimmer] and [fade] implementations are provided for easy usage. + * + * You can find more information on the pattern at the Material Theming + * [Placeholder UI](https://material.io/design/communication/launch-screen.html#placeholder-ui) + * guidelines. + * + * @sample com.google.accompanist.sample.placeholder.DocSample_Material_Placeholder + * + * @param visible whether the placeholder should be visible or not. + * @param color the color used to draw the placeholder UI. If [Color.Unspecified] is provided, + * the placeholder will use [PlaceholderDefaults.color]. + * @param shape desired shape of the placeholder. If null is provided the placeholder + * will use the small shape set in [MaterialTheme.shapes]. + * @param highlight optional highlight animation. + * @param placeholderFadeTransitionSpec The transition spec to use when fading the placeholder + * on/off screen. The boolean parameter defined for the transition is [visible]. + * @param contentFadeTransitionSpec The transition spec to use when fading the content + * on/off screen. The boolean parameter defined for the transition is [visible]. + */ +internal fun Modifier.placeholder( + visible: Boolean, + color: Color = Color.Unspecified, + shape: Shape? = null, + highlight: PlaceholderHighlight? = null, + placeholderFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() }, + contentFadeTransitionSpec: @Composable Transition.Segment.() -> FiniteAnimationSpec = { spring() } +): Modifier = composed { + Modifier.placeholder( + visible = visible, + color = if (color.isSpecified) color else PlaceholderDefaults.color(), + shape = shape ?: MaterialTheme.shapes.small, + highlight = highlight, + placeholderFadeTransitionSpec = placeholderFadeTransitionSpec, + contentFadeTransitionSpec = contentFadeTransitionSpec, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlight.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlight.kt similarity index 96% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlight.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlight.kt index 192b87704..8c18adf55 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlight.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/placeholder/material3/PlaceholderHighlight.kt @@ -33,7 +33,7 @@ import org.michaelbel.movies.ui.placeholder.shimmer */ @Composable fun PlaceholderHighlight.Companion.fade( - animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.fadeAnimationSpec, + animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.fadeAnimationSpec ) = PlaceholderHighlight.fade( highlightColor = PlaceholderDefaults.fadeHighlightColor(), animationSpec = animationSpec, @@ -51,9 +51,9 @@ fun PlaceholderHighlight.Companion.fade( * Defaults to 0.6F. */ @Composable -fun PlaceholderHighlight.Companion.shimmer( +internal fun PlaceholderHighlight.Companion.shimmer( animationSpec: InfiniteRepeatableSpec = PlaceholderDefaults.shimmerAnimationSpec, - @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6F, + @FloatRange(from = 0.0, to = 1.0) progressForMaxAlpha: Float = 0.6F ): PlaceholderHighlight = PlaceholderHighlight.shimmer( highlightColor = PlaceholderDefaults.shimmerHighlightColor(), animationSpec = animationSpec, diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreview.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreview.kt new file mode 100644 index 000000000..5fba89761 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreview.kt @@ -0,0 +1,11 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.ui.preview + +import androidx.compose.ui.tooling.preview.Preview + +@Preview( + widthDp = 800, + heightDp = 360 +) +annotation class DeviceLandscapePreview \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt similarity index 86% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt index 6b74e196a..52eea7e67 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + package org.michaelbel.movies.ui.preview import android.content.res.Configuration diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt similarity index 83% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt index b87e3e56b..a53510e9a 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + package org.michaelbel.movies.ui.preview import android.content.res.Configuration diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.kt similarity index 89% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.kt index 85228e6cb..fbe293d35 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceUserLandscapePreviews.kt @@ -1,3 +1,5 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + package org.michaelbel.movies.ui.preview import android.content.res.Configuration diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceUserPreviews.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceUserPreviews.kt new file mode 100644 index 000000000..23add676e --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/DeviceUserPreviews.kt @@ -0,0 +1,12 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package org.michaelbel.movies.ui.preview + +import androidx.compose.ui.tooling.preview.Preview + +@Preview +@Preview( + widthDp = 800, + heightDp = 360 +) +annotation class DeviceUserPreviews \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/AccountPreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/AccountPreviewParameterProvider.kt new file mode 100644 index 000000000..f2cbef37d --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/AccountPreviewParameterProvider.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.persistence.database.entity.AccountPojo + +class AccountPreviewParameterProvider: CollectionPreviewParameterProvider( + listOf( + AccountPojo( + accountId = 7692212, + avatarUrl = "", + language = "en", + country = "US", + name = "Michael Bely", + adult = true, + username = "michaelbel" + ) + ) +) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/AppearancePreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/AppearancePreviewParameterProvider.kt new file mode 100644 index 000000000..8a3de6fae --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/AppearancePreviewParameterProvider.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.common.appearance.FeedView + +class AppearancePreviewParameterProvider: CollectionPreviewParameterProvider(FeedView.VALUES) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/BooleanPreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/BooleanPreviewParameterProvider.kt new file mode 100644 index 000000000..31e2cfa77 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/BooleanPreviewParameterProvider.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider + +class BooleanPreviewParameterProvider: CollectionPreviewParameterProvider( + listOf(true, false) +) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/IconAliasPreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/IconAliasPreviewParameterProvider.kt new file mode 100644 index 000000000..4a2d87875 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/IconAliasPreviewParameterProvider.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.ui.appicon.IconAlias + +class IconAliasPreviewParameterProvider: CollectionPreviewParameterProvider(IconAlias.VALUES) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/LanguagePreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/LanguagePreviewParameterProvider.kt new file mode 100644 index 000000000..29811b7b4 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/LanguagePreviewParameterProvider.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.common.localization.model.AppLanguage + +class LanguagePreviewParameterProvider: CollectionPreviewParameterProvider(AppLanguage.VALUES) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.kt similarity index 76% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.kt index 551ffff2d..39998ccd3 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MovieDbPreviewParameterProvider.kt @@ -1,12 +1,12 @@ package org.michaelbel.movies.ui.preview.provider -import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import org.michaelbel.movies.network.model.Movie -import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.entity.MoviePojo -class MovieDbPreviewParameterProvider: PreviewParameterProvider { - override val values: Sequence = sequenceOf( - MovieDb( +class MovieDbPreviewParameterProvider: CollectionPreviewParameterProvider( + listOf( + MoviePojo( movieList = Movie.NOW_PLAYING, dateAdded = System.currentTimeMillis(), page = null, @@ -22,4 +22,4 @@ class MovieDbPreviewParameterProvider: PreviewParameterProvider { onContainerColor = null ) ) -} \ No newline at end of file +) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MovieListPreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MovieListPreviewParameterProvider.kt new file mode 100644 index 000000000..e20a14757 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MovieListPreviewParameterProvider.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.common.list.MovieList + +class MovieListPreviewParameterProvider: CollectionPreviewParameterProvider(MovieList.VALUES) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.kt similarity index 77% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.kt index 8d4e4536c..43a41d57a 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/MoviePreviewParameterProvider.kt @@ -1,12 +1,12 @@ package org.michaelbel.movies.ui.preview.provider -import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import org.michaelbel.movies.network.model.Movie -import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.entity.MoviePojo -class MoviePreviewParameterProvider: PreviewParameterProvider { - override val values: Sequence = sequenceOf( - MovieDb( +class MoviePreviewParameterProvider: CollectionPreviewParameterProvider( + listOf( + MoviePojo( movieList = Movie.NOW_PLAYING, dateAdded = System.currentTimeMillis(), page = null, @@ -26,4 +26,4 @@ class MoviePreviewParameterProvider: PreviewParameterProvider { onContainerColor = null ) ) -} \ No newline at end of file +) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/SuggestionDbPreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/SuggestionDbPreviewParameterProvider.kt new file mode 100644 index 000000000..5de1b1890 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/SuggestionDbPreviewParameterProvider.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo + +class SuggestionDbPreviewParameterProvider: CollectionPreviewParameterProvider>( + listOf( + listOf( + SuggestionPojo( + title = "Leo" + ), + SuggestionPojo( + title = "Trolls Band Together" + ), + SuggestionPojo( + title = "Five Nights at Freddy's" + ), + SuggestionPojo( + title = "Godzilla Minus One" + ), + SuggestionPojo( + title = "Napoleon" + ) + ) + ) +) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/ThemePreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/ThemePreviewParameterProvider.kt new file mode 100644 index 000000000..96e2496aa --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/ThemePreviewParameterProvider.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.common.theme.AppTheme + +class ThemePreviewParameterProvider: CollectionPreviewParameterProvider(AppTheme.VALUES) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/TitlePreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/TitlePreviewParameterProvider.kt new file mode 100644 index 000000000..d14128c7e --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/TitlePreviewParameterProvider.kt @@ -0,0 +1,10 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider + +class TitlePreviewParameterProvider: CollectionPreviewParameterProvider( + listOf( + "How to Train Your Dragon", + "Three Billboards Outside Ebbing, Missouri" + ) +) \ No newline at end of file diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/VersionPreviewParameterProvider.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/VersionPreviewParameterProvider.kt new file mode 100644 index 000000000..1d56c5ce9 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/preview/provider/VersionPreviewParameterProvider.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.ui.preview.provider + +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import org.michaelbel.movies.common.version.AppVersionData + +class VersionPreviewParameterProvider: CollectionPreviewParameterProvider( + listOf( + AppVersionData( + flavor = "GMS" + ), + AppVersionData( + flavor = "GMS" + ), + AppVersionData( + flavor = "FOSS" + ) + ) +) \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/shortcuts/MoviesShortcuts.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/shortcuts/MoviesShortcuts.kt similarity index 97% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/shortcuts/MoviesShortcuts.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/shortcuts/MoviesShortcuts.kt index ef70fe68a..6bcbae62d 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/shortcuts/MoviesShortcuts.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/shortcuts/MoviesShortcuts.kt @@ -6,9 +6,9 @@ import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat import androidx.core.net.toUri -import org.michaelbel.movies.ui.R import org.michaelbel.movies.ui.appicon.shortcutSearchIconRes import org.michaelbel.movies.ui.appicon.shortcutSettingsIconRes +import org.michaelbel.movies.ui_kmp.R private const val SEARCH_SHORTCUT_ID = "searchShortcutId" private const val SETTINGS_SHORTCUT_ID = "settingsShortcutId" diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/theme/Theme.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/theme/Theme.kt new file mode 100644 index 000000000..39f500bd1 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/theme/Theme.kt @@ -0,0 +1,78 @@ +package org.michaelbel.movies.ui.theme + +import androidx.activity.SystemBarStyle +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.ripple.LocalRippleTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.graphics.Color +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.color.PaletteStyle +import org.michaelbel.movies.ui.color.TonalPalettes.Companion.toTonalPalettes +import org.michaelbel.movies.ui.ktx.context +import org.michaelbel.movies.ui.theme.model.ComposeTheme +import org.michaelbel.movies.ui.theme.provider.MoviesRippleTheme + +private const val ColorTransparent = android.graphics.Color.TRANSPARENT + +@Composable +actual fun MoviesTheme( + themeData: ThemeData, + theme: AppTheme, + enableEdgeToEdge: (Any, Any) -> Unit, + content: @Composable () -> Unit +) { + val seedColorPalettes = Color(themeData.seedColor).toTonalPalettes(paletteStyles.getOrElse(themeData.paletteKey) { PaletteStyle.TonalSpot }) + val (colorScheme, detectDarkMode) = when (themeData.appTheme) { + AppTheme.NightNo -> { + ComposeTheme( + colorScheme = if (themeData.dynamicColors) dynamicLightColorScheme(context) else seedColorPalettes.paletteLightColorScheme, + detectDarkMode = false + ) + } + AppTheme.NightYes -> { + ComposeTheme( + colorScheme = if (themeData.dynamicColors) dynamicDarkColorScheme(context) else seedColorPalettes.paletteDarkColorScheme, + detectDarkMode = true + ) + } + AppTheme.FollowSystem -> { + val darkTheme = isSystemInDarkTheme() + ComposeTheme( + colorScheme = if (themeData.dynamicColors) { + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } else { + if (darkTheme) seedColorPalettes.paletteDarkColorScheme else seedColorPalettes.paletteLightColorScheme + }, + detectDarkMode = darkTheme + ) + } + AppTheme.Amoled -> { + ComposeTheme( + colorScheme = AmoledColorScheme, + detectDarkMode = true + ) + } + } + + enableEdgeToEdge( + SystemBarStyle.auto(ColorTransparent, ColorTransparent) { detectDarkMode }, + SystemBarStyle.auto(ColorTransparent, ColorTransparent) { detectDarkMode } + ) + + MaterialTheme( + colorScheme = colorScheme, + shapes = MoviesShapes, + typography = MoviesTypography + ) { + CompositionLocalProvider( + LocalRippleTheme provides MoviesRippleTheme + ) { + content() + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/theme/Type.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/theme/Type.kt similarity index 95% rename from core/ui/src/main/kotlin/org/michaelbel/movies/ui/theme/Type.kt rename to core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/theme/Type.kt index 10f7fc1d3..e85f7602a 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/theme/Type.kt +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/theme/Type.kt @@ -4,7 +4,7 @@ import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily -import org.michaelbel.movies.ui.R +import org.michaelbel.movies.ui_kmp.R private val OpenSans = FontFamily( Font(R.font.open_sans), diff --git a/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/tile/MoviesTileService.kt b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/tile/MoviesTileService.kt new file mode 100644 index 000000000..7dba98d65 --- /dev/null +++ b/core/ui-kmp/src/androidMain/kotlin/org/michaelbel/movies/ui/tile/MoviesTileService.kt @@ -0,0 +1,55 @@ +@file:SuppressLint("StartActivityAndCollapseDeprecated") +@file:Suppress( + "DEPRECATION", + "EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING" +) + +package org.michaelbel.movies.ui.tile + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Intent +import android.graphics.drawable.Icon +import android.os.Build +import android.service.quicksettings.TileService +import android.widget.Toast +import androidx.annotation.RequiresApi +import org.michaelbel.movies.ui.icons.MoviesAndroidIcons +import org.michaelbel.movies.ui_kmp.R + +@RequiresApi(24) +class MoviesTileService: TileService() { + + override fun onTileAdded() { + super.onTileAdded() + Toast.makeText(this, R.string.tile_added, Toast.LENGTH_SHORT).show() + } + + override fun onStartListening() { + super.onStartListening() + val tile = qsTile + tile.label = getString(R.string.tile_title) + tile.icon = Icon.createWithResource(this, MoviesAndroidIcons.MovieFilter24) + tile.updateTile() + } + + override fun onClick() { + super.onClick() + runCatching { + val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + val pendingIntent = PendingIntent.getActivity( + this, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + if (Build.VERSION.SDK_INT >= 34) { + startActivityAndCollapse(pendingIntent) + } else { + startActivityAndCollapse(intent) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/res/values-night-v23/themes.xml b/core/ui-kmp/src/androidMain/res/values-night-v23/themes.xml similarity index 100% rename from core/ui/src/main/res/values-night-v23/themes.xml rename to core/ui-kmp/src/androidMain/res/values-night-v23/themes.xml diff --git a/core/ui-kmp/src/androidMain/res/values-night-v27/themes.xml b/core/ui-kmp/src/androidMain/res/values-night-v27/themes.xml new file mode 100644 index 000000000..39fdad25a --- /dev/null +++ b/core/ui-kmp/src/androidMain/res/values-night-v27/themes.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + - - - - + + + + \ No newline at end of file diff --git a/core/widget-kmp/src/androidMain/res/values-v31/themes.xml b/core/widget-kmp/src/androidMain/res/values-v31/themes.xml new file mode 100644 index 000000000..68d04a0b7 --- /dev/null +++ b/core/widget-kmp/src/androidMain/res/values-v31/themes.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/core/widget-kmp/src/androidMain/res/values/attrs.xml b/core/widget-kmp/src/androidMain/res/values/attrs.xml new file mode 100644 index 000000000..6554bbc98 --- /dev/null +++ b/core/widget-kmp/src/androidMain/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/core/widget-kmp/src/androidMain/res/values/glance_colors.xml b/core/widget-kmp/src/androidMain/res/values/glance_colors.xml new file mode 100644 index 000000000..b3b1c73bf --- /dev/null +++ b/core/widget-kmp/src/androidMain/res/values/glance_colors.xml @@ -0,0 +1,33 @@ + + + @color/m3_sys_color_light_primary + @color/m3_sys_color_light_on_primary + @color/m3_sys_color_light_inverse_primary + @color/m3_sys_color_light_primary_container + @color/m3_sys_color_light_on_primary_container + @color/m3_sys_color_light_secondary + @color/m3_sys_color_light_on_secondary + @color/m3_sys_color_light_secondary_container + @color/m3_sys_color_light_on_secondary_container + @color/m3_sys_color_light_tertiary + @color/m3_sys_color_light_on_tertiary + @color/m3_sys_color_light_tertiary_container + @color/m3_sys_color_light_on_tertiary_container + @color/m3_sys_color_light_background + @color/m3_sys_color_light_on_background + @color/m3_sys_color_light_surface + @color/m3_sys_color_light_on_surface + @color/m3_sys_color_light_surface_variant + @color/m3_sys_color_light_on_surface_variant + @color/m3_sys_color_light_inverse_surface + @color/m3_sys_color_light_inverse_on_surface + @color/m3_sys_color_light_outline + @color/m3_sys_color_light_error + @color/m3_sys_color_light_on_error + @color/m3_sys_color_light_error_container + @color/m3_sys_color_light_on_error_container + @color/m3_default_color_primary_text + @color/m3_dark_default_color_primary_text + @color/m3_default_color_secondary_text + @color/m3_dark_default_color_secondary_text + \ No newline at end of file diff --git a/core/widget-kmp/src/androidMain/res/values/strings.xml b/core/widget-kmp/src/androidMain/res/values/strings.xml new file mode 100644 index 000000000..76479c716 --- /dev/null +++ b/core/widget-kmp/src/androidMain/res/values/strings.xml @@ -0,0 +1,9 @@ + + + Display upcoming movies + Upcoming Movies + Configure AppWidget + Configure soon... + Widget pinned on homescreen + Movies List is Empty + \ No newline at end of file diff --git a/core/widget-kmp/src/androidMain/res/values/styles.xml b/core/widget-kmp/src/androidMain/res/values/styles.xml new file mode 100644 index 000000000..118c7db49 --- /dev/null +++ b/core/widget-kmp/src/androidMain/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/core/widget-kmp/src/androidMain/res/values/themes.xml b/core/widget-kmp/src/androidMain/res/values/themes.xml new file mode 100644 index 000000000..0bd0d0e25 --- /dev/null +++ b/core/widget-kmp/src/androidMain/res/values/themes.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/core/widget-kmp/src/androidMain/res/xml/movies_appwidget_provider.xml b/core/widget-kmp/src/androidMain/res/xml/movies_appwidget_provider.xml new file mode 100644 index 000000000..0ebe743a4 --- /dev/null +++ b/core/widget-kmp/src/androidMain/res/xml/movies_appwidget_provider.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/feature/account/.gitignore b/core/work-kmp/.gitignore similarity index 100% rename from feature/account/.gitignore rename to core/work-kmp/.gitignore diff --git a/core/work-kmp/build.gradle.kts b/core/work-kmp/build.gradle.kts new file mode 100644 index 000000000..fafff4dde --- /dev/null +++ b/core/work-kmp/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:interactor-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:network-kmp")) + implementation(project(":core:notifications-kmp")) + implementation(project(":core:persistence-kmp")) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.kotlinx.serialization.json) + implementation(libs.androidx.work.runtime.ktx) + implementation(libs.koin.android) + implementation(libs.koin.androidx.workmanager) + } + } +} + +android { + namespace = "org.michaelbel.movies.work_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_AccountUpdateWorker_HiltModule.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_AccountUpdateWorker_HiltModule.java new file mode 100644 index 000000000..96ea974c9 --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_AccountUpdateWorker_HiltModule.java @@ -0,0 +1,15 @@ +package hilt_aggregated_deps; + +import dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps; +import javax.annotation.processing.Generated; + +/** + * This class should only be referenced by generated code! This class aggregates information across multiple compilations. + */ +@AggregatedDeps( + components = "dagger.hilt.components.SingletonComponent", + modules = "org.michaelbel.movies.work.AccountUpdateWorker_HiltModule" +) +@Generated("dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsGenerator") +public class _org_michaelbel_movies_work_AccountUpdateWorker_HiltModule { +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_DownloadImageWorker_HiltModule.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_DownloadImageWorker_HiltModule.java new file mode 100644 index 000000000..84e272dc3 --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_DownloadImageWorker_HiltModule.java @@ -0,0 +1,15 @@ +package hilt_aggregated_deps; + +import dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps; +import javax.annotation.processing.Generated; + +/** + * This class should only be referenced by generated code! This class aggregates information across multiple compilations. + */ +@AggregatedDeps( + components = "dagger.hilt.components.SingletonComponent", + modules = "org.michaelbel.movies.work.DownloadImageWorker_HiltModule" +) +@Generated("dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsGenerator") +public class _org_michaelbel_movies_work_DownloadImageWorker_HiltModule { +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_MoviesDatabaseWorker_HiltModule.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_MoviesDatabaseWorker_HiltModule.java new file mode 100644 index 000000000..8bddba63a --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/hilt_aggregated_deps/_org_michaelbel_movies_work_MoviesDatabaseWorker_HiltModule.java @@ -0,0 +1,15 @@ +package hilt_aggregated_deps; + +import dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps; +import javax.annotation.processing.Generated; + +/** + * This class should only be referenced by generated code! This class aggregates information across multiple compilations. + */ +@AggregatedDeps( + components = "dagger.hilt.components.SingletonComponent", + modules = "org.michaelbel.movies.work.MoviesDatabaseWorker_HiltModule" +) +@Generated("dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsGenerator") +public class _org_michaelbel_movies_work_MoviesDatabaseWorker_HiltModule { +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_AssistedFactory.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_AssistedFactory.java new file mode 100644 index 000000000..5c325f223 --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_AssistedFactory.java @@ -0,0 +1,10 @@ +package org.michaelbel.movies.work; + +import androidx.hilt.work.WorkerAssistedFactory; +import dagger.assisted.AssistedFactory; +import javax.annotation.processing.Generated; + +@Generated("androidx.hilt.AndroidXHiltProcessor") +@AssistedFactory +public interface AccountUpdateWorker_AssistedFactory extends WorkerAssistedFactory { +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_AssistedFactory_Impl.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_AssistedFactory_Impl.java new file mode 100644 index 000000000..dad85c086 --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_AssistedFactory_Impl.java @@ -0,0 +1,43 @@ +package org.michaelbel.movies.work; + +import android.content.Context; +import androidx.work.WorkerParameters; +import dagger.internal.DaggerGenerated; +import dagger.internal.InstanceFactory; +import javax.annotation.processing.Generated; +import javax.inject.Provider; + +@DaggerGenerated +@Generated( + value = "dagger.internal.codegen.ComponentProcessor", + comments = "https://dagger.dev" +) +@SuppressWarnings({ + "unchecked", + "rawtypes", + "KotlinInternal", + "KotlinInternalInJava", + "cast" +}) +public final class AccountUpdateWorker_AssistedFactory_Impl implements AccountUpdateWorker_AssistedFactory { + private final AccountUpdateWorker_Factory delegateFactory; + + AccountUpdateWorker_AssistedFactory_Impl(AccountUpdateWorker_Factory delegateFactory) { + this.delegateFactory = delegateFactory; + } + + @Override + public AccountUpdateWorker create(Context p0, WorkerParameters p1) { + return delegateFactory.get(p0, p1); + } + + public static Provider create( + AccountUpdateWorker_Factory delegateFactory) { + return InstanceFactory.create(new AccountUpdateWorker_AssistedFactory_Impl(delegateFactory)); + } + + public static dagger.internal.Provider createFactoryProvider( + AccountUpdateWorker_Factory delegateFactory) { + return InstanceFactory.create(new AccountUpdateWorker_AssistedFactory_Impl(delegateFactory)); + } +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_HiltModule.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_HiltModule.java new file mode 100644 index 000000000..51864ef88 --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/AccountUpdateWorker_HiltModule.java @@ -0,0 +1,26 @@ +package org.michaelbel.movies.work; + +import androidx.hilt.work.WorkerAssistedFactory; +import androidx.work.ListenableWorker; +import dagger.Binds; +import dagger.Module; +import dagger.hilt.InstallIn; +import dagger.hilt.codegen.OriginatingElement; +import dagger.hilt.components.SingletonComponent; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; +import javax.annotation.processing.Generated; + +@Generated("androidx.hilt.AndroidXHiltProcessor") +@Module +@InstallIn(SingletonComponent.class) +@OriginatingElement( + topLevelClass = AccountUpdateWorker.class +) +public interface AccountUpdateWorker_HiltModule { + @Binds + @IntoMap + @StringKey("org.michaelbel.movies.work.AccountUpdateWorker") + WorkerAssistedFactory bind( + AccountUpdateWorker_AssistedFactory factory); +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_AssistedFactory.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_AssistedFactory.java new file mode 100644 index 000000000..e7bf4534d --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_AssistedFactory.java @@ -0,0 +1,10 @@ +package org.michaelbel.movies.work; + +import androidx.hilt.work.WorkerAssistedFactory; +import dagger.assisted.AssistedFactory; +import javax.annotation.processing.Generated; + +@Generated("androidx.hilt.AndroidXHiltProcessor") +@AssistedFactory +public interface DownloadImageWorker_AssistedFactory extends WorkerAssistedFactory { +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_AssistedFactory_Impl.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_AssistedFactory_Impl.java new file mode 100644 index 000000000..feb3615fb --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_AssistedFactory_Impl.java @@ -0,0 +1,43 @@ +package org.michaelbel.movies.work; + +import android.content.Context; +import androidx.work.WorkerParameters; +import dagger.internal.DaggerGenerated; +import dagger.internal.InstanceFactory; +import javax.annotation.processing.Generated; +import javax.inject.Provider; + +@DaggerGenerated +@Generated( + value = "dagger.internal.codegen.ComponentProcessor", + comments = "https://dagger.dev" +) +@SuppressWarnings({ + "unchecked", + "rawtypes", + "KotlinInternal", + "KotlinInternalInJava", + "cast" +}) +public final class DownloadImageWorker_AssistedFactory_Impl implements DownloadImageWorker_AssistedFactory { + private final DownloadImageWorker_Factory delegateFactory; + + DownloadImageWorker_AssistedFactory_Impl(DownloadImageWorker_Factory delegateFactory) { + this.delegateFactory = delegateFactory; + } + + @Override + public DownloadImageWorker create(Context p0, WorkerParameters p1) { + return delegateFactory.get(p0, p1); + } + + public static Provider create( + DownloadImageWorker_Factory delegateFactory) { + return InstanceFactory.create(new DownloadImageWorker_AssistedFactory_Impl(delegateFactory)); + } + + public static dagger.internal.Provider createFactoryProvider( + DownloadImageWorker_Factory delegateFactory) { + return InstanceFactory.create(new DownloadImageWorker_AssistedFactory_Impl(delegateFactory)); + } +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_HiltModule.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_HiltModule.java new file mode 100644 index 000000000..d1dfd215b --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/DownloadImageWorker_HiltModule.java @@ -0,0 +1,26 @@ +package org.michaelbel.movies.work; + +import androidx.hilt.work.WorkerAssistedFactory; +import androidx.work.ListenableWorker; +import dagger.Binds; +import dagger.Module; +import dagger.hilt.InstallIn; +import dagger.hilt.codegen.OriginatingElement; +import dagger.hilt.components.SingletonComponent; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; +import javax.annotation.processing.Generated; + +@Generated("androidx.hilt.AndroidXHiltProcessor") +@Module +@InstallIn(SingletonComponent.class) +@OriginatingElement( + topLevelClass = DownloadImageWorker.class +) +public interface DownloadImageWorker_HiltModule { + @Binds + @IntoMap + @StringKey("org.michaelbel.movies.work.DownloadImageWorker") + WorkerAssistedFactory bind( + DownloadImageWorker_AssistedFactory factory); +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_AssistedFactory.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_AssistedFactory.java new file mode 100644 index 000000000..a865338cf --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_AssistedFactory.java @@ -0,0 +1,10 @@ +package org.michaelbel.movies.work; + +import androidx.hilt.work.WorkerAssistedFactory; +import dagger.assisted.AssistedFactory; +import javax.annotation.processing.Generated; + +@Generated("androidx.hilt.AndroidXHiltProcessor") +@AssistedFactory +public interface MoviesDatabaseWorker_AssistedFactory extends WorkerAssistedFactory { +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_AssistedFactory_Impl.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_AssistedFactory_Impl.java new file mode 100644 index 000000000..29aa6d999 --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_AssistedFactory_Impl.java @@ -0,0 +1,43 @@ +package org.michaelbel.movies.work; + +import android.content.Context; +import androidx.work.WorkerParameters; +import dagger.internal.DaggerGenerated; +import dagger.internal.InstanceFactory; +import javax.annotation.processing.Generated; +import javax.inject.Provider; + +@DaggerGenerated +@Generated( + value = "dagger.internal.codegen.ComponentProcessor", + comments = "https://dagger.dev" +) +@SuppressWarnings({ + "unchecked", + "rawtypes", + "KotlinInternal", + "KotlinInternalInJava", + "cast" +}) +public final class MoviesDatabaseWorker_AssistedFactory_Impl implements MoviesDatabaseWorker_AssistedFactory { + private final MoviesDatabaseWorker_Factory delegateFactory; + + MoviesDatabaseWorker_AssistedFactory_Impl(MoviesDatabaseWorker_Factory delegateFactory) { + this.delegateFactory = delegateFactory; + } + + @Override + public MoviesDatabaseWorker create(Context p0, WorkerParameters p1) { + return delegateFactory.get(p0, p1); + } + + public static Provider create( + MoviesDatabaseWorker_Factory delegateFactory) { + return InstanceFactory.create(new MoviesDatabaseWorker_AssistedFactory_Impl(delegateFactory)); + } + + public static dagger.internal.Provider createFactoryProvider( + MoviesDatabaseWorker_Factory delegateFactory) { + return InstanceFactory.create(new MoviesDatabaseWorker_AssistedFactory_Impl(delegateFactory)); + } +} diff --git a/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_HiltModule.java b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_HiltModule.java new file mode 100644 index 000000000..7e0b4f61b --- /dev/null +++ b/core/work-kmp/build/generated/ksp/android/androidDebug/java/org/michaelbel/movies/work/MoviesDatabaseWorker_HiltModule.java @@ -0,0 +1,26 @@ +package org.michaelbel.movies.work; + +import androidx.hilt.work.WorkerAssistedFactory; +import androidx.work.ListenableWorker; +import dagger.Binds; +import dagger.Module; +import dagger.hilt.InstallIn; +import dagger.hilt.codegen.OriginatingElement; +import dagger.hilt.components.SingletonComponent; +import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; +import javax.annotation.processing.Generated; + +@Generated("androidx.hilt.AndroidXHiltProcessor") +@Module +@InstallIn(SingletonComponent.class) +@OriginatingElement( + topLevelClass = MoviesDatabaseWorker.class +) +public interface MoviesDatabaseWorker_HiltModule { + @Binds + @IntoMap + @StringKey("org.michaelbel.movies.work.MoviesDatabaseWorker") + WorkerAssistedFactory bind( + MoviesDatabaseWorker_AssistedFactory factory); +} diff --git a/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt new file mode 100644 index 000000000..9e32ea18a --- /dev/null +++ b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt @@ -0,0 +1,38 @@ +package org.michaelbel.movies.work + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import org.michaelbel.movies.common.ktx.isTimePasses +import org.michaelbel.movies.interactor.Interactor +import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty +import java.util.concurrent.TimeUnit + +class AccountUpdateWorker( + context: Context, + workerParams: WorkerParameters, + private val interactor: Interactor +): CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + return try { + val accountId = interactor.accountId() + if (isTmdbApiKeyEmpty || accountId == 0) { + return Result.success() + } + + val expireTime = interactor.accountExpireTime() ?: 0L + val currentTime = System.currentTimeMillis() + if (isTimePasses(ONE_DAY_MILLS, expireTime, currentTime)) { + interactor.accountDetails() + } + Result.success() + } catch (ignored: Exception) { + Result.failure() + } + } + + private companion object { + private val ONE_DAY_MILLS = TimeUnit.DAYS.toMillis(1) + } +} \ No newline at end of file diff --git a/core/work/src/main/kotlin/org/michaelbel/movies/work/DownloadImageWorker.kt b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/DownloadImageWorker.kt similarity index 91% rename from core/work/src/main/kotlin/org/michaelbel/movies/work/DownloadImageWorker.kt rename to core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/DownloadImageWorker.kt index fc2e17ef1..62ddf0021 100644 --- a/core/work/src/main/kotlin/org/michaelbel/movies/work/DownloadImageWorker.kt +++ b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/DownloadImageWorker.kt @@ -7,22 +7,18 @@ import android.os.Build import android.os.Environment import android.provider.MediaStore import androidx.core.net.toUri -import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import androidx.work.workDataOf -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject +import org.michaelbel.movies.common.ktx.currentDateTime +import org.michaelbel.movies.notifications.NotificationClient import java.io.File import java.io.FileOutputStream import java.net.URL -import org.michaelbel.movies.common.ktx.currentDateTime -import org.michaelbel.movies.notifications.NotificationClient -@HiltWorker -class DownloadImageWorker @AssistedInject constructor( - @Assisted private val context: Context, - @Assisted private val workerParams: WorkerParameters, +class DownloadImageWorker( + private val context: Context, + workerParams: WorkerParameters, private val notificationClient: NotificationClient ): CoroutineWorker(context, workerParams) { @@ -42,7 +38,7 @@ class DownloadImageWorker @AssistedInject constructor( contentTextRes = contentTextRes ) - val uri: Uri? = saveImageToDownloads( + val uri = saveImageToDownloads( url = imageUrl, name = "$currentDateTime.jpg" ) diff --git a/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt new file mode 100644 index 000000000..9795a76f8 --- /dev/null +++ b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt @@ -0,0 +1,52 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package org.michaelbel.movies.work + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import kotlinx.coroutines.withContext +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.persistence.database.MoviePersistence +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.ktx.moviePojo + +class MoviesDatabaseWorker( + context: Context, + workerParams: WorkerParameters, + private val dispatchers: MoviesDispatchers, + private val moviePersistence: MoviePersistence +): CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + return withContext(dispatchers.io) { + try { + val filename = inputData.getString(KEY_FILENAME) + if (filename != null && moviePersistence.isEmpty(MoviePojo.MOVIES_LOCAL_LIST)) { + applicationContext.assets.open(filename).use { inputStream -> + val format = Json { ignoreUnknownKeys = true } + val moviesJsonData: List = format.decodeFromStream(inputStream) + val moviesDb = moviesJsonData.mapIndexed { index, movieResponse -> + movieResponse.moviePojo( + movieList = MoviePojo.MOVIES_LOCAL_LIST, + position = index.plus(1) + ) + } + moviePersistence.insertMovies(moviesDb) + } + } + Result.success() + } catch (ignored: Exception) { + Result.failure() + } + } + } + + companion object { + const val KEY_FILENAME = "MOVIES_DATA_FILENAME" + } +} \ No newline at end of file diff --git a/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/di/WorkKoinModule.kt b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/di/WorkKoinModule.kt new file mode 100644 index 000000000..3a44d2660 --- /dev/null +++ b/core/work-kmp/src/androidMain/kotlin/org/michaelbel/movies/work/di/WorkKoinModule.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.work.di + +import androidx.work.WorkManager +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.workmanager.dsl.workerOf +import org.koin.dsl.module +import org.michaelbel.movies.common.dispatchers.di.dispatchersKoinModule +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.notifications.di.notificationClientKoinModule +import org.michaelbel.movies.persistence.database.di.persistenceKoinModule +import org.michaelbel.movies.work.AccountUpdateWorker +import org.michaelbel.movies.work.DownloadImageWorker +import org.michaelbel.movies.work.MoviesDatabaseWorker + +val workKoinModule = module { + includes( + interactorKoinModule, + dispatchersKoinModule, + persistenceKoinModule, + notificationClientKoinModule + ) + single { WorkManager.getInstance(androidContext()) } + workerOf(::AccountUpdateWorker) + workerOf(::DownloadImageWorker) + workerOf(::MoviesDatabaseWorker) +} \ No newline at end of file diff --git a/core/work/build.gradle.kts b/core/work/build.gradle.kts deleted file mode 100644 index 711be577b..000000000 --- a/core/work/build.gradle.kts +++ /dev/null @@ -1,55 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.work" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" - ) - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:interactor")) - implementation(project(":core:common")) - implementation(project(":core:network")) - implementation(project(":core:notifications")) - implementation(project(":core:ui")) - implementation(libs.androidx.paging.compose) - implementation(libs.androidx.hilt.work) - implementation(libs.androidx.work.runtime.ktx) - ksp(libs.androidx.hilt.compiler) -} \ No newline at end of file diff --git a/core/work/src/main/AndroidManifest.xml b/core/work/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/work/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/work/src/main/assets/movies.json b/core/work/src/main/assets/movies.json deleted file mode 100644 index 80abacaf9..000000000 --- a/core/work/src/main/assets/movies.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 505642, - "overview": "Queen Ramonda, Shuri, M’Baku, Okoye and the Dora Milaje fight to protect their nation from intervening world powers in the wake of King T’Challa’s death. As the Wakandans strive to embrace their next chapter, the heroes must band together with the help of War Dog Nakia and Everett Ross and forge a new path for the kingdom of Wakanda.", - "poster_path": "/sv1xJUazXeYqALzczSZ3O6nkH75.jpg", - "backdrop_path": "/xDMIl84Qo5Tsu62c9DGWhmPI67A.jpg", - "release_date": "2022-11-09", - "title": "Black Panther: Wakanda Forever", - "vote_average": 7.4, - "genre_ids": [28, 12, 878] - }, - { - "id": 640146, - "overview": "Super-Hero partners Scott Lang and Hope van Dyne, along with with Hope's parents Janet van Dyne and Hank Pym, and Scott's daughter Cassie Lang, find themselves exploring the Quantum Realm, interacting with strange new creatures and embarking on an adventure that will push them beyond the limits of what they thought possible.", - "poster_path": "/ngl2FKBlU4fhbdsrtdom9LVLBXw.jpg", - "backdrop_path": "/8YFL5QQVPy3AgrEQxNYVSgiPEbe.jpg", - "release_date": "2023-02-10", - "title": "Ant-Man and the Wasp: Quantumania", - "vote_average": 6.5, - "genre_ids": [12, 878, 35] - }, - { - "id": 49046, - "overview": "Paul Baumer and his friends Albert and Muller, egged on by romantic dreams of heroism, voluntarily enlist in the German army. Full of excitement and patriotic fervour, the boys enthusiastically march into a war they believe in. But once on the Western Front, they discover the soul-destroying horror of World War I.", - "poster_path": "/hYqOjJ7Gh1fbqXrxlIao1g8ZehF.jpg", - "backdrop_path": "/mqsPyyeDCBAghXyjbw4TfEYwljw.jpg", - "release_date": "2022-10-07", - "title": "All Quiet on the Western Front", - "vote_average": 7.8, - "genre_ids": [28, 18, 10752] - } -] \ No newline at end of file diff --git a/core/work/src/main/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt b/core/work/src/main/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt deleted file mode 100644 index e1947550a..000000000 --- a/core/work/src/main/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.michaelbel.movies.work - -import android.content.Context -import androidx.hilt.work.HiltWorker -import androidx.work.CoroutineWorker -import androidx.work.WorkerParameters -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import java.util.concurrent.TimeUnit -import org.michaelbel.movies.common.ktx.isTimePasses -import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.network.isTmdbApiKeyEmpty - -@HiltWorker -class AccountUpdateWorker @AssistedInject constructor( - @Assisted context: Context, - @Assisted workerParams: WorkerParameters, - private val interactor: Interactor -): CoroutineWorker(context, workerParams) { - - override suspend fun doWork(): Result { - return try { - val accountId: Int? = interactor.accountId() - if (isTmdbApiKeyEmpty || accountId == null) { - return Result.success() - } - - val expireTime: Long = interactor.accountExpireTime() ?: 0L - val currentTime: Long = System.currentTimeMillis() - if (isTimePasses(ONE_DAY_MILLS, expireTime, currentTime)) { - interactor.accountDetails() - } - Result.success() - } catch (ignored: Exception) { - Result.failure() - } - } - - private companion object { - private val ONE_DAY_MILLS: Long = TimeUnit.DAYS.toMillis(1) - } -} \ No newline at end of file diff --git a/core/work/src/main/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt b/core/work/src/main/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt deleted file mode 100644 index 0ed545e9e..000000000 --- a/core/work/src/main/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.michaelbel.movies.work - -import android.content.Context -import androidx.hilt.work.HiltWorker -import androidx.work.CoroutineWorker -import androidx.work.WorkerParameters -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import kotlinx.coroutines.withContext -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream -import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.persistence.database.dao.MovieDao -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.ktx.movieDb - -@HiltWorker -class MoviesDatabaseWorker @AssistedInject constructor( - @Assisted context: Context, - @Assisted workerParams: WorkerParameters, - private val dispatchers: MoviesDispatchers, - private val movieDao: MovieDao -): CoroutineWorker(context, workerParams) { - - override suspend fun doWork(): Result { - return withContext(dispatchers.io) { - try { - val filename: String? = inputData.getString(KEY_FILENAME) - if (filename != null && movieDao.isEmpty(MovieDb.MOVIES_LOCAL_LIST)) { - applicationContext.assets.open(filename).use { inputStream -> - val format = Json { ignoreUnknownKeys = true } - val moviesJsonData: List = format.decodeFromStream(inputStream) - val moviesDb: List = moviesJsonData.mapIndexed { index, movieResponse -> - movieResponse.movieDb( - movieList = MovieDb.MOVIES_LOCAL_LIST, - position = index.plus(1) - ) - } - movieDao.insertMovies(moviesDb) - } - } - Result.success() - } catch (ignored: Exception) { - Result.failure() - } - } - } - - companion object { - const val KEY_FILENAME = "MOVIES_DATA_FILENAME" - } -} \ No newline at end of file diff --git a/core/work/src/main/kotlin/org/michaelbel/movies/work/di/WorkModule.kt b/core/work/src/main/kotlin/org/michaelbel/movies/work/di/WorkModule.kt deleted file mode 100644 index f0688e0d4..000000000 --- a/core/work/src/main/kotlin/org/michaelbel/movies/work/di/WorkModule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.michaelbel.movies.work.di - -import android.content.Context -import androidx.work.WorkManager -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -internal object WorkModule { - - @Provides - fun provideWorkManager( - @ApplicationContext context: Context - ): WorkManager = WorkManager.getInstance(context) -} \ No newline at end of file diff --git a/feature/auth-impl/.gitignore b/desktopApp/.gitignore similarity index 100% rename from feature/auth-impl/.gitignore rename to desktopApp/.gitignore diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts new file mode 100755 index 000000000..cd3287378 --- /dev/null +++ b/desktopApp/build.gradle.kts @@ -0,0 +1,59 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) +} + +kotlin { + jvm() + + sourceSets { + jvmMain.dependencies { + implementation(project(":core:common-kmp")) + implementation(project(":core:ui-kmp")) + implementation(project(":feature:account-kmp")) + implementation(project(":feature:auth-kmp")) + implementation(project(":feature:details-kmp")) + implementation(project(":feature:feed-kmp")) + implementation(project(":feature:gallery-kmp")) + implementation(project(":feature:search-kmp")) + implementation(project(":feature:settings-kmp")) + implementation(compose.desktop.currentOs) + implementation(compose.desktop.common) + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.animation) + implementation(compose.material) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(libs.precompose) + } + } +} + +compose.desktop { + application { + mainClass = "org.michaelbel.movies.MoviesDesktopKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "org.michaelbel.movies" + packageVersion = "1.0.0" + + val iconsRoot = project.file("desktop-icons") + macOS { + iconFile.set(iconsRoot.resolve("movies-macos.icns")) + } + windows { + iconFile.set(iconsRoot.resolve("movies-windows.ico")) + menuGroup = "Movies Menu" + // see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html + upgradeUuid = "3e111aef-dba0-434e-82ca-a89155e2d306" + } + linux { + iconFile.set(iconsRoot.resolve("movies-linux.png")) + } + } + } +} \ No newline at end of file diff --git a/desktopApp/desktop-icons/movies_linux.png b/desktopApp/desktop-icons/movies_linux.png new file mode 100644 index 000000000..d46146cd5 Binary files /dev/null and b/desktopApp/desktop-icons/movies_linux.png differ diff --git a/desktopApp/desktop-icons/movies_macos.icns b/desktopApp/desktop-icons/movies_macos.icns new file mode 100644 index 000000000..599ce7872 Binary files /dev/null and b/desktopApp/desktop-icons/movies_macos.icns differ diff --git a/desktopApp/desktop-icons/movies_windows.ico b/desktopApp/desktop-icons/movies_windows.ico new file mode 100644 index 000000000..0bfafc6d6 Binary files /dev/null and b/desktopApp/desktop-icons/movies_windows.ico differ diff --git a/desktopApp/src/jvmMain/kotlin/org/michaelbel/movies/MainWindowContent.kt b/desktopApp/src/jvmMain/kotlin/org/michaelbel/movies/MainWindowContent.kt new file mode 100644 index 000000000..634f9c18a --- /dev/null +++ b/desktopApp/src/jvmMain/kotlin/org/michaelbel/movies/MainWindowContent.kt @@ -0,0 +1,56 @@ +package org.michaelbel.movies + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.NavHost +import moe.tlaster.precompose.navigation.rememberNavigator +import org.michaelbel.movies.account.accountGraph +import org.michaelbel.movies.account.navigateToAccount +import org.michaelbel.movies.auth.authGraph +import org.michaelbel.movies.auth.navigateToAuth +import org.michaelbel.movies.details.detailsGraph +import org.michaelbel.movies.details.navigateToDetails +import org.michaelbel.movies.feed.feedGraph +import org.michaelbel.movies.gallery.galleryGraph +import org.michaelbel.movies.gallery.navigateToGallery +import org.michaelbel.movies.search.navigateToSearch +import org.michaelbel.movies.search.searchGraph +import org.michaelbel.movies.settings.navigateToSettings +import org.michaelbel.movies.settings.settingsGraph + +@Composable +internal fun MainWindowContent() { + val navHostController = rememberNavigator() + + NavHost( + navigator = navHostController, + initialRoute = "feed" + ) { + authGraph( + navigateBack = navHostController::popBackStack + ) + accountGraph( + navigateBack = navHostController::popBackStack + ) + feedGraph( + navigateToSearch = navHostController::navigateToSearch, + navigateToAuth = navHostController::navigateToAuth, + navigateToAccount = navHostController::navigateToAccount, + navigateToSettings = navHostController::navigateToSettings, + navigateToDetails = navHostController::navigateToDetails + ) + detailsGraph( + navigateBack = navHostController::popBackStack, + navigateToGallery = navHostController::navigateToGallery + ) + galleryGraph( + navigateBack = navHostController::popBackStack + ) + searchGraph( + navigateBack = navHostController::popBackStack, + navigateToDetails = navHostController::navigateToDetails, + ) + settingsGraph( + navigateBack = navHostController::popBackStack + ) + } +} \ No newline at end of file diff --git a/desktopApp/src/jvmMain/kotlin/org/michaelbel/movies/MoviesDesktop.kt b/desktopApp/src/jvmMain/kotlin/org/michaelbel/movies/MoviesDesktop.kt new file mode 100644 index 000000000..fcd937415 --- /dev/null +++ b/desktopApp/src/jvmMain/kotlin/org/michaelbel/movies/MoviesDesktop.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies + +import androidx.compose.ui.window.singleWindowApplication +import moe.tlaster.precompose.PreComposeApp +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.theme.MoviesTheme + +fun main() = singleWindowApplication( + title = "Movies", + icon = null +) { + PreComposeApp { + MoviesTheme( + theme = AppTheme.FollowSystem, + dynamicColors = false, + enableEdgeToEdge = { _,_ -> } + ) { + MainWindowContent() + } + } +} \ No newline at end of file diff --git a/feature/auth/.gitignore b/feature/account-impl-kmp/.gitignore similarity index 100% rename from feature/auth/.gitignore rename to feature/account-impl-kmp/.gitignore diff --git a/feature/account-impl-kmp/build.gradle.kts b/feature/account-impl-kmp/build.gradle.kts new file mode 100644 index 000000000..f8a7b5344 --- /dev/null +++ b/feature/account-impl-kmp/build.gradle.kts @@ -0,0 +1,73 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + api(project(":core:navigation-kmp")) + api(project(":core:ui-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:network-kmp")) + implementation(compose.components.resources) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.runtime) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.androidx.work.runtime.ktx) + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + } +} + +android { + namespace = "org.michaelbel.movies.account_impl_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/account-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/di/AccountKoinModule.kt b/feature/account-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/di/AccountKoinModule.kt new file mode 100644 index 000000000..75b6ef2f6 --- /dev/null +++ b/feature/account-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/di/AccountKoinModule.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.account.di + +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.dsl.module +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.account.AccountViewModel + +val accountKoinModule = module { + includes( + interactorKoinModule + ) + viewModelOf(::AccountViewModel) +} \ No newline at end of file diff --git a/feature/account-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/ui/AccountRoute.kt b/feature/account-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/ui/AccountRoute.kt new file mode 100644 index 000000000..f35a89a84 --- /dev/null +++ b/feature/account-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/ui/AccountRoute.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.account.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.account.AccountViewModel +import org.michaelbel.movies.persistence.database.ktx.orEmpty + +@Composable +fun AccountRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, + viewModel: AccountViewModel = koinViewModel() +) { + val account by viewModel.account.collectAsStateWithLifecycle() + + AccountScreenContent( + account = account.orEmpty, + loading = viewModel.loading, + onBackClick = onBackClick, + onLogoutClick = { viewModel.onLogoutClick(onBackClick) }, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/AccountViewModel.kt b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt similarity index 82% rename from feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/AccountViewModel.kt rename to feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt index dd89b0c51..b43414d04 100644 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/AccountViewModel.kt +++ b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt @@ -3,8 +3,6 @@ package org.michaelbel.movies.account import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn @@ -12,20 +10,19 @@ import kotlinx.coroutines.launch import org.michaelbel.movies.common.exceptions.DeleteSessionException import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.persistence.database.entity.AccountDb +import org.michaelbel.movies.persistence.database.entity.AccountPojo -@HiltViewModel -class AccountViewModel @Inject constructor( +class AccountViewModel( private val interactor: Interactor ): BaseViewModel() { var loading by mutableStateOf(false) - val account: StateFlow = interactor.account + val account: StateFlow = interactor.account .stateIn( scope = this, started = SharingStarted.Lazily, - initialValue = AccountDb.Empty + initialValue = AccountPojo.Empty ) override fun handleError(throwable: Throwable) { diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt similarity index 84% rename from feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt rename to feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt index 46aead4c1..ed3e53aa7 100644 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt +++ b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalResourceApi::class) + package org.michaelbel.movies.account.ui import androidx.compose.foundation.background @@ -12,18 +14,17 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun AccountCountryBox( +internal fun AccountCountryBox( country: String, modifier: Modifier = Modifier ) { @@ -34,7 +35,7 @@ fun AccountCountryBox( ) { Icon( imageVector = MoviesIcons.LocationOn, - contentDescription = stringResource(MoviesContentDescription.UserLocationIcon), + contentDescription = stringResource(MoviesContentDescriptionCommon.UserLocationIcon), modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) @@ -43,15 +44,13 @@ fun AccountCountryBox( text = country, modifier = Modifier.padding(start = 4.dp), overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) } } @Composable -@DevicePreviews +/*@DevicePreviews*/ private fun AccountCountryBoxPreview() { MoviesTheme { AccountCountryBox( @@ -64,7 +63,7 @@ private fun AccountCountryBoxPreview() { } @Composable -@Preview +/*@Preview*/ private fun AccountCountryBoxAmoledPreview() { MoviesTheme( theme = AppTheme.Amoled diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt similarity index 83% rename from feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt rename to feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt index 763736434..6e084a73a 100644 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt +++ b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalResourceApi::class) + package org.michaelbel.movies.account.ui import androidx.compose.foundation.background @@ -14,52 +16,29 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ChainStyle import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import org.michaelbel.movies.account.AccountViewModel -import org.michaelbel.movies.account_impl.R +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.persistence.database.entity.AccountDb -import org.michaelbel.movies.persistence.database.ktx.orEmpty -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon import org.michaelbel.movies.ui.compose.AccountAvatar import org.michaelbel.movies.ui.icons.MoviesIcons import org.michaelbel.movies.ui.ktx.isPortrait import org.michaelbel.movies.ui.ktx.lettersTextFontSizeLarge -import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.strings.MoviesStrings import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun AccountRoute( - onBackClick: () -> Unit, - modifier: Modifier = Modifier, - viewModel: AccountViewModel = hiltViewModel() -) { - val account by viewModel.account.collectAsStateWithLifecycle() - - AccountScreenContent( - account = account.orEmpty, - loading = viewModel.loading, - onBackClick = onBackClick, - onLogoutClick = { viewModel.onLogoutClick(onBackClick) }, - modifier = modifier - ) -} - -@Composable -private fun AccountScreenContent( - account: AccountDb, +internal fun AccountScreenContent( + account: AccountPojo, loading: Boolean, onBackClick: () -> Unit, onLogoutClick: () -> Unit, @@ -111,7 +90,7 @@ private fun AccountScreenContent( if (account.adult) { Icon( painter = painterResource(MoviesIcons.AdultOutline), - contentDescription = stringResource(MoviesContentDescription.AdultIcon), + contentDescription = stringResource(MoviesContentDescriptionCommon.AdultIcon), modifier = Modifier .constrainAs(adultIcon) { width = Dimension.value(24.dp) @@ -143,9 +122,7 @@ private fun AccountScreenContent( Text( text = account.name, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) } @@ -153,9 +130,7 @@ private fun AccountScreenContent( Text( text = account.username, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.secondary - ) + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.secondary) ) } } @@ -195,7 +170,7 @@ private fun AccountScreenContent( ) } else { Text( - text = stringResource(R.string.account_logout), + text = stringResource(MoviesStrings.account_logout), ) } } @@ -203,11 +178,11 @@ private fun AccountScreenContent( } @Composable -@DevicePreviews +/*@DevicePreviews*/ private fun AccountScreenContentPreview() { MoviesTheme { AccountScreenContent( - account = AccountDb( + account = AccountPojo( accountId = 0, avatarUrl = "", language = "", @@ -224,13 +199,13 @@ private fun AccountScreenContentPreview() { } @Composable -@Preview +/*@Preview*/ private fun AccountScreenContentAmoledPreview() { MoviesTheme( theme = AppTheme.Amoled ) { AccountScreenContent( - account = AccountDb( + account = AccountPojo( accountId = 0, avatarUrl = "", language = "", diff --git a/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt new file mode 100644 index 000000000..d49df2ec5 --- /dev/null +++ b/feature/account-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt @@ -0,0 +1,67 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) + +package org.michaelbel.movies.account.ui + +import androidx.compose.foundation.background +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.compose.iconbutton.CloseIcon +import org.michaelbel.movies.ui.strings.MoviesStrings +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun AccountToolbar( + modifier: Modifier = Modifier, + onNavigationIconClick: () -> Unit +) { + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(MoviesStrings.account_title), + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + }, + modifier = modifier, + navigationIcon = { + CloseIcon( + onClick = onNavigationIconClick + ) + }, + colors = TopAppBarDefaults.topAppBarColors(Color.Transparent) + ) +} + +@Composable +/*@DevicePreviews*/ +private fun AccountToolbarPreview() { + MoviesTheme { + AccountToolbar( + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer), + onNavigationIconClick = {} + ) + } +} + +@Composable +/*@Preview*/ +private fun AccountToolbarAmoledPreview() { + MoviesTheme( + theme = AppTheme.Amoled + ) { + AccountToolbar( + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer), + onNavigationIconClick = {} + ) + } +} \ No newline at end of file diff --git a/feature/account-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/account/ui/AccountRoute.kt b/feature/account-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/account/ui/AccountRoute.kt new file mode 100644 index 000000000..0a29858fd --- /dev/null +++ b/feature/account-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/account/ui/AccountRoute.kt @@ -0,0 +1,19 @@ +package org.michaelbel.movies.account.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import org.michaelbel.movies.persistence.database.entity.AccountPojo + +@Composable +fun AccountRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + AccountScreenContent( + account = AccountPojo.Empty, + loading = false, + onBackClick = onBackClick, + onLogoutClick = {}, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/account-impl/build.gradle.kts b/feature/account-impl/build.gradle.kts deleted file mode 100644 index afdd17cc6..000000000 --- a/feature/account-impl/build.gradle.kts +++ /dev/null @@ -1,70 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.account_impl" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" - ) - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(project(":core:navigation")) - api(project(":core:ui")) - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:network")) - implementation(libs.androidx.work.runtime.ktx) - implementation(libs.androidx.hilt.work) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/account-impl/src/main/AndroidManifest.xml b/feature/account-impl/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/account-impl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt deleted file mode 100644 index 9782f801c..000000000 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.michaelbel.movies.account.ui - -import androidx.compose.foundation.background -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import org.michaelbel.movies.account_impl.R -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.ui.compose.iconbutton.CloseIcon -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun AccountToolbar( - modifier: Modifier = Modifier, - onNavigationIconClick: () -> Unit -) { - CenterAlignedTopAppBar( - title = { - Text( - text = stringResource(R.string.account_title), - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - }, - modifier = modifier, - navigationIcon = { - CloseIcon( - onClick = onNavigationIconClick - ) - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent - ) - ) -} - -@Composable -@DevicePreviews -private fun AccountToolbarPreview() { - MoviesTheme { - AccountToolbar( - modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer), - onNavigationIconClick = {} - ) - } -} - -@Composable -@Preview -private fun AccountToolbarAmoledPreview() { - MoviesTheme( - theme = AppTheme.Amoled - ) { - AccountToolbar( - modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer), - onNavigationIconClick = {} - ) - } -} \ No newline at end of file diff --git a/feature/account-impl/src/main/res/values-ru/strings.xml b/feature/account-impl/src/main/res/values-ru/strings.xml deleted file mode 100644 index f98229b66..000000000 --- a/feature/account-impl/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - Аккаунт - Выйти - \ No newline at end of file diff --git a/feature/account-impl/src/main/res/values/strings.xml b/feature/account-impl/src/main/res/values/strings.xml deleted file mode 100644 index 189da38ec..000000000 --- a/feature/account-impl/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - Account - Log out - \ No newline at end of file diff --git a/feature/details-impl/.gitignore b/feature/account-kmp/.gitignore similarity index 100% rename from feature/details-impl/.gitignore rename to feature/account-kmp/.gitignore diff --git a/feature/account-kmp/build.gradle.kts b/feature/account-kmp/build.gradle.kts new file mode 100644 index 000000000..cf30eac9a --- /dev/null +++ b/feature/account-kmp/build.gradle.kts @@ -0,0 +1,53 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:navigation-kmp")) + api(project(":feature:account-impl-kmp")) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.material3) + implementation(libs.precompose) + } + } +} + +android { + namespace = "org.michaelbel.movies.account_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/account-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt b/feature/account-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt new file mode 100644 index 000000000..5c41543c1 --- /dev/null +++ b/feature/account-kmp/src/androidMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.account + +import androidx.compose.ui.window.DialogProperties +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.dialog +import org.michaelbel.movies.account.ui.AccountRoute + +fun NavController.navigateToAccount() { + navigate(AccountDestination.route) +} + +fun NavGraphBuilder.accountGraph( + navigateBack: () -> Unit +) { + dialog( + route = AccountDestination.route, + dialogProperties = DialogProperties( + usePlatformDefaultWidth = false + ) + ) { + AccountRoute( + onBackClick = navigateBack + ) + } +} \ No newline at end of file diff --git a/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountDestination.kt b/feature/account-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/AccountDestination.kt similarity index 85% rename from feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountDestination.kt rename to feature/account-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/AccountDestination.kt index 55ae76c3f..1d088d113 100644 --- a/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountDestination.kt +++ b/feature/account-kmp/src/commonMain/kotlin/org/michaelbel/movies/account/AccountDestination.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.auth +package org.michaelbel.movies.account import org.michaelbel.movies.navigation.MoviesNavigationDestination diff --git a/feature/account-kmp/src/desktopMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt b/feature/account-kmp/src/desktopMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt new file mode 100644 index 000000000..b54884d15 --- /dev/null +++ b/feature/account-kmp/src/desktopMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.account + +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.RouteBuilder +import org.michaelbel.movies.account.ui.AccountRoute + +fun Navigator.navigateToAccount() { + navigate(AccountDestination.route) +} + +fun RouteBuilder.accountGraph( + navigateBack: () -> Unit +) { + dialog( + route = AccountDestination.route + ) { + AccountRoute( + onBackClick = navigateBack + ) + } +} \ No newline at end of file diff --git a/feature/account/build.gradle.kts b/feature/account/build.gradle.kts deleted file mode 100644 index 87497dd6a..000000000 --- a/feature/account/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.account" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":feature:account-impl")) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/account/src/main/AndroidManifest.xml b/feature/account/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/account/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountNavigation.kt b/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountNavigation.kt deleted file mode 100644 index 0e7678c3c..000000000 --- a/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountNavigation.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.michaelbel.movies.auth - -import androidx.compose.ui.window.DialogProperties -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.dialog -import org.michaelbel.movies.account.ui.AccountRoute - -fun NavController.navigateToAccount() { - navigate(AccountDestination.route) -} - -fun NavGraphBuilder.accountGraph( - navigateBack: () -> Unit -) { - dialog( - route = AccountDestination.route, - dialogProperties = DialogProperties( - usePlatformDefaultWidth = false - ) - ) { - AccountRoute( - onBackClick = navigateBack - ) - } -} \ No newline at end of file diff --git a/feature/details/.gitignore b/feature/auth-impl-kmp/.gitignore similarity index 100% rename from feature/details/.gitignore rename to feature/auth-impl-kmp/.gitignore diff --git a/feature/auth-impl-kmp/build.gradle.kts b/feature/auth-impl-kmp/build.gradle.kts new file mode 100644 index 000000000..9f74ae93b --- /dev/null +++ b/feature/auth-impl-kmp/build.gradle.kts @@ -0,0 +1,71 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + api(project(":core:navigation-kmp")) + api(project(":core:ui-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:network-kmp")) + implementation(compose.components.resources) + implementation(compose.material3) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.androidx.autofill) + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + } +} + +android { + namespace = "org.michaelbel.movies.auth_impl_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt b/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt similarity index 85% rename from feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt rename to feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt index 288bd4795..a0cbfcbfc 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt +++ b/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt @@ -3,8 +3,6 @@ package org.michaelbel.movies.auth import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.launch import org.michaelbel.movies.common.exceptions.AccountDetailsException import org.michaelbel.movies.common.exceptions.CreateRequestTokenException @@ -12,14 +10,15 @@ import org.michaelbel.movies.common.exceptions.CreateSessionException import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.interactor.Interactor +import org.michaelbel.movies.interactor.entity.Password +import org.michaelbel.movies.interactor.entity.Username -@HiltViewModel -class AuthViewModel @Inject constructor( +class AuthViewModel( private val interactor: Interactor ): BaseViewModel() { - var signInLoading: Boolean by mutableStateOf(false) - var loginLoading: Boolean by mutableStateOf(false) + var signInLoading by mutableStateOf(false) + var loginLoading by mutableStateOf(false) var error: Throwable? by mutableStateOf(null) var requestToken: String? by mutableStateOf(null) @@ -40,7 +39,7 @@ class AuthViewModel @Inject constructor( } } - fun onSignInClick(username: String, password: String, onResult: () -> Unit) = launch { + fun onSignInClick(username: Username, password: Password, onResult: () -> Unit) = launch { error = null signInLoading = true val token = interactor.createRequestToken(loginViaTmdb = false) diff --git a/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/di/AuthKoinModule.kt b/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/di/AuthKoinModule.kt new file mode 100644 index 000000000..17f8e3924 --- /dev/null +++ b/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/di/AuthKoinModule.kt @@ -0,0 +1,13 @@ +package org.michaelbel.movies.auth.di + +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.dsl.module +import org.michaelbel.movies.auth.AuthViewModel +import org.michaelbel.movies.interactor.di.interactorKoinModule + +val authKoinModule = module { + includes( + interactorKoinModule + ) + viewModelOf(::AuthViewModel) +} \ No newline at end of file diff --git a/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/ui/AuthRoute.kt b/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/ui/AuthRoute.kt new file mode 100644 index 000000000..142ce1ef8 --- /dev/null +++ b/feature/auth-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/ui/AuthRoute.kt @@ -0,0 +1,36 @@ +package org.michaelbel.movies.auth.ui + +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.auth.AuthViewModel +import org.michaelbel.movies.common.browser.openUrl + +@Composable +fun AuthRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, + viewModel: AuthViewModel = koinViewModel() +) { + val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + val toolbarColor = MaterialTheme.colorScheme.primary.toArgb() + + AuthScreenContent( + error = viewModel.error, + signInLoading = viewModel.signInLoading, + loginLoading = viewModel.loginLoading, + requestToken = viewModel.requestToken, + onBackClick = onBackClick, + onSignInClick = { username, password -> + viewModel.onSignInClick(username, password, onBackClick) + }, + onLoginClick = viewModel::onLoginClick, + onResetRequestToken = viewModel::onResetRequestToken, + onUrlClick = { url -> openUrl(resultContract, toolbarColor, url) }, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ktx/ThrowableKtx.kt b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ktx/ThrowableKtx.kt new file mode 100644 index 000000000..ad0bfc000 --- /dev/null +++ b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ktx/ThrowableKtx.kt @@ -0,0 +1,21 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.auth.ktx + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.exceptions.AccountDetailsException +import org.michaelbel.movies.common.exceptions.CreateRequestTokenException +import org.michaelbel.movies.common.exceptions.CreateSessionException +import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException +import org.michaelbel.movies.ui.strings.MoviesStrings + +val Throwable?.text: String + @Composable get() = when (this) { + is CreateRequestTokenException -> stringResource(MoviesStrings.auth_error_while_create_request_token) + is CreateSessionWithLoginException -> stringResource(MoviesStrings.auth_error_while_create_session_with_login) + is CreateSessionException -> stringResource(MoviesStrings.auth_error_while_create_session) + is AccountDetailsException -> stringResource(MoviesStrings.auth_error_while_loading_account_details) + else -> "" + } \ No newline at end of file diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt similarity index 78% rename from feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt rename to feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt index efda45012..6d4e49327 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt +++ b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalResourceApi::class) + package org.michaelbel.movies.auth.ui import androidx.compose.foundation.background @@ -8,20 +10,19 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Divider +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import org.michaelbel.movies.auth_impl.R +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.ui.ktx.clickableWithoutRipple -import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.strings.MoviesStrings import org.michaelbel.movies.ui.theme.MoviesTheme @Composable @@ -35,10 +36,9 @@ fun AuthLinksBox( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally ) { - Divider( - modifier = Modifier, - color = MaterialTheme.colorScheme.onSecondaryContainer, - thickness = .5.dp + HorizontalDivider( + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer ) Row( @@ -46,13 +46,11 @@ fun AuthLinksBox( verticalAlignment = Alignment.CenterVertically ) { Text( - text = stringResource(R.string.auth_terms_of_use), + text = stringResource(MoviesStrings.auth_terms_of_use), modifier = Modifier .padding(vertical = 16.dp) .clickableWithoutRipple { onTermsOfUseClick() }, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) Box( @@ -64,20 +62,18 @@ fun AuthLinksBox( ) Text( - text = stringResource(R.string.auth_privacy_policy), + text = stringResource(MoviesStrings.auth_privacy_policy), modifier = Modifier .padding(vertical = 16.dp) .clickableWithoutRipple { onPrivacyPolicyClick() }, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) } } } @Composable -@DevicePreviews +/*@DevicePreviews*/ private fun AuthLinksBoxPreview() { MoviesTheme { AuthLinksBox( @@ -89,7 +85,7 @@ private fun AuthLinksBoxPreview() { } @Composable -@Preview +/*@Preview*/ private fun AuthLinksBoxAmoledPreview() { MoviesTheme( theme = AppTheme.Amoled diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt similarity index 76% rename from feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt rename to feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt index af7fdd3e2..772ece496 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt +++ b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt @@ -1,7 +1,7 @@ +@file:OptIn(ExperimentalResourceApi::class) + package org.michaelbel.movies.auth.ui -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -29,61 +29,41 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension -import androidx.hilt.navigation.compose.hiltViewModel -import org.michaelbel.movies.auth.AuthViewModel +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource import org.michaelbel.movies.auth.ktx.text -import org.michaelbel.movies.auth_impl.R -import org.michaelbel.movies.common.browser.openUrl import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.network.TMDB_AUTH_REDIRECT_URL -import org.michaelbel.movies.network.TMDB_AUTH_URL -import org.michaelbel.movies.network.TMDB_PRIVACY_POLICY -import org.michaelbel.movies.network.TMDB_REGISTER -import org.michaelbel.movies.network.TMDB_RESET_PASSWORD -import org.michaelbel.movies.network.TMDB_TERMS_OF_USE -import org.michaelbel.movies.network.TMDB_URL -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.interactor.entity.Password +import org.michaelbel.movies.interactor.entity.Username +import org.michaelbel.movies.interactor.ktx.PasswordSaver +import org.michaelbel.movies.interactor.ktx.UsernameSaver +import org.michaelbel.movies.interactor.ktx.isNotEmpty +import org.michaelbel.movies.interactor.ktx.trim +import org.michaelbel.movies.network.config.TMDB_AUTH_REDIRECT_URL +import org.michaelbel.movies.network.config.TMDB_AUTH_URL +import org.michaelbel.movies.network.config.TMDB_PRIVACY_POLICY +import org.michaelbel.movies.network.config.TMDB_REGISTER +import org.michaelbel.movies.network.config.TMDB_RESET_PASSWORD +import org.michaelbel.movies.network.config.TMDB_TERMS_OF_USE +import org.michaelbel.movies.network.config.TMDB_URL +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon import org.michaelbel.movies.ui.compose.iconbutton.PasswordIcon import org.michaelbel.movies.ui.icons.MoviesIcons import org.michaelbel.movies.ui.ktx.clickableWithoutRipple import org.michaelbel.movies.ui.ktx.isPortrait -import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.strings.MoviesStrings import org.michaelbel.movies.ui.theme.MoviesTheme -@Composable -fun AuthRoute( - onBackClick: () -> Unit, - modifier: Modifier = Modifier, - viewModel: AuthViewModel = hiltViewModel() -) { - AuthScreenContent( - error = viewModel.error, - signInLoading = viewModel.signInLoading, - loginLoading = viewModel.loginLoading, - requestToken = viewModel.requestToken, - onBackClick = onBackClick, - onSignInClick = { username, password -> - viewModel.onSignInClick(username, password, onBackClick) - }, - onLoginClick = viewModel::onLoginClick, - onResetRequestToken = viewModel::onResetRequestToken, - modifier = modifier - ) -} - @Composable internal fun AuthScreenContent( error: Throwable?, @@ -91,26 +71,22 @@ internal fun AuthScreenContent( loginLoading: Boolean, requestToken: String?, onBackClick: () -> Unit, - onSignInClick: (String, String) -> Unit, + onSignInClick: (Username, Password) -> Unit, onLoginClick: () -> Unit, onResetRequestToken: () -> Unit, + onUrlClick: (String) -> Unit, modifier: Modifier = Modifier ) { - val resultContract = rememberLauncherForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) {} - val toolbarColor = MaterialTheme.colorScheme.primary.toArgb() - val focusManager = LocalFocusManager.current val scrollState = rememberScrollState() - var username by rememberSaveable { mutableStateOf("") } - var password by rememberSaveable { mutableStateOf("") } + var username by rememberSaveable(saver = UsernameSaver) { mutableStateOf(Username("")) } + var password by rememberSaveable(saver = PasswordSaver) { mutableStateOf(Password("")) } var passwordVisible by rememberSaveable { mutableStateOf(false) } if (requestToken != null) { val signUrl = String.format(TMDB_AUTH_URL, requestToken, TMDB_AUTH_REDIRECT_URL) - openUrl(resultContract, toolbarColor, signUrl) + onUrlClick(signUrl) onResetRequestToken() } @@ -149,7 +125,7 @@ internal fun AuthScreenContent( Icon( painter = painterResource(MoviesIcons.TmdbLogo), - contentDescription = MoviesContentDescription.None, + contentDescription = MoviesContentDescriptionCommon.None, modifier = Modifier .constrainAs(logo) { width = Dimension.wrapContent @@ -158,14 +134,14 @@ internal fun AuthScreenContent( top.linkTo(toolbar.bottom, 8.dp) end.linkTo(parent.end, 16.dp) } - .clickableWithoutRipple { openUrl(resultContract, toolbarColor, TMDB_URL) }, + .clickableWithoutRipple { onUrlClick(TMDB_URL) }, tint = MaterialTheme.colorScheme.onPrimaryContainer ) OutlinedTextField( - value = username, - onValueChange = { value: String -> - username = value + value = username.value, + onValueChange = { value -> + username = Username(value) }, modifier = Modifier.constrainAs(usernameField) { width = Dimension.fillToConstraints @@ -176,7 +152,7 @@ internal fun AuthScreenContent( }, label = { Text( - text = stringResource(R.string.auth_label_username) + text = stringResource(MoviesStrings.auth_label_username) ) }, isError = error != null, @@ -193,9 +169,9 @@ internal fun AuthScreenContent( ) OutlinedTextField( - value = password, - onValueChange = { value: String -> - password = value + value = password.value, + onValueChange = { value -> + password = Password(value) }, modifier = Modifier.constrainAs(passwordField) { width = Dimension.fillToConstraints @@ -206,12 +182,12 @@ internal fun AuthScreenContent( }, label = { Text( - text = stringResource(R.string.auth_label_password) + text = stringResource(MoviesStrings.auth_label_password) ) }, trailingIcon = { AnimatedVisibility( - visible = password.isNotEmpty(), + visible = password.isNotEmpty, enter = fadeIn(), exit = fadeOut() ) { @@ -256,10 +232,10 @@ internal fun AuthScreenContent( exit = fadeOut() ) { TextButton( - onClick = { openUrl(resultContract, toolbarColor, TMDB_RESET_PASSWORD) } + onClick = { onUrlClick(TMDB_RESET_PASSWORD) } ) { Text( - text = stringResource(R.string.auth_reset_password) + text = stringResource(MoviesStrings.auth_reset_password) ) } } @@ -277,16 +253,16 @@ internal fun AuthScreenContent( exit = fadeOut() ) { TextButton( - onClick = { openUrl(resultContract, toolbarColor, TMDB_REGISTER) } + onClick = { onUrlClick(TMDB_REGISTER) } ) { Text( - text = stringResource(R.string.auth_sign_up) + text = stringResource(MoviesStrings.auth_sign_up) ) } } Button( - onClick = { onSignInClick(username.trim(), password.trim()) }, + onClick = { onSignInClick(username.trim, password.trim) }, modifier = Modifier.constrainAs(signInButton) { width = Dimension.fillToConstraints height = Dimension.wrapContent @@ -294,7 +270,7 @@ internal fun AuthScreenContent( top.linkTo(if (error != null && error is CreateSessionWithLoginException) resetPasswordButton.bottom else passwordField.bottom, 16.dp) end.linkTo(parent.end, 16.dp) }, - enabled = username.isNotEmpty() && password.isNotEmpty() && !signInLoading, + enabled = username.isNotEmpty && password.isNotEmpty && !signInLoading, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.surfaceTint ), @@ -307,7 +283,7 @@ internal fun AuthScreenContent( ) } else { Text( - text = stringResource(R.string.auth_sign_in) + text = stringResource(MoviesStrings.auth_sign_in) ) } } @@ -334,14 +310,14 @@ internal fun AuthScreenContent( ) } else { Text( - text = stringResource(R.string.auth_login) + text = stringResource(MoviesStrings.auth_login) ) } } AuthLinksBox( - onTermsOfUseClick = { openUrl(resultContract, toolbarColor, TMDB_TERMS_OF_USE) }, - onPrivacyPolicyClick = { openUrl(resultContract, toolbarColor, TMDB_PRIVACY_POLICY) }, + onTermsOfUseClick = { onUrlClick(TMDB_TERMS_OF_USE) }, + onPrivacyPolicyClick = { onUrlClick(TMDB_PRIVACY_POLICY) }, modifier = Modifier.constrainAs(linksBox) { width = Dimension.fillToConstraints height = Dimension.wrapContent @@ -354,7 +330,7 @@ internal fun AuthScreenContent( } @Composable -@DevicePreviews +/*@DevicePreviews*/ private fun AuthScreenContentPreview() { MoviesTheme { AuthScreenContent( @@ -366,13 +342,14 @@ private fun AuthScreenContentPreview() { onSignInClick = { _,_ -> }, onLoginClick = {}, onResetRequestToken = {}, + onUrlClick = {}, modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) ) } } @Composable -@Preview +/*@Preview*/ private fun AuthScreenContentAmoledPreview() { MoviesTheme( theme = AppTheme.Amoled @@ -386,6 +363,7 @@ private fun AuthScreenContentAmoledPreview() { onSignInClick = { _,_ -> }, onLoginClick = {}, onResetRequestToken = {}, + onUrlClick = {}, modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) ) } diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt similarity index 75% rename from feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt rename to feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt index 79a3f28b3..cad194618 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt +++ b/feature/auth-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt @@ -1,20 +1,22 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) + package org.michaelbel.movies.auth.ui import androidx.compose.foundation.background import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import org.michaelbel.movies.auth_impl.R +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.ui.compose.iconbutton.CloseIcon -import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.strings.MoviesStrings import org.michaelbel.movies.ui.theme.MoviesTheme @Composable @@ -25,11 +27,9 @@ fun AuthToolbar( CenterAlignedTopAppBar( title = { Text( - text = stringResource(R.string.auth_title), + text = stringResource(MoviesStrings.auth_title), overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) }, modifier = modifier, @@ -38,14 +38,12 @@ fun AuthToolbar( onClick = onNavigationIconClick ) }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent - ) + colors = TopAppBarDefaults.topAppBarColors(Color.Transparent) ) } @Composable -@DevicePreviews +/*@DevicePreviews*/ private fun AuthToolbarPreview() { MoviesTheme { AuthToolbar( @@ -56,7 +54,7 @@ private fun AuthToolbarPreview() { } @Composable -@Preview +/*@Preview*/ private fun AuthToolbarAmoledPreview() { MoviesTheme( theme = AppTheme.Amoled diff --git a/feature/auth-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/auth/ui/AuthRoute.kt b/feature/auth-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/auth/ui/AuthRoute.kt new file mode 100644 index 000000000..eaad0153c --- /dev/null +++ b/feature/auth-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/auth/ui/AuthRoute.kt @@ -0,0 +1,24 @@ +package org.michaelbel.movies.auth.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import org.michaelbel.movies.common.browser.openUrl + +@Composable +fun AuthRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + AuthScreenContent( + error = null, + signInLoading = false, + loginLoading = false, + requestToken = null, + onBackClick = onBackClick, + onSignInClick = { username, password -> }, + onLoginClick = {}, + onResetRequestToken = {}, + onUrlClick = { url -> openUrl(url) }, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/auth-impl/build.gradle.kts b/feature/auth-impl/build.gradle.kts deleted file mode 100644 index fae60a257..000000000 --- a/feature/auth-impl/build.gradle.kts +++ /dev/null @@ -1,70 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.auth_impl" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" - ) - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(project(":core:navigation")) - api(project(":core:ui")) - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:network")) - - implementation(libs.androidx.autofill) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/auth-impl/src/main/AndroidManifest.xml b/feature/auth-impl/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/auth-impl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ktx/ThrowableKtx.kt b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ktx/ThrowableKtx.kt deleted file mode 100644 index 166633b72..000000000 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ktx/ThrowableKtx.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.auth.ktx - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.auth_impl.R -import org.michaelbel.movies.common.exceptions.AccountDetailsException -import org.michaelbel.movies.common.exceptions.CreateRequestTokenException -import org.michaelbel.movies.common.exceptions.CreateSessionException -import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException - -internal val Throwable?.text: String - @Composable get() = when (this) { - is CreateRequestTokenException -> stringResource(R.string.auth_error_while_create_request_token) - is CreateSessionWithLoginException -> stringResource(R.string.auth_error_while_create_session_with_login) - is CreateSessionException -> stringResource(R.string.auth_error_while_create_session) - is AccountDetailsException -> stringResource(R.string.auth_error_while_loading_account_details) - else -> "" - } \ No newline at end of file diff --git a/feature/auth-impl/src/main/res/values-ru/strings.xml b/feature/auth-impl/src/main/res/values-ru/strings.xml deleted file mode 100644 index b607a8ad6..000000000 --- a/feature/auth-impl/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Войти - Имя пользователя - Пароль - Сбросить пароль - Войти - Зарегистрироваться - Войти с Themoviedb - Terms of Use - Privacy Policy - Ошибка при получении токена авторизации - Неверный логин или пароль - Ошибка при получении идентификатора сессии - Ошибка при загрузке информации об аккаунте - \ No newline at end of file diff --git a/feature/auth-impl/src/main/res/values/strings.xml b/feature/auth-impl/src/main/res/values/strings.xml deleted file mode 100644 index 794c4587a..000000000 --- a/feature/auth-impl/src/main/res/values/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Login - Username - Password - Reset Password - Sign in - Sign up - Login with Themoviedb - Terms of Use - Privacy Policy - Error while create request token - Invalid username or password - Error while create session - Error while loading account details - \ No newline at end of file diff --git a/feature/feed-impl/.gitignore b/feature/auth-kmp/.gitignore similarity index 100% rename from feature/feed-impl/.gitignore rename to feature/auth-kmp/.gitignore diff --git a/feature/auth-kmp/build.gradle.kts b/feature/auth-kmp/build.gradle.kts new file mode 100644 index 000000000..aa61a8b71 --- /dev/null +++ b/feature/auth-kmp/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:navigation-kmp")) + api(project(":feature:auth-impl-kmp")) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.material) + implementation(compose.material3) + implementation(libs.precompose) + } + } +} + +android { + namespace = "org.michaelbel.movies.auth_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/auth/src/main/kotlin/org/michaelbel/movies/auth/AuthNavigation.kt b/feature/auth-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthNavigation.kt similarity index 100% rename from feature/auth/src/main/kotlin/org/michaelbel/movies/auth/AuthNavigation.kt rename to feature/auth-kmp/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthNavigation.kt diff --git a/feature/auth/src/main/kotlin/org/michaelbel/movies/auth/AuthDestination.kt b/feature/auth-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/AuthDestination.kt similarity index 100% rename from feature/auth/src/main/kotlin/org/michaelbel/movies/auth/AuthDestination.kt rename to feature/auth-kmp/src/commonMain/kotlin/org/michaelbel/movies/auth/AuthDestination.kt diff --git a/feature/auth-kmp/src/desktopMain/kotlin/org/michaelbel/movies/auth/AuthNavigation.kt b/feature/auth-kmp/src/desktopMain/kotlin/org/michaelbel/movies/auth/AuthNavigation.kt new file mode 100644 index 000000000..420c7fe1e --- /dev/null +++ b/feature/auth-kmp/src/desktopMain/kotlin/org/michaelbel/movies/auth/AuthNavigation.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.auth + +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.RouteBuilder +import org.michaelbel.movies.auth.ui.AuthRoute + +fun Navigator.navigateToAuth() { + navigate(AuthDestination.route) +} + +fun RouteBuilder.authGraph( + navigateBack: () -> Unit +) { + dialog( + route = AuthDestination.route, + ) { + AuthRoute( + onBackClick = navigateBack + ) + } +} \ No newline at end of file diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts deleted file mode 100644 index f4ce1a863..000000000 --- a/feature/auth/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.auth" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":feature:auth-impl")) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/auth/src/main/AndroidManifest.xml b/feature/auth/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/auth/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/feed/.gitignore b/feature/details-impl-kmp/.gitignore similarity index 100% rename from feature/feed/.gitignore rename to feature/details-impl-kmp/.gitignore diff --git a/feature/details-impl-kmp/build.gradle.kts b/feature/details-impl-kmp/build.gradle.kts new file mode 100644 index 000000000..7fb05d2cb --- /dev/null +++ b/feature/details-impl-kmp/build.gradle.kts @@ -0,0 +1,71 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + api(project(":core:navigation-kmp")) + api(project(":core:ui-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:network-kmp")) + implementation(compose.components.resources) + implementation(compose.material3) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + } +} + +android { + namespace = "org.michaelbel.movies.details_impl_kmp" + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt similarity index 85% rename from feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt rename to feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt index 6a13f4557..5e69e4ba4 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt +++ b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt @@ -2,8 +2,6 @@ package org.michaelbel.movies.details import androidx.lifecycle.SavedStateHandle import androidx.palette.graphics.Palette -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -15,12 +13,11 @@ import org.michaelbel.movies.common.ktx.require import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.network.ScreenState +import org.michaelbel.movies.network.config.ScreenState import org.michaelbel.movies.network.connectivity.NetworkManager import org.michaelbel.movies.network.connectivity.NetworkStatus -@HiltViewModel -class DetailsViewModel @Inject constructor( +class DetailsViewModel( savedStateHandle: SavedStateHandle, networkManager: NetworkManager, private val interactor: Interactor @@ -64,11 +61,14 @@ class DetailsViewModel @Inject constructor( val onContainerColor = palette.vibrantSwatch?.bodyTextColor if (containerColor != null && onContainerColor != null) { interactor.updateMovieColors(movieId, containerColor, onContainerColor) - _detailsState.value = ScreenState.Content(interactor.movie(movieList.orEmpty(), movieId)) + if (movieList != null) { + _detailsState.value = ScreenState.Content(interactor.movie(movieList, movieId)) + } } } private fun loadMovie() = launch { - _detailsState.emit(ScreenState.Content(interactor.movieDetails(movieList.orEmpty(), movieId))) + val movieDb = interactor.movieDetails(movieList.orEmpty(), movieId) + _detailsState.value = ScreenState.Content(movieDb) } } \ No newline at end of file diff --git a/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/di/DetailsKoinModule.kt b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/di/DetailsKoinModule.kt new file mode 100644 index 000000000..4f21c6e39 --- /dev/null +++ b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/di/DetailsKoinModule.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.details.di + +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.michaelbel.movies.details.DetailsViewModel +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.network.connectivity.di.networkManagerKoinModule + +val detailsKoinModule = module { + includes( + interactorKoinModule, + networkManagerKoinModule + ) + viewModel { DetailsViewModel(get(), get(), get()) } +} \ No newline at end of file diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt similarity index 91% rename from feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt rename to feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt index bdd5a936f..a0ca01241 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt +++ b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -32,8 +33,8 @@ import coil.compose.AsyncImage import coil.request.ImageRequest import coil.request.SuccessResult import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.network.formatBackdropImage -import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.network.config.formatBackdropImage +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.persistence.database.ktx.isNotEmpty import org.michaelbel.movies.ui.accessibility.MoviesContentDescription import org.michaelbel.movies.ui.ktx.isErrorOrEmpty @@ -45,8 +46,8 @@ import org.michaelbel.movies.ui.preview.provider.MovieDbPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun DetailsContent( - movie: MovieDb, +internal fun DetailsContent( + movie: MoviePojo, onNavigateToGallery: (Int) -> Unit, onGenerateColors: (Int, Palette) -> Unit, modifier: Modifier = Modifier, @@ -56,7 +57,7 @@ fun DetailsContent( ) { val context = LocalContext.current val scrollState = rememberScrollState() - var isNoImageVisible: Boolean by remember { mutableStateOf(false) } + var isNoImageVisible by remember { mutableStateOf(false) } if (!isThemeAmoled && !placeholder) { LaunchedEffect(key1 = movie.backdropPath.formatBackdropImage) { @@ -136,13 +137,10 @@ fun DetailsContent( ), overflow = TextOverflow.Ellipsis, maxLines = 3, - style = MaterialTheme.typography.titleLarge.copy( - color = onContainerColor - ) + style = MaterialTheme.typography.titleLarge.copy(onContainerColor) ) - Text( - text = movie.overview, + SelectionContainer( modifier = Modifier .constrainAs(overview) { width = Dimension.fillToConstraints @@ -156,20 +154,20 @@ fun DetailsContent( color = MaterialTheme.colorScheme.inversePrimary, shape = MaterialTheme.shapes.small, highlight = PlaceholderHighlight.fade() - ), - overflow = TextOverflow.Ellipsis, - maxLines = 10, - style = MaterialTheme.typography.bodyMedium.copy( - color = onContainerColor + ) + ) { + Text( + text = movie.overview, + style = MaterialTheme.typography.bodyMedium.copy(onContainerColor) ) - ) + } } } @Composable @DevicePreviews private fun DetailsContentPreview( - @PreviewParameter(MovieDbPreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MovieDbPreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme { DetailsContent( @@ -186,7 +184,7 @@ private fun DetailsContentPreview( @Composable @Preview private fun DetailsContentAmoledPreview( - @PreviewParameter(MovieDbPreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MovieDbPreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme( theme = AppTheme.Amoled diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt similarity index 90% rename from feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt rename to feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt index d4191b352..43092e71b 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt +++ b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt @@ -7,17 +7,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun DetailsLoading( +internal fun DetailsLoading( modifier: Modifier = Modifier ) { DetailsContent( modifier = modifier, - movie = MovieDb.Empty, + movie = MoviePojo.Empty, onNavigateToGallery = {}, onGenerateColors = { _,_ -> }, placeholder = true diff --git a/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsRoute.kt b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsRoute.kt new file mode 100644 index 000000000..f9014f876 --- /dev/null +++ b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsRoute.kt @@ -0,0 +1,55 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.details.ui + +import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.details.DetailsViewModel +import org.michaelbel.movies.ui.strings.MoviesStrings + +@Composable +fun DetailsRoute( + onBackClick: () -> Unit, + onNavigateToGallery: (Int) -> Unit, + modifier: Modifier = Modifier, + viewModel: DetailsViewModel = koinViewModel() +) { + val detailsState by viewModel.detailsState.collectAsStateWithLifecycle() + val networkStatus by viewModel.networkStatus.collectAsStateWithLifecycle() + val currentTheme by viewModel.currentTheme.collectAsStateWithLifecycle() + + val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + val shareTitle = stringResource(MoviesStrings.share_via) + + val onShareUrl: (String) -> Unit = { url -> + Intent().apply { + type = "text/plain" + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, url) + }.also { intent: Intent -> + resultContract.launch(Intent.createChooser(intent, shareTitle)) + } + } + + DetailsScreenContent( + onBackClick = onBackClick, + onShareClick = onShareUrl, + onNavigateToGallery = onNavigateToGallery, + onGenerateColors = viewModel::onGenerateColors, + detailsState = detailsState, + networkStatus = networkStatus, + isThemeAmoled = currentTheme is AppTheme.Amoled, + onRetry = viewModel::retry, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt similarity index 83% rename from feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt rename to feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt index faecb12fe..6130a1f91 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt +++ b/feature/details-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt @@ -1,8 +1,14 @@ +@file:OptIn( + ExperimentalMaterial3Api::class, + ExperimentalFoundationApi::class +) + package org.michaelbel.movies.details.ui import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -12,26 +18,21 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.palette.graphics.Palette -import java.net.UnknownHostException -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.details.DetailsViewModel import org.michaelbel.movies.details.ktx.movie import org.michaelbel.movies.details.ktx.movieUrl import org.michaelbel.movies.details.ktx.onPrimaryContainer import org.michaelbel.movies.details.ktx.primaryContainer import org.michaelbel.movies.details.ktx.scrolledContainerColor import org.michaelbel.movies.details.ktx.toolbarTitle -import org.michaelbel.movies.network.ScreenState +import org.michaelbel.movies.network.config.ScreenState import org.michaelbel.movies.network.connectivity.NetworkStatus import org.michaelbel.movies.network.connectivity.ktx.isAvailable import org.michaelbel.movies.network.ktx.isFailure @@ -39,33 +40,12 @@ import org.michaelbel.movies.network.ktx.throwable import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets import org.michaelbel.movies.ui.ktx.screenHeight import org.michaelbel.movies.ui.ktx.screenWidth +import java.net.UnknownHostException @Composable -fun DetailsRoute( - onBackClick: () -> Unit, - onNavigateToGallery: (Int) -> Unit, - modifier: Modifier = Modifier, - viewModel: DetailsViewModel = hiltViewModel() -) { - val detailsState by viewModel.detailsState.collectAsStateWithLifecycle() - val networkStatus by viewModel.networkStatus.collectAsStateWithLifecycle() - val currentTheme by viewModel.currentTheme.collectAsStateWithLifecycle() - - DetailsScreenContent( - onBackClick = onBackClick, - onNavigateToGallery = onNavigateToGallery, - onGenerateColors = viewModel::onGenerateColors, - detailsState = detailsState, - networkStatus = networkStatus, - isThemeAmoled = currentTheme is AppTheme.Amoled, - onRetry = viewModel::retry, - modifier = modifier - ) -} - -@Composable -private fun DetailsScreenContent( +internal fun DetailsScreenContent( onBackClick: () -> Unit, + onShareClick: (String) -> Unit, onNavigateToGallery: (Int) -> Unit, onGenerateColors: (Int, Palette) -> Unit, detailsState: ScreenState, @@ -117,6 +97,7 @@ private fun DetailsScreenContent( movieTitle = detailsState.toolbarTitle, movieUrl = detailsState.movieUrl, onNavigationIconClick = onBackClick, + onShareClick = onShareClick, topAppBarScrollBehavior = topAppBarScrollBehavior, onContainerColor = animateOnContainerColor.value, scrolledContainerColor = detailsState.scrolledContainerColor(isThemeAmoled), diff --git a/feature/details-impl/src/main/res/values-ru/strings.xml b/feature/details-impl-kmp/src/androidMain/res/values-ru/strings.xml similarity index 100% rename from feature/details-impl/src/main/res/values-ru/strings.xml rename to feature/details-impl-kmp/src/androidMain/res/values-ru/strings.xml diff --git a/feature/details-impl/src/main/res/values/strings.xml b/feature/details-impl-kmp/src/androidMain/res/values/strings.xml similarity index 100% rename from feature/details-impl/src/main/res/values/strings.xml rename to feature/details-impl-kmp/src/androidMain/res/values/strings.xml diff --git a/feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt b/feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt new file mode 100644 index 000000000..b0b555cbc --- /dev/null +++ b/feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt @@ -0,0 +1,53 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.details.ktx + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.network.config.ScreenState +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.ktx.url +import org.michaelbel.movies.ui.strings.MoviesStrings + +internal val ScreenState.Content<*>.movie: MoviePojo + get() = data as MoviePojo + +internal val ScreenState.toolbarTitle: String + @Composable get() = when (this) { + is ScreenState.Loading -> stringResource(MoviesStrings.details_title) + is ScreenState.Content<*> -> movie.title + is ScreenState.Failure -> stringResource(MoviesStrings.details_title) + } + +internal val ScreenState.movieUrl: String? + get() = if (this is ScreenState.Content<*>) movie.url else null + +@Composable +internal fun ScreenState.primaryContainer(isAmoledTheme: Boolean): Color { + return when (this) { + is ScreenState.Loading -> MaterialTheme.colorScheme.primaryContainer + is ScreenState.Content<*> -> if (movie.containerColor != null && !isAmoledTheme) Color(requireNotNull(movie.containerColor)) else MaterialTheme.colorScheme.primaryContainer + is ScreenState.Failure -> MaterialTheme.colorScheme.primaryContainer + } +} + +@Composable +internal fun ScreenState.onPrimaryContainer(isAmoledTheme: Boolean): Color { + return when (this) { + is ScreenState.Loading -> MaterialTheme.colorScheme.onPrimaryContainer + is ScreenState.Content<*> -> if (movie.onContainerColor != null && !isAmoledTheme) Color(requireNotNull(movie.onContainerColor)) else MaterialTheme.colorScheme.onPrimaryContainer + is ScreenState.Failure -> MaterialTheme.colorScheme.onPrimaryContainer + } +} + +@Composable +internal fun ScreenState.scrolledContainerColor(isAmoledTheme: Boolean): Color { + return when (this) { + is ScreenState.Loading -> MaterialTheme.colorScheme.inversePrimary + is ScreenState.Content<*> -> if (movie.containerColor != null && !isAmoledTheme) Color.Transparent else MaterialTheme.colorScheme.inversePrimary + is ScreenState.Failure -> MaterialTheme.colorScheme.inversePrimary + } +} \ No newline at end of file diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt b/feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt similarity index 80% rename from feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt rename to feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt index a88446705..0a923820d 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt +++ b/feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalResourceApi::class) + package org.michaelbel.movies.details.ui import androidx.compose.foundation.background @@ -7,21 +9,20 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.details_impl.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.strings.MoviesStrings import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun DetailsFailure( +internal fun DetailsFailure( modifier: Modifier = Modifier ) { ConstraintLayout( @@ -31,7 +32,7 @@ fun DetailsFailure( Icon( imageVector = MoviesIcons.Info, - contentDescription = MoviesContentDescription.None, + contentDescription = MoviesContentDescriptionCommon.None, modifier = Modifier.constrainAs(image) { width = Dimension.value(36.dp) height = Dimension.value(36.dp) @@ -44,7 +45,7 @@ fun DetailsFailure( ) Text( - text = stringResource(R.string.details_error_loading), + text = stringResource(MoviesStrings.details_error_loading), modifier = Modifier.constrainAs(text) { width = Dimension.fillToConstraints height = Dimension.wrapContent @@ -53,15 +54,13 @@ fun DetailsFailure( end.linkTo(parent.end, 16.dp) }, textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) } } @Composable -@DevicePreviews +/*@DevicePreviews*/ private fun DetailsFailurePreview() { MoviesTheme { DetailsFailure( @@ -73,7 +72,7 @@ private fun DetailsFailurePreview() { } @Composable -@Preview +/*@Preview*/ private fun DetailsFailureAmoledPreview() { MoviesTheme( theme = AppTheme.Amoled diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt b/feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt similarity index 78% rename from feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt rename to feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt index 5c580a2ce..e3f4b279c 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt +++ b/feature/details-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt @@ -1,9 +1,11 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package org.michaelbel.movies.details.ui import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -13,46 +15,42 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.ui.compose.iconbutton.BackIcon import org.michaelbel.movies.ui.compose.iconbutton.ShareIcon -import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.TitlePreviewParameterProvider +import org.michaelbel.movies.ui.ktx.modifierDisplayCutoutWindowInsets import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun DetailsToolbar( +internal fun DetailsToolbar( movieTitle: String, movieUrl: String?, onNavigationIconClick: () -> Unit, + onShareClick: (String) -> Unit, topAppBarScrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier, onContainerColor: Color = MaterialTheme.colorScheme.onPrimaryContainer, - scrolledContainerColor: Color = MaterialTheme.colorScheme.inversePrimary, + scrolledContainerColor: Color = MaterialTheme.colorScheme.inversePrimary ) { TopAppBar( title = { Text( text = movieTitle, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge.copy( - color = onContainerColor - ) + style = MaterialTheme.typography.titleLarge.copy(onContainerColor) ) }, modifier = modifier, actions = { AnimatedVisibility( visible = movieUrl != null, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets), + modifier = Modifier.then(modifierDisplayCutoutWindowInsets), enter = fadeIn() ) { if (movieUrl != null) { ShareIcon( url = movieUrl, + onShareClick = onShareClick, onContainerColor = onContainerColor ) } @@ -61,7 +59,7 @@ fun DetailsToolbar( navigationIcon = { BackIcon( onClick = onNavigationIconClick, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets), + modifier = Modifier.then(modifierDisplayCutoutWindowInsets), onContainerColor = onContainerColor ) }, @@ -74,15 +72,16 @@ fun DetailsToolbar( } @Composable -@DevicePreviews +/*@DevicePreviews*/ private fun DetailsToolbarPreview( - @PreviewParameter(TitlePreviewParameterProvider::class) title: String + /*@PreviewParameter(TitlePreviewParameterProvider::class)*/ title: String ) { MoviesTheme { DetailsToolbar( movieTitle = title, movieUrl = null, onNavigationIconClick = {}, + onShareClick = {}, topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), modifier = Modifier.statusBarsPadding() ) @@ -90,9 +89,9 @@ private fun DetailsToolbarPreview( } @Composable -@Preview +/*@Preview*/ private fun DetailsToolbarAmoledPreview( - @PreviewParameter(TitlePreviewParameterProvider::class) title: String + /*@PreviewParameter(TitlePreviewParameterProvider::class)*/ title: String ) { MoviesTheme( theme = AppTheme.Amoled @@ -101,6 +100,7 @@ private fun DetailsToolbarAmoledPreview( movieTitle = title, movieUrl = null, onNavigationIconClick = {}, + onShareClick = {}, topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), modifier = Modifier.statusBarsPadding() ) diff --git a/feature/details-impl/build.gradle.kts b/feature/details-impl/build.gradle.kts deleted file mode 100644 index cb4a8159e..000000000 --- a/feature/details-impl/build.gradle.kts +++ /dev/null @@ -1,69 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.details_impl" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", - "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi" - ) - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(project(":core:navigation")) - api(project(":core:ui")) - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:network")) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/details-impl/src/main/AndroidManifest.xml b/feature/details-impl/src/main/AndroidManifest.xml deleted file mode 100644 index 6522f7291..000000000 --- a/feature/details-impl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt deleted file mode 100644 index 7b85fca56..000000000 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.michaelbel.movies.details.ktx - -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.details_impl.R -import org.michaelbel.movies.network.ScreenState -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.ktx.url - -internal val ScreenState.Content<*>.movie: MovieDb - get() = data as MovieDb - -internal val ScreenState.toolbarTitle: String - @Composable get() = when (this) { - is ScreenState.Loading -> stringResource(R.string.details_title) - is ScreenState.Content<*> -> movie.title - is ScreenState.Failure -> stringResource(R.string.details_title) - } - -internal val ScreenState.movieUrl: String? - get() = if (this is ScreenState.Content<*>) movie.url else null - -@Composable -internal fun ScreenState.primaryContainer(isAmoledTheme: Boolean): Color { - return when (this) { - is ScreenState.Loading -> MaterialTheme.colorScheme.primaryContainer - is ScreenState.Content<*> -> if (movie.containerColor != null && !isAmoledTheme) Color(requireNotNull(movie.containerColor)) else MaterialTheme.colorScheme.primaryContainer - is ScreenState.Failure -> MaterialTheme.colorScheme.primaryContainer - } -} - -@Composable -internal fun ScreenState.onPrimaryContainer(isAmoledTheme: Boolean): Color { - return when (this) { - is ScreenState.Loading -> MaterialTheme.colorScheme.onPrimaryContainer - is ScreenState.Content<*> -> if (movie.onContainerColor != null && !isAmoledTheme) Color(requireNotNull(movie.onContainerColor)) else MaterialTheme.colorScheme.onPrimaryContainer - is ScreenState.Failure -> MaterialTheme.colorScheme.onPrimaryContainer - } -} - -@Composable -internal fun ScreenState.scrolledContainerColor(isAmoledTheme: Boolean): Color { - return when (this) { - is ScreenState.Loading -> MaterialTheme.colorScheme.inversePrimary - is ScreenState.Content<*> -> if (movie.containerColor != null && !isAmoledTheme) Color.Transparent else MaterialTheme.colorScheme.inversePrimary - is ScreenState.Failure -> MaterialTheme.colorScheme.inversePrimary - } -} \ No newline at end of file diff --git a/feature/gallery-impl/.gitignore b/feature/details-kmp/.gitignore similarity index 100% rename from feature/gallery-impl/.gitignore rename to feature/details-kmp/.gitignore diff --git a/feature/details-kmp/build.gradle.kts b/feature/details-kmp/build.gradle.kts new file mode 100644 index 000000000..b2be6af68 --- /dev/null +++ b/feature/details-kmp/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:common-kmp")) + implementation(project(":core:navigation-kmp")) + api(project(":feature:details-impl-kmp")) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.material) + implementation(compose.material3) + implementation(libs.precompose) + } + } +} + +android { + namespace = "org.michaelbel.movies.details_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/details/src/main/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt b/feature/details-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt similarity index 100% rename from feature/details/src/main/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt rename to feature/details-kmp/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt diff --git a/feature/details/src/main/kotlin/org/michaelbel/movies/details/DetailsDestination.kt b/feature/details-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/DetailsDestination.kt similarity index 100% rename from feature/details/src/main/kotlin/org/michaelbel/movies/details/DetailsDestination.kt rename to feature/details-kmp/src/commonMain/kotlin/org/michaelbel/movies/details/DetailsDestination.kt diff --git a/feature/details-kmp/src/desktopMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt b/feature/details-kmp/src/desktopMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt new file mode 100644 index 000000000..b6d7ac6a1 --- /dev/null +++ b/feature/details-kmp/src/desktopMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.details + +import androidx.compose.material.Text +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.RouteBuilder + +fun Navigator.navigateToDetails(movieList: String, movieId: Int) { + navigate("movie?movieList=$movieList&movieId=$movieId") +} + +fun RouteBuilder.detailsGraph( + navigateBack: () -> Unit, + navigateToGallery: (Int) -> Unit +) { + scene( + route = DetailsDestination.route + ) { + Text("details") + } +} \ No newline at end of file diff --git a/feature/details/build.gradle.kts b/feature/details/build.gradle.kts deleted file mode 100644 index 5fa4f8b91..000000000 --- a/feature/details/build.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.details" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:common")) - implementation(project(":feature:details-impl")) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/details/src/main/AndroidManifest.xml b/feature/details/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/details/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/gallery/.gitignore b/feature/feed-impl-kmp/.gitignore similarity index 100% rename from feature/gallery/.gitignore rename to feature/feed-impl-kmp/.gitignore diff --git a/feature/feed-impl-kmp/build.gradle.kts b/feature/feed-impl-kmp/build.gradle.kts new file mode 100644 index 000000000..e2a536a6f --- /dev/null +++ b/feature/feed-impl-kmp/build.gradle.kts @@ -0,0 +1,79 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + implementation(project(":core:platform-services:interactor-kmp")) + api(project(":core:navigation-kmp")) + api(project(":core:ui-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core::interactor-kmp")) + implementation(project(":core:network-kmp")) + implementation(project(":core:notifications-kmp")) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.material3) + implementation(compose.runtime) + } + } +} + +android { + namespace = "org.michaelbel.movies.feed_impl_kmp" + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt similarity index 81% rename from feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt rename to feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt index fe5198d79..fec2c0c16 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt +++ b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt @@ -1,14 +1,14 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + package org.michaelbel.movies.feed -import android.app.Activity import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.paging.PagingData import androidx.paging.cachedIn -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -28,28 +28,24 @@ import org.michaelbel.movies.interactor.Interactor import org.michaelbel.movies.network.connectivity.NetworkManager import org.michaelbel.movies.network.connectivity.NetworkStatus import org.michaelbel.movies.notifications.NotificationClient -import org.michaelbel.movies.persistence.database.entity.AccountDb -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.platform.update.UpdateListener -import org.michaelbel.movies.platform.update.UpdateService +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import org.michaelbel.movies.persistence.database.entity.MoviePojo -@HiltViewModel -class FeedViewModel @Inject constructor( +class FeedViewModel( savedStateHandle: SavedStateHandle, private val interactor: Interactor, private val notificationClient: NotificationClient, - private val updateService: UpdateService, networkManager: NetworkManager ): BaseViewModel() { private val requestToken: String? = savedStateHandle["request_token"] private val approved: Boolean? = savedStateHandle["approved"] - val account: StateFlow = interactor.account + val account: StateFlow = interactor.account .stateIn( scope = this, started = SharingStarted.Lazily, - initialValue = AccountDb.Empty + initialValue = AccountPojo.Empty ) val networkStatus: StateFlow = networkManager.status @@ -73,7 +69,7 @@ class FeedViewModel @Inject constructor( initialValue = runBlocking { interactor.currentMovieList.first() } ) - val pagingDataFlow: Flow> = currentMovieList + val pagingDataFlow: Flow> = currentMovieList .flatMapLatest { movieList -> interactor.moviesPagingData(movieList) } .cachedIn(this) @@ -81,14 +77,8 @@ class FeedViewModel @Inject constructor( val notificationsPermissionRequired: StateFlow = _notificationsPermissionRequired.asStateFlow() var isAuthFailureSnackbarShowed: Boolean by mutableStateOf(false) - var updateAvailableMessage: Boolean by mutableStateOf(false) init { - updateService.setUpdateAvailableListener(object: UpdateListener { - override fun onAvailable(result: Boolean) { - updateAvailableMessage = result - } - }) authorizeAccount(requestToken, approved) subscribeNotificationsPermissionRequired() } @@ -101,10 +91,6 @@ class FeedViewModel @Inject constructor( } } - fun startUpdate(activity: Activity) { - updateService.startUpdate(activity) - } - fun onNotificationBottomSheetHide() = launch { _notificationsPermissionRequired.tryEmit(false) notificationClient.updateNotificationExpireTime() diff --git a/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/di/FeedKoinModule.kt b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/di/FeedKoinModule.kt new file mode 100644 index 000000000..b499483c6 --- /dev/null +++ b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/di/FeedKoinModule.kt @@ -0,0 +1,17 @@ +package org.michaelbel.movies.feed.di + +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.michaelbel.movies.feed.FeedViewModel +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.network.connectivity.di.networkManagerKoinModule +import org.michaelbel.movies.notifications.di.notificationClientKoinModule + +val feedKoinModule = module { + includes( + interactorKoinModule, + notificationClientKoinModule, + networkManagerKoinModule + ) + viewModel { FeedViewModel(get(), get(), get(), get()) } +} \ No newline at end of file diff --git a/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt new file mode 100644 index 000000000..eebafba31 --- /dev/null +++ b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt @@ -0,0 +1,14 @@ +package org.michaelbel.movies.feed.ktx + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.feed_impl_kmp.R + +internal val MovieList.titleText: String + @Composable get() = when (this) { + is MovieList.NowPlaying -> stringResource(R.string.feed_title_now_playing) + is MovieList.Popular -> stringResource(R.string.feed_title_popular) + is MovieList.TopRated -> stringResource(R.string.feed_title_top_rated) + is MovieList.Upcoming -> stringResource(R.string.feed_title_upcoming) + } \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedEmpty.kt b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedEmpty.kt similarity index 92% rename from feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedEmpty.kt rename to feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedEmpty.kt index 2deb54c09..fc2125d6d 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedEmpty.kt +++ b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedEmpty.kt @@ -14,14 +14,14 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.feed_impl.R +import org.michaelbel.movies.feed_impl_kmp.R import org.michaelbel.movies.ui.accessibility.MoviesContentDescription import org.michaelbel.movies.ui.icons.MoviesIcons import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun FeedEmpty( +internal fun FeedEmpty( modifier: Modifier = Modifier ) { ConstraintLayout( @@ -53,9 +53,7 @@ fun FeedEmpty( end.linkTo(parent.end, 16.dp) }, textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) } } diff --git a/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedRoute.kt b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedRoute.kt new file mode 100644 index 000000000..55be2d457 --- /dev/null +++ b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedRoute.kt @@ -0,0 +1,47 @@ +package org.michaelbel.movies.feed.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.compose.collectAsLazyPagingItems +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.feed.FeedViewModel +import org.michaelbel.movies.persistence.database.ktx.orEmpty + +@Composable +fun FeedRoute( + onNavigateToSearch: () -> Unit, + onNavigateToAuth: () -> Unit, + onNavigateToAccount: () -> Unit, + onNavigateToSettings: () -> Unit, + onNavigateToDetails: (String, Int) -> Unit, + modifier: Modifier = Modifier, + viewModel: FeedViewModel = koinViewModel() +) { + val pagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems() + val account by viewModel.account.collectAsStateWithLifecycle() + val currentFeedView by viewModel.currentFeedView.collectAsStateWithLifecycle() + val currentMovieList by viewModel.currentMovieList.collectAsStateWithLifecycle() + val notificationsPermissionRequired by viewModel.notificationsPermissionRequired.collectAsStateWithLifecycle() + val networkStatus by viewModel.networkStatus.collectAsStateWithLifecycle() + val isAuthFailureSnackbarShowed = viewModel.isAuthFailureSnackbarShowed + + FeedScreenContent( + pagingItems = pagingItems, + account = account.orEmpty, + networkStatus = networkStatus, + currentFeedView = currentFeedView, + currentMovieList = currentMovieList, + notificationsPermissionRequired = notificationsPermissionRequired, + isAuthFailureSnackbarShowed = isAuthFailureSnackbarShowed, + onNavigateToSearch = onNavigateToSearch, + onNavigateToAuth = onNavigateToAuth, + onNavigateToAccount = onNavigateToAccount, + onNavigateToSettings = onNavigateToSettings, + onNavigateToDetails = onNavigateToDetails, + onNotificationBottomSheetHideClick = viewModel::onNotificationBottomSheetHide, + onSnackbarDismissed = viewModel::onSnackbarDismissed, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt new file mode 100644 index 000000000..31e8a04bb --- /dev/null +++ b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt @@ -0,0 +1,182 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package org.michaelbel.movies.feed.ui + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.paging.compose.LazyPagingItems +import kotlinx.coroutines.launch +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException +import org.michaelbel.movies.common.exceptions.PageEmptyException +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.feed.ktx.titleText +import org.michaelbel.movies.feed_impl_kmp.R +import org.michaelbel.movies.network.connectivity.NetworkStatus +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.ui.compose.NotificationBottomSheet +import org.michaelbel.movies.ui.compose.page.PageContent +import org.michaelbel.movies.ui.compose.page.PageFailure +import org.michaelbel.movies.ui.compose.page.PageLoading +import org.michaelbel.movies.ui.ktx.clickableWithoutRipple +import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets +import org.michaelbel.movies.ui.ktx.isFailure +import org.michaelbel.movies.ui.ktx.isLoading +import org.michaelbel.movies.ui.ktx.refreshThrowable +import java.net.UnknownHostException +import org.michaelbel.movies.ui_kmp.R as UiR + +@Composable +internal fun FeedScreenContent( + pagingItems: LazyPagingItems, + account: AccountPojo, + networkStatus: NetworkStatus, + currentFeedView: FeedView, + currentMovieList: MovieList, + notificationsPermissionRequired: Boolean, + isAuthFailureSnackbarShowed: Boolean, + onNavigateToSearch: () -> Unit, + onNavigateToAuth: () -> Unit, + onNavigateToAccount: () -> Unit, + onNavigateToSettings: () -> Unit, + onNavigateToDetails: (String, Int) -> Unit, + onNotificationBottomSheetHideClick: () -> Unit, + onSnackbarDismissed: () -> Unit, + modifier: Modifier = Modifier +) { + val scope = rememberCoroutineScope() + val lazyListState = rememberLazyListState() + val lazyGridState = rememberLazyGridState() + val lazyStaggeredGridState = rememberLazyStaggeredGridState() + val snackbarHostState = remember { SnackbarHostState() } + + val onScrollToTop: () -> Unit = { + scope.launch { lazyListState.animateScrollToItem(0) } + scope.launch { lazyGridState.animateScrollToItem(0) } + scope.launch { lazyStaggeredGridState.animateScrollToItem(0) } + } + + val onShowSnackbar: (String, SnackbarDuration) -> Unit = { message, snackbarDuration -> + scope.launch { + snackbarHostState.currentSnackbarData?.dismiss() + val snackbarResult = snackbarHostState.showSnackbar( + message = message, + duration = snackbarDuration + ) + if (snackbarResult == SnackbarResult.Dismissed) { + onSnackbarDismissed() + } + } + } + + if (pagingItems.isFailure && pagingItems.refreshThrowable is ApiKeyNotNullException) { + onShowSnackbar(stringResource(UiR.string.error_api_key_null), SnackbarDuration.Long) + } + + if (networkStatus == NetworkStatus.Available && pagingItems.isFailure && pagingItems.refreshThrowable is UnknownHostException) { + pagingItems.retry() + } + + var modalDialog by remember { mutableStateOf(false) } + modalDialog = notificationsPermissionRequired + if (modalDialog) { + NotificationBottomSheet( + onDismissRequest = { + modalDialog = false + onNotificationBottomSheetHideClick() + }, + modifier = Modifier.fillMaxWidth() + ) + } + + if (isAuthFailureSnackbarShowed) { + onShowSnackbar(stringResource(R.string.feed_auth_failure), SnackbarDuration.Short) + } + + val topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + + Scaffold( + modifier = modifier.nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), + topBar = { + FeedToolbar( + title = currentMovieList.titleText, + modifier = Modifier.clickableWithoutRipple(onScrollToTop), + account = account, + onSearchIconClick = onNavigateToSearch, + onAuthIconClick = onNavigateToAuth, + onAccountIconClick = onNavigateToAccount, + topAppBarScrollBehavior = topAppBarScrollBehavior, + onSettingsIconClick = onNavigateToSettings + ) + }, + snackbarHost = { + SnackbarHost( + hostState = snackbarHostState + ) + }, + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { innerPadding -> + when { + pagingItems.isLoading -> { + PageLoading( + feedView = currentFeedView, + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets), + paddingValues = innerPadding + ) + } + pagingItems.isFailure -> { + if (pagingItems.refreshThrowable is PageEmptyException) { + FeedEmpty( + modifier = Modifier + .padding(innerPadding) + .windowInsetsPadding(displayCutoutWindowInsets) + .fillMaxSize() + ) + } else { + PageFailure( + modifier = Modifier + .padding(innerPadding) + .windowInsetsPadding(displayCutoutWindowInsets) + .fillMaxSize() + .clickableWithoutRipple(pagingItems::retry) + ) + } + } + else -> { + PageContent( + feedView = currentFeedView, + lazyListState = lazyListState, + lazyGridState = lazyGridState, + lazyStaggeredGridState = lazyStaggeredGridState, + pagingItems = pagingItems, + onMovieClick = onNavigateToDetails, + contentPadding = innerPadding, + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) + ) + } + } + } +} \ No newline at end of file diff --git a/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt new file mode 100644 index 000000000..4cccb80b0 --- /dev/null +++ b/feature/feed-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt @@ -0,0 +1,151 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package org.michaelbel.movies.feed.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.feed_impl_kmp.R +import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty +import org.michaelbel.movies.persistence.database.entity.AccountPojo +import org.michaelbel.movies.persistence.database.ktx.isEmpty +import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.ui.compose.AccountAvatar +import org.michaelbel.movies.ui.compose.ApiKeyBox +import org.michaelbel.movies.ui.compose.iconbutton.SearchIcon +import org.michaelbel.movies.ui.compose.iconbutton.SettingsIcon +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets +import org.michaelbel.movies.ui.ktx.lettersTextFontSizeSmall +import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun FeedToolbar( + title: String, + account: AccountPojo, + onSearchIconClick: () -> Unit, + onAuthIconClick: () -> Unit, + onAccountIconClick: () -> Unit, + onSettingsIconClick: () -> Unit, + topAppBarScrollBehavior: TopAppBarScrollBehavior, + modifier: Modifier = Modifier +) { + Column( + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TopAppBar( + title = { + Text( + text = title, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + }, + modifier = modifier, + navigationIcon = { + SearchIcon( + onClick = onSearchIconClick, + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) + ) + }, + actions = { + Row( + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) + ) { + SettingsIcon( + onClick = onSettingsIconClick + ) + + IconButton( + onClick = if (account.isEmpty) onAuthIconClick else onAccountIconClick + ) { + if (account.isEmpty) { + Image( + imageVector = MoviesIcons.AccountCircle, + contentDescription = stringResource(MoviesContentDescription.AccountIcon), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer) + ) + } else { + AccountAvatar( + account = account, + fontSize = account.lettersTextFontSizeSmall, + modifier = Modifier.size(32.dp) + ) + } + } + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + scrolledContainerColor = MaterialTheme.colorScheme.inversePrimary + ), + scrollBehavior = topAppBarScrollBehavior + ) + + if (isTmdbApiKeyEmpty) { + ApiKeyBox( + modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp) + ) + } + } +} + +@Composable +@DevicePreviews +private fun FeedToolbarPreview() { + MoviesTheme { + FeedToolbar( + title = stringResource(R.string.feed_title_now_playing), + account = AccountPojo.Empty, + onSearchIconClick = {}, + onAccountIconClick = {}, + onAuthIconClick = {}, + onSettingsIconClick = {}, + topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), + modifier = Modifier.statusBarsPadding() + ) + } +} + +@Composable +@Preview +private fun FeedToolbarAmoledPreview() { + MoviesTheme( + theme = AppTheme.Amoled + ) { + FeedToolbar( + title = stringResource(R.string.feed_title_now_playing), + account = AccountPojo.Empty, + onSearchIconClick = {}, + onAccountIconClick = {}, + onAuthIconClick = {}, + onSettingsIconClick = {}, + topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), + modifier = Modifier.statusBarsPadding() + ) + } +} \ No newline at end of file diff --git a/feature/feed-impl/src/main/res/values-ru/strings.xml b/feature/feed-impl-kmp/src/androidMain/res/values-ru/strings.xml similarity index 100% rename from feature/feed-impl/src/main/res/values-ru/strings.xml rename to feature/feed-impl-kmp/src/androidMain/res/values-ru/strings.xml diff --git a/feature/feed-impl/src/main/res/values/strings.xml b/feature/feed-impl-kmp/src/androidMain/res/values/strings.xml similarity index 100% rename from feature/feed-impl/src/main/res/values/strings.xml rename to feature/feed-impl-kmp/src/androidMain/res/values/strings.xml diff --git a/feature/feed-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt b/feature/feed-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt new file mode 100644 index 000000000..07fa0e474 --- /dev/null +++ b/feature/feed-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt @@ -0,0 +1,52 @@ +package org.michaelbel.movies.feed.ui + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun FeedRoute( + onNavigateToSearch: () -> Unit, + onNavigateToAuth: () -> Unit, + onNavigateToAccount: () -> Unit, + onNavigateToSettings: () -> Unit, + onNavigateToDetails: (String, Int) -> Unit, + modifier: Modifier = Modifier +) { + FeedScreenContent( + onNavigateToSearch = onNavigateToSearch, + onNavigateToAuth = onNavigateToAuth, + onNavigateToAccount = onNavigateToAccount, + onNavigateToSettings = onNavigateToSettings, + onNavigateToDetails = onNavigateToDetails, + modifier = modifier + ) +} + +@Composable +private fun FeedScreenContent( + onNavigateToSearch: () -> Unit, + onNavigateToAuth: () -> Unit, + onNavigateToAccount: () -> Unit, + onNavigateToSettings: () -> Unit, + onNavigateToDetails: (String, Int) -> Unit, + modifier: Modifier = Modifier +) { + Scaffold( + modifier = modifier.fillMaxSize(), + topBar = { + FeedToolbar( + title = "Now Playing Movies", + onSearchIconClick = onNavigateToSearch, + onAuthIconClick = onNavigateToAuth, + onAccountIconClick = onNavigateToAccount, + onSettingsIconClick = onNavigateToSettings + ) + }, + backgroundColor = MaterialTheme.colors.background + ) { innerPadding -> + + } +} \ No newline at end of file diff --git a/feature/feed-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt b/feature/feed-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt new file mode 100644 index 000000000..0a7898dde --- /dev/null +++ b/feature/feed-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt @@ -0,0 +1,80 @@ +package org.michaelbel.movies.feed.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.text.style.TextOverflow +import org.michaelbel.movies.ui.compose.iconbutton.SearchIcon +import org.michaelbel.movies.ui.compose.iconbutton.SettingsIcon +import org.michaelbel.movies.ui.icons.MoviesIcons + +@Composable +internal fun FeedToolbar( + title: String, + onSearchIconClick: () -> Unit, + onAuthIconClick: () -> Unit, + onAccountIconClick: () -> Unit, + onSettingsIconClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TopAppBar( + title = { + Text( + text = title, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + }, + modifier = modifier, + navigationIcon = { + SearchIcon( + onClick = onSearchIconClick + ) + }, + actions = { + Row { + SettingsIcon( + onClick = onSettingsIconClick + ) + + IconButton( + onClick = if (true) onAuthIconClick else onAccountIconClick + ) { + if (true) { + Image( + imageVector = MoviesIcons.AccountCircle, + contentDescription = null, + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer) + ) + } else { + /*AccountAvatar( + account = account, + fontSize = account.lettersTextFontSizeSmall, + modifier = Modifier.size(32.dp) + )*/ + } + } + } + } + ) + + /*if (isTmdbApiKeyEmpty) { + ApiKeyBox( + modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp) + ) + }*/ + } +} \ No newline at end of file diff --git a/feature/feed-impl/build.gradle.kts b/feature/feed-impl/build.gradle.kts deleted file mode 100644 index 17c7205e9..000000000 --- a/feature/feed-impl/build.gradle.kts +++ /dev/null @@ -1,71 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.feed_impl" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" - ) - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:platform-services:interactor")) - api(project(":core:navigation")) - api(project(":core:ui")) - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:network")) - implementation(project(":core:notifications")) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/feed-impl/src/main/AndroidManifest.xml b/feature/feed-impl/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/feed-impl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt deleted file mode 100644 index c0a8e457c..000000000 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.feed.ktx - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.feed_impl.R - -internal val MovieList.titleText: String - @Composable get() = when (this) { - is MovieList.NowPlaying -> stringResource(R.string.feed_title_now_playing) - is MovieList.Popular -> stringResource(R.string.feed_title_popular) - is MovieList.TopRated -> stringResource(R.string.feed_title_top_rated) - is MovieList.Upcoming -> stringResource(R.string.feed_title_upcoming) - } \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt deleted file mode 100644 index cc2f9f146..000000000 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt +++ /dev/null @@ -1,283 +0,0 @@ -package org.michaelbel.movies.feed.ui - -import android.app.Activity -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState -import androidx.compose.material3.BottomSheetScaffold -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SheetValue -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.rememberBottomSheetScaffoldState -import androidx.compose.material3.rememberStandardBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.collectAsLazyPagingItems -import java.net.UnknownHostException -import kotlinx.coroutines.launch -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException -import org.michaelbel.movies.common.exceptions.PageEmptyException -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.feed.FeedViewModel -import org.michaelbel.movies.feed.ktx.titleText -import org.michaelbel.movies.feed_impl.R -import org.michaelbel.movies.network.connectivity.NetworkStatus -import org.michaelbel.movies.network.isTmdbApiKeyEmpty -import org.michaelbel.movies.persistence.database.entity.AccountDb -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.ktx.orEmpty -import org.michaelbel.movies.ui.compose.NotificationBottomSheet -import org.michaelbel.movies.ui.compose.page.PageContent -import org.michaelbel.movies.ui.compose.page.PageFailure -import org.michaelbel.movies.ui.compose.page.PageLoading -import org.michaelbel.movies.ui.ktx.clickableWithoutRipple -import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets -import org.michaelbel.movies.ui.ktx.isFailure -import org.michaelbel.movies.ui.ktx.isLoading -import org.michaelbel.movies.ui.ktx.refreshThrowable -import org.michaelbel.movies.ui.R as UiR - -@Composable -fun FeedRoute( - onNavigateToSearch: () -> Unit, - onNavigateToAuth: () -> Unit, - onNavigateToAccount: () -> Unit, - onNavigateToSettings: () -> Unit, - onNavigateToDetails: (String, Int) -> Unit, - modifier: Modifier = Modifier, - viewModel: FeedViewModel = hiltViewModel() -) { - val pagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems() - val account by viewModel.account.collectAsStateWithLifecycle() - val currentFeedView by viewModel.currentFeedView.collectAsStateWithLifecycle() - val currentMovieList by viewModel.currentMovieList.collectAsStateWithLifecycle() - val notificationsPermissionRequired by viewModel.notificationsPermissionRequired.collectAsStateWithLifecycle() - val networkStatus by viewModel.networkStatus.collectAsStateWithLifecycle() - val isUpdateAvailable by rememberUpdatedState(viewModel.updateAvailableMessage) - val isAuthFailureSnackbarShowed = viewModel.isAuthFailureSnackbarShowed - - FeedScreenContent( - pagingItems = pagingItems, - account = account.orEmpty, - networkStatus = networkStatus, - currentFeedView = currentFeedView, - currentMovieList = currentMovieList, - notificationsPermissionRequired = notificationsPermissionRequired, - isAuthFailureSnackbarShowed = isAuthFailureSnackbarShowed, - isUpdateIconVisible = isUpdateAvailable, - onNavigateToSearch = onNavigateToSearch, - onNavigateToAuth = onNavigateToAuth, - onNavigateToAccount = onNavigateToAccount, - onNavigateToSettings = onNavigateToSettings, - onNavigateToDetails = onNavigateToDetails, - onUpdateIconClick = viewModel::startUpdate, - onNotificationBottomSheetHideClick = viewModel::onNotificationBottomSheetHide, - onSnackbarDismissed = viewModel::onSnackbarDismissed, - modifier = modifier - ) -} - -@Composable -private fun FeedScreenContent( - pagingItems: LazyPagingItems, - account: AccountDb, - networkStatus: NetworkStatus, - currentFeedView: FeedView, - currentMovieList: MovieList, - notificationsPermissionRequired: Boolean, - isAuthFailureSnackbarShowed: Boolean, - isUpdateIconVisible: Boolean, - onNavigateToSearch: () -> Unit, - onNavigateToAuth: () -> Unit, - onNavigateToAccount: () -> Unit, - onNavigateToSettings: () -> Unit, - onNavigateToDetails: (String, Int) -> Unit, - onUpdateIconClick: (Activity) -> Unit, - onNotificationBottomSheetHideClick: () -> Unit, - onSnackbarDismissed: () -> Unit, - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val scope = rememberCoroutineScope() - val lazyListState = rememberLazyListState() - val lazyGridState = rememberLazyGridState() - val lazyStaggeredGridState = rememberLazyStaggeredGridState() - val snackbarHostState = remember { SnackbarHostState() } - val notificationBottomSheetScaffoldState = rememberBottomSheetScaffoldState( - bottomSheetState = rememberStandardBottomSheetState( - confirmValueChange = { sheetValue -> - if (sheetValue == SheetValue.Hidden) { - onNotificationBottomSheetHideClick() - } - true - }, - skipHiddenState = false - ) - ) - val onNotificationBottomSheetShow: () -> Unit = { - scope.launch { - notificationBottomSheetScaffoldState.bottomSheetState.expand() - } - } - val onNotificationBottomSheetHide: () -> Unit = { - scope.launch { - notificationBottomSheetScaffoldState.bottomSheetState.hide() - onNotificationBottomSheetHideClick() - } - } - - val onScrollToTop: () -> Unit = { - scope.launch { - lazyListState.animateScrollToItem(0) - } - scope.launch { - lazyGridState.animateScrollToItem(0) - } - scope.launch { - lazyStaggeredGridState.animateScrollToItem(0) - } - } - - val onShowSnackbar: (String, SnackbarDuration) -> Unit = { message, snackbarDuration -> - scope.launch { - snackbarHostState.currentSnackbarData?.dismiss() - val snackbarResult = snackbarHostState.showSnackbar( - message = message, - duration = snackbarDuration - ) - if (snackbarResult == SnackbarResult.Dismissed) { - onSnackbarDismissed() - } - } - } - - if (pagingItems.isFailure && pagingItems.refreshThrowable is ApiKeyNotNullException) { - onShowSnackbar(stringResource(UiR.string.error_api_key_null), SnackbarDuration.Long) - } - - if (networkStatus == NetworkStatus.Available && pagingItems.isFailure && pagingItems.refreshThrowable is UnknownHostException) { - pagingItems.retry() - } - - if (notificationsPermissionRequired) { - onNotificationBottomSheetShow() - } - - var isBottomSheetExpanded: Boolean by remember { mutableStateOf(false) } - isBottomSheetExpanded = notificationBottomSheetScaffoldState.bottomSheetState.currentValue == SheetValue.Expanded - - if (isAuthFailureSnackbarShowed) { - onShowSnackbar(stringResource(R.string.feed_auth_failure), SnackbarDuration.Short) - } - - BackHandler(isBottomSheetExpanded, onNotificationBottomSheetHide) - - BottomSheetScaffold( - sheetContent = { - NotificationBottomSheet( - modifier = Modifier.fillMaxWidth(), - onBottomSheetHide = onNotificationBottomSheetHide - ) - }, - scaffoldState = notificationBottomSheetScaffoldState, - sheetPeekHeight = 0.dp - ) { bottomSheetInnerPadding -> - val topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - - Scaffold( - modifier = modifier - .padding(bottomSheetInnerPadding) - .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), - topBar = { - FeedToolbar( - title = currentMovieList.titleText, - modifier = Modifier.clickableWithoutRipple(onScrollToTop), - account = account, - isUpdateIconVisible = isUpdateIconVisible, - onSearchIconClick = onNavigateToSearch, - onAuthIconClick = { - when { - isTmdbApiKeyEmpty -> onShowSnackbar(context.getString(UiR.string.error_api_key_null), SnackbarDuration.Long) - else -> onNavigateToAuth() - } - }, - onAccountIconClick = onNavigateToAccount, - onUpdateIconClick = { onUpdateIconClick(context as Activity) }, - topAppBarScrollBehavior = topAppBarScrollBehavior, - onSettingsIconClick = onNavigateToSettings - ) - }, - snackbarHost = { - SnackbarHost( - hostState = snackbarHostState - ) - }, - containerColor = MaterialTheme.colorScheme.primaryContainer - ) { innerPadding -> - when { - pagingItems.isLoading -> { - PageLoading( - feedView = currentFeedView, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets), - paddingValues = innerPadding - ) - } - pagingItems.isFailure -> { - if (pagingItems.refreshThrowable is PageEmptyException) { - FeedEmpty( - modifier = Modifier - .padding(innerPadding) - .windowInsetsPadding(displayCutoutWindowInsets) - .fillMaxSize() - ) - } else { - PageFailure( - modifier = Modifier - .padding(innerPadding) - .windowInsetsPadding(displayCutoutWindowInsets) - .fillMaxSize() - .clickableWithoutRipple(pagingItems::retry) - ) - } - } - else -> { - PageContent( - feedView = currentFeedView, - lazyListState = lazyListState, - lazyGridState = lazyGridState, - lazyStaggeredGridState = lazyStaggeredGridState, - pagingItems = pagingItems, - onMovieClick = onNavigateToDetails, - contentPadding = innerPadding, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) - ) - } - } - } - } -} \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt deleted file mode 100644 index 328ca2cbe..000000000 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt +++ /dev/null @@ -1,151 +0,0 @@ -package org.michaelbel.movies.feed.ui - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.feed_impl.R -import org.michaelbel.movies.persistence.database.entity.AccountDb -import org.michaelbel.movies.persistence.database.ktx.isEmpty -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.compose.AccountAvatar -import org.michaelbel.movies.ui.compose.iconbutton.SearchIcon -import org.michaelbel.movies.ui.compose.iconbutton.SettingsIcon -import org.michaelbel.movies.ui.compose.iconbutton.UpdateIcon -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets -import org.michaelbel.movies.ui.ktx.lettersTextFontSizeSmall -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.BooleanPreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun FeedToolbar( - title: String, - account: AccountDb, - isUpdateIconVisible: Boolean, - onSearchIconClick: () -> Unit, - onAuthIconClick: () -> Unit, - onAccountIconClick: () -> Unit, - onUpdateIconClick: () -> Unit, - onSettingsIconClick: () -> Unit, - topAppBarScrollBehavior: TopAppBarScrollBehavior, - modifier: Modifier = Modifier -) { - TopAppBar( - title = { - Text( - text = title, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - }, - modifier = modifier, - navigationIcon = { - SearchIcon( - onClick = onSearchIconClick, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) - ) - }, - actions = { - Row( - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) - ) { - if (isUpdateIconVisible) { - UpdateIcon( - onClick = onUpdateIconClick - ) - } - - SettingsIcon( - onClick = onSettingsIconClick - ) - - IconButton( - onClick = if (account.isEmpty) onAuthIconClick else onAccountIconClick - ) { - if (account.isEmpty) { - Image( - imageVector = MoviesIcons.AccountCircle, - contentDescription = stringResource(MoviesContentDescription.AccountIcon), - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer) - ) - } else { - AccountAvatar( - account = account, - fontSize = account.lettersTextFontSizeSmall, - modifier = Modifier.size(32.dp) - ) - } - } - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - scrolledContainerColor = MaterialTheme.colorScheme.inversePrimary - ), - scrollBehavior = topAppBarScrollBehavior - ) -} - -@Composable -@DevicePreviews -private fun FeedToolbarPreview( - @PreviewParameter(BooleanPreviewParameterProvider::class) visible: Boolean -) { - MoviesTheme { - FeedToolbar( - title = stringResource(R.string.feed_title_now_playing), - account = AccountDb.Empty, - isUpdateIconVisible = visible, - onSearchIconClick = {}, - onAccountIconClick = {}, - onAuthIconClick = {}, - onUpdateIconClick = {}, - onSettingsIconClick = {}, - topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), - modifier = Modifier.statusBarsPadding() - ) - } -} - -@Composable -@Preview -private fun FeedToolbarAmoledPreview( - @PreviewParameter(BooleanPreviewParameterProvider::class) visible: Boolean -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - FeedToolbar( - title = stringResource(R.string.feed_title_now_playing), - account = AccountDb.Empty, - isUpdateIconVisible = visible, - onSearchIconClick = {}, - onAccountIconClick = {}, - onAuthIconClick = {}, - onUpdateIconClick = {}, - onSettingsIconClick = {}, - topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), - modifier = Modifier.statusBarsPadding() - ) - } -} \ No newline at end of file diff --git a/feature/search-impl/.gitignore b/feature/feed-kmp/.gitignore similarity index 100% rename from feature/search-impl/.gitignore rename to feature/feed-kmp/.gitignore diff --git a/feature/feed-kmp/build.gradle.kts b/feature/feed-kmp/build.gradle.kts new file mode 100644 index 000000000..1a81e9750 --- /dev/null +++ b/feature/feed-kmp/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:navigation-kmp")) + api(project(":feature:feed-impl-kmp")) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.material) + implementation(compose.material3) + implementation(libs.precompose) + } + } +} + +android { + namespace = "org.michaelbel.movies.feed_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/feed/src/main/kotlin/org/michaelbel/movies/feed/FeedNavigation.kt b/feature/feed-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.kt similarity index 100% rename from feature/feed/src/main/kotlin/org/michaelbel/movies/feed/FeedNavigation.kt rename to feature/feed-kmp/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.kt diff --git a/feature/feed/src/main/kotlin/org/michaelbel/movies/feed/FeedDestination.kt b/feature/feed-kmp/src/commonMain/kotlin/org/michaelbel/movies/feed/FeedDestination.kt similarity index 100% rename from feature/feed/src/main/kotlin/org/michaelbel/movies/feed/FeedDestination.kt rename to feature/feed-kmp/src/commonMain/kotlin/org/michaelbel/movies/feed/FeedDestination.kt diff --git a/feature/feed-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.kt b/feature/feed-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.kt new file mode 100644 index 000000000..47ccb088d --- /dev/null +++ b/feature/feed-kmp/src/desktopMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.kt @@ -0,0 +1,27 @@ +package org.michaelbel.movies.feed + +import moe.tlaster.precompose.navigation.RouteBuilder +import org.michaelbel.movies.feed.ui.FeedRoute + +fun RouteBuilder.feedGraph( + navigateToSearch: () -> Unit, + navigateToAuth: () -> Unit, + navigateToAccount: () -> Unit, + navigateToSettings: () -> Unit, + navigateToDetails: (String, Int) -> Unit +) { + scene( + route = FeedDestination.route, + deepLinks = listOf( + "movies://redirect_url?request_token={request_token}&approved={approved}" + ) + ) { + FeedRoute( + onNavigateToSearch = navigateToSearch, + onNavigateToAccount = navigateToAccount, + onNavigateToAuth = navigateToAuth, + onNavigateToSettings = navigateToSettings, + onNavigateToDetails = navigateToDetails + ) + } +} \ No newline at end of file diff --git a/feature/feed/build.gradle.kts b/feature/feed/build.gradle.kts deleted file mode 100644 index 03fd62d3e..000000000 --- a/feature/feed/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.feed" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":feature:feed-impl")) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/feed/src/main/AndroidManifest.xml b/feature/feed/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/feed/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/search/.gitignore b/feature/gallery-impl-kmp/.gitignore similarity index 100% rename from feature/search/.gitignore rename to feature/gallery-impl-kmp/.gitignore diff --git a/feature/gallery-impl-kmp/build.gradle.kts b/feature/gallery-impl-kmp/build.gradle.kts new file mode 100644 index 000000000..e63face77 --- /dev/null +++ b/feature/gallery-impl-kmp/build.gradle.kts @@ -0,0 +1,70 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + api(project(":core:navigation-kmp")) + api(project(":core:ui-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:network-kmp")) + implementation(project(":core:work-kmp")) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + } +} + +android { + namespace = "org.michaelbel.movies.gallery_impl_kmp" + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt similarity index 78% rename from feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt rename to feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt index 1370536de..2dadc3a49 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt @@ -8,8 +8,6 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkInfo import androidx.work.WorkManager -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -19,14 +17,13 @@ import kotlinx.coroutines.launch import org.michaelbel.movies.common.ktx.require import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.gallery.ktx.nameRes -import org.michaelbel.movies.gallery_impl.R +import org.michaelbel.movies.gallery_impl_kmp.R import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.persistence.database.entity.ImageDb +import org.michaelbel.movies.persistence.database.entity.ImagePojo import org.michaelbel.movies.persistence.database.ktx.original import org.michaelbel.movies.work.DownloadImageWorker -@HiltViewModel -class GalleryViewModel @Inject constructor( +class GalleryViewModel( savedStateHandle: SavedStateHandle, private val interactor: Interactor, private val workManager: WorkManager @@ -34,7 +31,7 @@ class GalleryViewModel @Inject constructor( private val movieId: String = savedStateHandle.require("movieId") - val movieImagesFlow: StateFlow> = interactor.imagesFlow(movieId.toInt()) + val movieImagesFlow: StateFlow> = interactor.imagesFlow(movieId.toInt()) .stateIn( scope = this, started = SharingStarted.Lazily, @@ -48,11 +45,11 @@ class GalleryViewModel @Inject constructor( loadMovieImages(movieId.toInt()) } - fun downloadImage(imageDb: ImageDb) = launch { + fun downloadImage(image: ImagePojo) = launch { val workData = Data.Builder() - .putString(DownloadImageWorker.KEY_IMAGE_URL, imageDb.original) + .putString(DownloadImageWorker.KEY_IMAGE_URL, image.original) .putInt(DownloadImageWorker.KEY_CONTENT_TITLE, R.string.gallery_downloading_image) - .putInt(DownloadImageWorker.KEY_CONTENT_TEXT, imageDb.type.nameRes) + .putInt(DownloadImageWorker.KEY_CONTENT_TEXT, image.type.nameRes) .build() val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) @@ -65,7 +62,7 @@ class GalleryViewModel @Inject constructor( .setInputData(workData) .build() workManager.run { - enqueueUniqueWork(imageDb.toString(), ExistingWorkPolicy.KEEP, downloadImageWorker) + enqueueUniqueWork(image.toString(), ExistingWorkPolicy.KEEP, downloadImageWorker) getWorkInfoByIdFlow(downloadImageWorker.id).collect { workInfo -> _workInfoFlow.emit(workInfo) } diff --git a/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/di/GalleryKoinModule.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/di/GalleryKoinModule.kt new file mode 100644 index 000000000..f260cc9b6 --- /dev/null +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/di/GalleryKoinModule.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.gallery.di + +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.michaelbel.movies.gallery.GalleryViewModel +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.work.di.workKoinModule + +val galleryKoinModule = module { + includes( + interactorKoinModule, + workKoinModule + ) + viewModel { GalleryViewModel(get(), get(), get()) } +} \ No newline at end of file diff --git a/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ktx/ImageTypeKtx.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ktx/ImageTypeKtx.kt new file mode 100644 index 000000000..c28060297 --- /dev/null +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ktx/ImageTypeKtx.kt @@ -0,0 +1,12 @@ +package org.michaelbel.movies.gallery.ktx + +import androidx.annotation.StringRes +import org.michaelbel.movies.gallery_impl_kmp.R +import org.michaelbel.movies.persistence.database.entity.ImageType + +internal val ImageType.nameRes: Int + @StringRes get() = when (this) { + ImageType.POSTER -> R.string.gallery_poster + ImageType.BACKDROP -> R.string.gallery_backdrop + ImageType.LOGO -> R.string.gallery_logo + } \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryLoading.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryLoading.kt similarity index 97% rename from feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryLoading.kt rename to feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryLoading.kt index 6417ae441..8307e7cc3 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryLoading.kt +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryLoading.kt @@ -13,7 +13,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun GalleryLoading( +internal fun GalleryLoading( modifier: Modifier = Modifier ) { Box( diff --git a/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryRoute.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryRoute.kt new file mode 100644 index 000000000..468ccb31b --- /dev/null +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryRoute.kt @@ -0,0 +1,27 @@ +package org.michaelbel.movies.gallery.ui + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.gallery.GalleryViewModel + +@Composable +fun GalleryRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, + viewModel: GalleryViewModel = koinViewModel() +) { + val movieImages by viewModel.movieImagesFlow.collectAsStateWithLifecycle() + val workInfo by viewModel.workInfoFlow.collectAsStateWithLifecycle() + + GalleryScreenContent( + movieImages = movieImages, + workInfo = workInfo, + onBackClick = onBackClick, + onDownloadClick = viewModel::downloadImage, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt similarity index 88% rename from feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt rename to feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt index a1a126bfc..75a91a892 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalFoundationApi::class) + package org.michaelbel.movies.gallery.ui import android.content.Intent @@ -7,6 +9,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding @@ -46,20 +49,20 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.core.net.toUri -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.work.WorkInfo import coil.compose.AsyncImage import coil.compose.AsyncImagePainter import coil.request.ImageRequest import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.gallery.GalleryViewModel import org.michaelbel.movies.gallery.zoomable.rememberZoomState import org.michaelbel.movies.gallery.zoomable.zoomable -import org.michaelbel.movies.gallery_impl.R -import org.michaelbel.movies.network.isNotOriginal -import org.michaelbel.movies.persistence.database.entity.ImageDb +import org.michaelbel.movies.gallery_impl_kmp.R +import org.michaelbel.movies.network.config.isNotOriginal +import org.michaelbel.movies.persistence.database.entity.ImagePojo import org.michaelbel.movies.persistence.database.ktx.original import org.michaelbel.movies.ui.accessibility.MoviesContentDescription import org.michaelbel.movies.ui.compose.iconbutton.BackIcon @@ -70,38 +73,18 @@ import org.michaelbel.movies.ui.theme.MoviesTheme import org.michaelbel.movies.work.DownloadImageWorker @Composable -fun GalleryRoute( - onBackClick: () -> Unit, - modifier: Modifier = Modifier, - viewModel: GalleryViewModel = hiltViewModel() -) { - val movieImages by viewModel.movieImagesFlow.collectAsStateWithLifecycle() - val workInfo by viewModel.workInfoFlow.collectAsStateWithLifecycle() - - GalleryScreenContent( - movieImages = movieImages, - workInfo = workInfo, - onBackClick = onBackClick, - onDownloadClick = viewModel::downloadImage, - modifier = modifier - ) -} - -@Composable -private fun GalleryScreenContent( - movieImages: List, +internal fun GalleryScreenContent( + movieImages: List, workInfo: WorkInfo?, onBackClick: () -> Unit, - onDownloadClick: (ImageDb) -> Unit, + onDownloadClick: (ImagePojo) -> Unit, modifier: Modifier = Modifier ) { val context = LocalContext.current val hapticFeedback = LocalHapticFeedback.current val coroutineScope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } - val resultContract = rememberLauncherForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) {} + val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} val pagerState = rememberPagerState( initialPage = 0, @@ -191,10 +174,10 @@ private fun GalleryScreenContent( bottom.linkTo(parent.bottom) } ) { page -> - val imageDb: ImageDb = movieImages[page] + val imageDb = movieImages[page] var imageDiskCacheKey: String? by remember { mutableStateOf(null) } - var image: String by remember { mutableStateOf("") } + var image by remember { mutableStateOf("") } image = imageDb.original var loading: Boolean by remember { mutableStateOf(true) } @@ -287,9 +270,7 @@ private fun GalleryScreenContent( .statusBarsPadding(), overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Start, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) AnimatedVisibility( @@ -317,25 +298,6 @@ private fun GalleryScreenContent( } } -@Composable -private fun LoopHorizontalPager( - pagerState: PagerState, - modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), - content: @Composable PagerScope.(page: Int) -> Unit, -) { - HorizontalPager( - state = pagerState, - modifier = modifier, - contentPadding = contentPadding, - pageSpacing = 8.dp, - flingBehavior = PagerDefaults.flingBehavior(state = pagerState), - pageContent = { index -> - content(index) - } - ) -} - @Composable @DevicePreviews private fun GalleryScreenContentPreview() { diff --git a/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/LoopHorizontalPager.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/LoopHorizontalPager.kt new file mode 100644 index 000000000..3a4512424 --- /dev/null +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/ui/LoopHorizontalPager.kt @@ -0,0 +1,32 @@ +@file:OptIn(ExperimentalFoundationApi::class) + +package org.michaelbel.movies.gallery.ui + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerDefaults +import androidx.compose.foundation.pager.PagerScope +import androidx.compose.foundation.pager.PagerState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +internal fun LoopHorizontalPager( + pagerState: PagerState, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), + content: @Composable PagerScope.(page: Int) -> Unit, +) { + HorizontalPager( + state = pagerState, + modifier = modifier, + contentPadding = contentPadding, + pageSpacing = 8.dp, + flingBehavior = PagerDefaults.flingBehavior(state = pagerState), + pageContent = { index -> + content(index) + } + ) +} \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt similarity index 99% rename from feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt rename to feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt index 580a8393f..21e229303 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt @@ -423,10 +423,10 @@ class ZoomState( * @param velocityDecay The decay animation spec for fling behaviour. */ @Composable -fun rememberZoomState( - @FloatRange(from = 1.0) maxScale: Float = 5f, +internal fun rememberZoomState( + @FloatRange(from = 1.0) maxScale: Float = 5F, contentSize: Size = Size.Zero, - velocityDecay: DecayAnimationSpec = exponentialDecay(), + velocityDecay: DecayAnimationSpec = exponentialDecay() ) = remember { ZoomState(maxScale, contentSize, velocityDecay) } \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/Zoomable.kt b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/zoomable/Zoomable.kt similarity index 99% rename from feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/Zoomable.kt rename to feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/zoomable/Zoomable.kt index 1f658d6dd..34d7f40ba 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/Zoomable.kt +++ b/feature/gallery-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/zoomable/Zoomable.kt @@ -40,8 +40,8 @@ import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.unit.toSize import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastForEach -import kotlin.math.abs import kotlinx.coroutines.launch +import kotlin.math.abs /** * Customized transform gesture detector. @@ -234,7 +234,7 @@ fun Modifier.zoomable( zoomState: ZoomState, enableOneFingerZoom: Boolean = true, onTap: (position: Offset) -> Unit = {}, - onDoubleTap: suspend (position: Offset) -> Unit = { position -> zoomState.toggleScale(2.5f, position) }, + onDoubleTap: suspend (position: Offset) -> Unit = { position -> zoomState.toggleScale(2.5F, position) } ): Modifier = composed( inspectorInfo = debugInspectorInfo { name = "zoomable" @@ -292,7 +292,7 @@ fun Modifier.zoomable( * @param position Zoom around this point. * @param animationSpec The animation configuration. */ -suspend fun ZoomState.toggleScale( +internal suspend fun ZoomState.toggleScale( targetScale: Float, position: Offset, animationSpec: AnimationSpec = spring(), diff --git a/feature/gallery-impl/src/main/res/values-ru/strings.xml b/feature/gallery-impl-kmp/src/androidMain/res/values-ru/strings.xml similarity index 100% rename from feature/gallery-impl/src/main/res/values-ru/strings.xml rename to feature/gallery-impl-kmp/src/androidMain/res/values-ru/strings.xml diff --git a/feature/gallery-impl/src/main/res/values/strings.xml b/feature/gallery-impl-kmp/src/androidMain/res/values/strings.xml similarity index 100% rename from feature/gallery-impl/src/main/res/values/strings.xml rename to feature/gallery-impl-kmp/src/androidMain/res/values/strings.xml diff --git a/feature/gallery-impl/build.gradle.kts b/feature/gallery-impl/build.gradle.kts deleted file mode 100644 index ea599bbb1..000000000 --- a/feature/gallery-impl/build.gradle.kts +++ /dev/null @@ -1,69 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.gallery_impl" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi" - ) - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(project(":core:navigation")) - api(project(":core:ui")) - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:network")) - implementation(project(":core:work")) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/gallery-impl/src/main/AndroidManifest.xml b/feature/gallery-impl/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/gallery-impl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ktx/ImageTypeKtx.kt b/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ktx/ImageTypeKtx.kt deleted file mode 100644 index 52aed62de..000000000 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ktx/ImageTypeKtx.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.michaelbel.movies.gallery.ktx - -import androidx.annotation.StringRes -import org.michaelbel.movies.gallery_impl.R -import org.michaelbel.movies.persistence.database.entity.ImageDb - -internal val ImageDb.Type.nameRes: Int - @StringRes get() = when (this) { - ImageDb.Type.POSTER -> R.string.gallery_poster - ImageDb.Type.BACKDROP -> R.string.gallery_backdrop - ImageDb.Type.LOGO -> R.string.gallery_logo - } \ No newline at end of file diff --git a/feature/settings-impl/.gitignore b/feature/gallery-kmp/.gitignore similarity index 100% rename from feature/settings-impl/.gitignore rename to feature/gallery-kmp/.gitignore diff --git a/feature/gallery-kmp/build.gradle.kts b/feature/gallery-kmp/build.gradle.kts new file mode 100644 index 000000000..2a74c717b --- /dev/null +++ b/feature/gallery-kmp/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:navigation-kmp")) + api(project(":feature:gallery-impl-kmp")) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.material) + implementation(compose.material3) + implementation(libs.precompose) + } + } +} + +android { + namespace = "org.michaelbel.movies.gallery_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/gallery/src/main/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt b/feature/gallery-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt similarity index 100% rename from feature/gallery/src/main/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt rename to feature/gallery-kmp/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt diff --git a/feature/gallery/src/main/kotlin/org/michaelbel/movies/gallery/GalleryDestination.kt b/feature/gallery-kmp/src/commonMain/kotlin/org/michaelbel/movies/gallery/GalleryDestination.kt similarity index 76% rename from feature/gallery/src/main/kotlin/org/michaelbel/movies/gallery/GalleryDestination.kt rename to feature/gallery-kmp/src/commonMain/kotlin/org/michaelbel/movies/gallery/GalleryDestination.kt index ff0506efa..c0c8c127d 100644 --- a/feature/gallery/src/main/kotlin/org/michaelbel/movies/gallery/GalleryDestination.kt +++ b/feature/gallery-kmp/src/commonMain/kotlin/org/michaelbel/movies/gallery/GalleryDestination.kt @@ -2,7 +2,7 @@ package org.michaelbel.movies.gallery import org.michaelbel.movies.navigation.MoviesNavigationDestination -object GalleryDestination: MoviesNavigationDestination { +internal object GalleryDestination: MoviesNavigationDestination { override val route: String = "gallery/{movieId}" diff --git a/feature/gallery-kmp/src/desktopMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt b/feature/gallery-kmp/src/desktopMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt new file mode 100644 index 000000000..ef2a3c053 --- /dev/null +++ b/feature/gallery-kmp/src/desktopMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt @@ -0,0 +1,19 @@ +package org.michaelbel.movies.gallery + +import androidx.compose.material.Text +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.RouteBuilder + +fun Navigator.navigateToGallery(movieId: Int) { + navigate("gallery/$movieId") +} + +fun RouteBuilder.galleryGraph( + navigateBack: () -> Unit, +) { + scene( + route = GalleryDestination.route + ) { + Text("gallery") + } +} \ No newline at end of file diff --git a/feature/gallery/build.gradle.kts b/feature/gallery/build.gradle.kts deleted file mode 100644 index 9496764a9..000000000 --- a/feature/gallery/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.gallery" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":feature:gallery-impl")) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/gallery/src/main/AndroidManifest.xml b/feature/gallery/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/gallery/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/settings/.gitignore b/feature/search-impl-kmp/.gitignore similarity index 100% rename from feature/settings/.gitignore rename to feature/search-impl-kmp/.gitignore diff --git a/feature/search-impl-kmp/build.gradle.kts b/feature/search-impl-kmp/build.gradle.kts new file mode 100644 index 000000000..2283b588d --- /dev/null +++ b/feature/search-impl-kmp/build.gradle.kts @@ -0,0 +1,71 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + api(project(":core:navigation-kmp")) + api(project(":core:ui-kmp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:interactor-kmp")) + implementation(project(":core:network-kmp")) + implementation(project(":core:notifications-kmp")) + implementation(libs.bundles.paging.common) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + } +} + +android { + namespace = "org.michaelbel.movies.search_impl_kmp" + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt new file mode 100644 index 000000000..4ecc7da34 --- /dev/null +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt @@ -0,0 +1,96 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package org.michaelbel.movies.search + +import androidx.paging.PagingData +import androidx.paging.cachedIn +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.viewmodel.BaseViewModel +import org.michaelbel.movies.interactor.Interactor +import org.michaelbel.movies.network.connectivity.NetworkManager +import org.michaelbel.movies.network.connectivity.NetworkStatus +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo + +class SearchViewModel( + private val interactor: Interactor, + networkManager: NetworkManager +): BaseViewModel() { + + val networkStatus: StateFlow = networkManager.status + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = NetworkStatus.Unavailable + ) + + val currentFeedView: StateFlow = interactor.currentFeedView + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = runBlocking { interactor.currentFeedView.first() } + ) + + val suggestionsFlow: StateFlow> = interactor.suggestions() + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = emptyList() + ) + + val searchHistoryMoviesFlow: StateFlow> = interactor.moviesFlow(MoviePojo.MOVIES_SEARCH_HISTORY, Int.MAX_VALUE) + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = emptyList() + ) + + private val _query: MutableStateFlow = MutableStateFlow("") + private val query: StateFlow = _query.asStateFlow() + + val isSearchActive: StateFlow = interactor.isSearchActive + + val pagingDataFlow: Flow> = query + .flatMapLatest { query -> interactor.moviesPagingData(query) } + .cachedIn(this) + + init { + loadSuggestions() + } + + fun onChangeSearchQuery(query: String) { + _query.value = query + } + + fun onChangeActiveState(state: Boolean) { + interactor.setSearchActive(state) + } + + fun onSaveToHistory(movieId: Int) = launch { + val movie: MoviePojo = interactor.movie(query.value, movieId) + interactor.insertMovie(MoviePojo.MOVIES_SEARCH_HISTORY, movie) + } + + fun onRemoveFromHistory(movieId: Int) = launch { + interactor.removeMovie(MoviePojo.MOVIES_SEARCH_HISTORY, movieId) + } + + fun onClearSearchHistory() = launch { + interactor.removeMovies(MoviePojo.MOVIES_SEARCH_HISTORY) + } + + private fun loadSuggestions() = launch { + interactor.updateSuggestions() + } +} \ No newline at end of file diff --git a/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/di/SearchKoinModule.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/di/SearchKoinModule.kt new file mode 100644 index 000000000..2fe5984f5 --- /dev/null +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/di/SearchKoinModule.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.search.di + +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.dsl.module +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.network.connectivity.di.networkManagerKoinModule +import org.michaelbel.movies.search.SearchViewModel + +val searchKoinModule = module { + includes( + interactorKoinModule, + networkManagerKoinModule + ) + viewModelOf(::SearchViewModel) +} \ No newline at end of file diff --git a/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchEmpty.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchEmpty.kt new file mode 100644 index 000000000..4c2788c3d --- /dev/null +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchEmpty.kt @@ -0,0 +1,80 @@ +package org.michaelbel.movies.search.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.search_impl_kmp.R +import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun SearchEmpty( + modifier: Modifier = Modifier +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + imageVector = MoviesIcons.ManageSearch, + contentDescription = MoviesContentDescription.None, + modifier = Modifier.size(36.dp), + tint = MaterialTheme.colorScheme.error + ) + + Text( + text = stringResource(R.string.search_results_empty), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 16.dp, top = 8.dp, end = 16.dp), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + } +} + +@Composable +@DevicePreviews +private fun SearchEmptyPreview() { + MoviesTheme { + SearchEmpty( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} + +@Composable +@Preview +private fun SearchEmptyAmoledPreview() { + MoviesTheme( + theme = AppTheme.Amoled + ) { + SearchEmpty( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} \ No newline at end of file diff --git a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchHistoryHeader.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchHistoryHeader.kt similarity index 92% rename from feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchHistoryHeader.kt rename to feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchHistoryHeader.kt index 118274a15..aac4974ff 100644 --- a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchHistoryHeader.kt +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchHistoryHeader.kt @@ -15,12 +15,12 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.search_impl.R +import org.michaelbel.movies.search_impl_kmp.R import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun SearchHistoryHeader( +internal fun SearchHistoryHeader( onClearButtonClick: () -> Unit, modifier: Modifier = Modifier ) { @@ -39,9 +39,7 @@ fun SearchHistoryHeader( bottom.linkTo(parent.bottom) }, textAlign = TextAlign.Start, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) TextButton( diff --git "a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchRe\321\201entResult.kt" "b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchRe\321\201entResult.kt" similarity index 93% rename from "feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchRe\321\201entResult.kt" rename to "feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchRe\321\201entResult.kt" index 6c60b41d3..d0ce27f93 100644 --- "a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchRe\321\201entResult.kt" +++ "b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchRe\321\201entResult.kt" @@ -17,7 +17,7 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.entity.MoviePojo import org.michaelbel.movies.ui.accessibility.MoviesContentDescription import org.michaelbel.movies.ui.compose.iconbutton.CloseIcon import org.michaelbel.movies.ui.icons.MoviesIcons @@ -26,7 +26,7 @@ import org.michaelbel.movies.ui.preview.provider.MoviePreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun SearchRecentResult( +internal fun SearchRecentResult( text: String, onRemoveClick: () -> Unit, modifier: Modifier = Modifier @@ -62,9 +62,7 @@ fun SearchRecentResult( textAlign = TextAlign.Start, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) CloseIcon( @@ -83,7 +81,7 @@ fun SearchRecentResult( @Composable @DevicePreviews private fun SearchRecentResultPreview( - @PreviewParameter(MoviePreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MoviePreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme { SearchRecentResult( @@ -100,7 +98,7 @@ private fun SearchRecentResultPreview( @Composable @Preview private fun SearchRecentResultAmoledPreview( - @PreviewParameter(MoviePreviewParameterProvider::class) movie: MovieDb + @PreviewParameter(MoviePreviewParameterProvider::class) movie: MoviePojo ) { MoviesTheme( theme = AppTheme.Amoled diff --git a/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchRoute.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchRoute.kt new file mode 100644 index 000000000..9c09d6a09 --- /dev/null +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchRoute.kt @@ -0,0 +1,41 @@ +package org.michaelbel.movies.search.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.compose.collectAsLazyPagingItems +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.search.SearchViewModel + +@Composable +fun SearchRoute( + onBackClick: () -> Unit, + onNavigateToDetails: (String, Int) -> Unit, + modifier: Modifier = Modifier, + viewModel: SearchViewModel = koinViewModel() +) { + val pagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems() + val currentFeedView by viewModel.currentFeedView.collectAsStateWithLifecycle() + val networkStatus by viewModel.networkStatus.collectAsStateWithLifecycle() + val suggestions by viewModel.suggestionsFlow.collectAsStateWithLifecycle() + val searchHistoryMovies by viewModel.searchHistoryMoviesFlow.collectAsStateWithLifecycle() + val active by viewModel.isSearchActive.collectAsStateWithLifecycle() + + SearchScreenContent( + pagingItems = pagingItems, + networkStatus = networkStatus, + currentFeedView = currentFeedView, + suggestions = suggestions, + searchHistoryMovies = searchHistoryMovies, + onBackClick = onBackClick, + onNavigateToDetails = onNavigateToDetails, + onChangeSearchQuery = viewModel::onChangeSearchQuery, + onSaveMovieToHistory = viewModel::onSaveToHistory, + onRemoveMovieFromHistory = viewModel::onRemoveFromHistory, + onHistoryClear = viewModel::onClearSearchHistory, + active = active, + onChangeActiveState = viewModel::onChangeActiveState, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt new file mode 100644 index 000000000..6f2a01c5a --- /dev/null +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt @@ -0,0 +1,184 @@ +package org.michaelbel.movies.search.ui + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.paging.compose.LazyPagingItems +import kotlinx.coroutines.launch +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException +import org.michaelbel.movies.common.exceptions.PageEmptyException +import org.michaelbel.movies.network.connectivity.NetworkStatus +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo +import org.michaelbel.movies.ui.compose.page.PageContent +import org.michaelbel.movies.ui.compose.page.PageFailure +import org.michaelbel.movies.ui.compose.page.PageLoading +import org.michaelbel.movies.ui.ktx.clickableWithoutRipple +import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets +import org.michaelbel.movies.ui.ktx.isFailure +import org.michaelbel.movies.ui.ktx.isLoading +import org.michaelbel.movies.ui.ktx.refreshThrowable +import java.net.UnknownHostException +import org.michaelbel.movies.ui_kmp.R as UiR + +@Composable +internal fun SearchScreenContent( + pagingItems: LazyPagingItems, + networkStatus: NetworkStatus, + currentFeedView: FeedView, + suggestions: List, + searchHistoryMovies: List, + onBackClick: () -> Unit, + onNavigateToDetails: (String, Int) -> Unit, + onChangeSearchQuery: (String) -> Unit, + onSaveMovieToHistory: (Int) -> Unit, + onRemoveMovieFromHistory: (Int) -> Unit, + onHistoryClear: () -> Unit, + active: Boolean, + onChangeActiveState: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + val scope = rememberCoroutineScope() + val lazyListState = rememberLazyListState() + val lazyGridState = rememberLazyGridState() + val lazyStaggeredGridState = rememberLazyStaggeredGridState() + val snackbarHostState = remember { SnackbarHostState() } + val focusRequester = remember { FocusRequester() } + + val onShowSnackbar: (String) -> Unit = { message -> + scope.launch { + snackbarHostState.showSnackbar( + message = message, + duration = SnackbarDuration.Long + ) + } + } + + if (pagingItems.isFailure && pagingItems.refreshThrowable is ApiKeyNotNullException) { + onShowSnackbar(stringResource(UiR.string.error_api_key_null)) + } + + if (networkStatus == NetworkStatus.Available && pagingItems.isFailure && pagingItems.refreshThrowable is UnknownHostException) { + pagingItems.retry() + } + + var query by rememberSaveable { mutableStateOf("") } + + val searchBarHorizontalPadding: Dp by animateDpAsState( + targetValue = if (active) 0.dp else 8.dp, + label = "" + ) + + Scaffold( + modifier = modifier, + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { innerPadding -> + Column { + SearchToolbar( + query = query, + onQueryChange = { text -> + query = text + }, + onSearch = { + onChangeSearchQuery(query) + onChangeActiveState(false) + }, + active = active, + onActiveChange = { state -> + onChangeActiveState(state) + }, + onBackClick = onBackClick, + onCloseClick = { + onChangeActiveState(query.isNotEmpty()) + query = "" + focusRequester.requestFocus() + }, + onInputText = { text -> + query = text + onChangeSearchQuery(text) + onChangeActiveState(query.isEmpty()) + }, + suggestions = suggestions, + searchHistoryMovies = searchHistoryMovies, + onHistoryMovieRemoveClick = onRemoveMovieFromHistory, + onClearHistoryClick = onHistoryClear, + modifier = Modifier + .padding(horizontal = searchBarHorizontalPadding) + .windowInsetsPadding(displayCutoutWindowInsets) + .fillMaxWidth() + .focusRequester(focusRequester) + ) + + when { + pagingItems.isLoading -> { + PageLoading( + feedView = currentFeedView, + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) + ) + } + pagingItems.isFailure -> { + if (pagingItems.refreshThrowable is PageEmptyException) { + SearchEmpty( + modifier = Modifier + .windowInsetsPadding(displayCutoutWindowInsets) + .fillMaxSize() + ) + } else { + PageFailure( + modifier = Modifier + .padding(innerPadding) + .windowInsetsPadding(displayCutoutWindowInsets) + .fillMaxSize() + .clickableWithoutRipple(pagingItems::retry) + ) + } + } + else -> { + PageContent( + feedView = currentFeedView, + lazyListState = lazyListState, + lazyGridState = lazyGridState, + lazyStaggeredGridState = lazyStaggeredGridState, + pagingItems = pagingItems, + onMovieClick = { movieList, movieId -> + onSaveMovieToHistory(movieId) + onNavigateToDetails(movieList, movieId) + }, + contentPadding = PaddingValues(bottom = innerPadding.calculateBottomPadding()), + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) + ) + } + } + } + } + + LaunchedEffect(focusRequester) { + focusRequester.requestFocus() + } +} \ No newline at end of file diff --git a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchSuggestion.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchSuggestion.kt similarity index 95% rename from feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchSuggestion.kt rename to feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchSuggestion.kt index 68ccb213d..525e2a54a 100644 --- a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchSuggestion.kt +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchSuggestion.kt @@ -16,13 +16,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.persistence.database.entity.SuggestionDb +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.preview.provider.SuggestionDbPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun SearchSuggestion( +internal fun SearchSuggestion( text: String, modifier: Modifier = Modifier ) { @@ -43,7 +43,7 @@ fun SearchSuggestion( @Composable @DevicePreviews private fun SearchSuggestionPreview( - @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List + @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List ) { MoviesTheme { SearchSuggestion( @@ -59,7 +59,7 @@ private fun SearchSuggestionPreview( @Composable @Preview private fun SearchSuggestionAmoledPreview( - @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List + @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List ) { MoviesTheme( theme = AppTheme.Amoled diff --git a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt similarity index 78% rename from feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt rename to feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt index d53c6603b..beb6e4a24 100644 --- a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt +++ b/feature/search-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package org.michaelbel.movies.search.ui import androidx.compose.foundation.clickable @@ -9,8 +11,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Divider -import androidx.compose.material3.Icon +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults @@ -23,19 +24,18 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.entity.SuggestionDb -import org.michaelbel.movies.search_impl.R +import org.michaelbel.movies.persistence.database.entity.MoviePojo +import org.michaelbel.movies.persistence.database.entity.SuggestionPojo +import org.michaelbel.movies.search_impl_kmp.R import org.michaelbel.movies.ui.compose.iconbutton.BackIcon import org.michaelbel.movies.ui.compose.iconbutton.CloseIcon import org.michaelbel.movies.ui.compose.iconbutton.VoiceIcon -import org.michaelbel.movies.ui.icons.MoviesIcons import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.preview.provider.SuggestionDbPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun SearchToolbar( +internal fun SearchToolbar( query: String, onQueryChange: (String) -> Unit, onSearch: (String) -> Unit, @@ -44,10 +44,8 @@ fun SearchToolbar( onBackClick: () -> Unit, onCloseClick: () -> Unit, onInputText: (String) -> Unit, - suggestions: List, - onSuggestionClick: (SuggestionDb) -> Unit, - searchHistoryMovies: List, - onHistoryMovieClick: (String) -> Unit, + suggestions: List, + searchHistoryMovies: List, onHistoryMovieRemoveClick: (Int) -> Unit, onClearHistoryClick: () -> Unit, modifier: Modifier = Modifier @@ -65,19 +63,12 @@ fun SearchToolbar( ) }, leadingIcon = { - if (active) { - Icon( - imageVector = MoviesIcons.Search, - contentDescription = null - ) - } else { - BackIcon( - onClick = onBackClick - ) - } + BackIcon( + onClick = onBackClick + ) }, trailingIcon = { - if (active) { + if (query.isNotEmpty()) { CloseIcon( onClick = onCloseClick ) @@ -88,22 +79,22 @@ fun SearchToolbar( } }, colors = SearchBarDefaults.colors( - containerColor = MaterialTheme.colorScheme.inversePrimary + containerColor = MaterialTheme.colorScheme.inversePrimary, + dividerColor = MaterialTheme.colorScheme.onPrimaryContainer ) ) { - Divider( - thickness = .1.dp, - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - when { searchHistoryMovies.isNotEmpty() -> { Column( - modifier = Modifier.fillMaxSize().imePadding() + modifier = Modifier + .fillMaxSize() + .imePadding() ) { SearchHistoryHeader( onClearButtonClick = onClearHistoryClick, - modifier = Modifier.fillMaxWidth().height(48.dp) + modifier = Modifier + .fillMaxWidth() + .height(48.dp) ) LazyColumn { @@ -114,7 +105,7 @@ fun SearchToolbar( modifier = Modifier .fillMaxWidth() .height(52.dp) - .clickable { onHistoryMovieClick(movie.title) } + .clickable { onInputText(movie.title) } ) } } @@ -122,7 +113,9 @@ fun SearchToolbar( } suggestions.isNotEmpty() -> { Box( - modifier = Modifier.fillMaxSize().imePadding(), + modifier = Modifier + .fillMaxSize() + .imePadding(), contentAlignment = Alignment.Center ) { LazyColumn { @@ -132,12 +125,22 @@ fun SearchToolbar( modifier = Modifier .fillMaxWidth() .height(52.dp) - .clickable { onSuggestionClick(suggestion) } + .clickable { onInputText(suggestion.title) } ) } } } } + else -> { + Box( + modifier = Modifier + .fillMaxSize() + .imePadding(), + contentAlignment = Alignment.Center + ) { + SearchEmpty() + } + } } } } @@ -145,7 +148,7 @@ fun SearchToolbar( @Composable @DevicePreviews private fun SearchToolbarPreview( - @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List + @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List ) { MoviesTheme { SearchToolbar( @@ -158,9 +161,7 @@ private fun SearchToolbarPreview( onCloseClick = {}, onInputText = {}, suggestions = suggestions, - onSuggestionClick = {}, searchHistoryMovies = emptyList(), - onHistoryMovieClick = {}, onHistoryMovieRemoveClick = {}, onClearHistoryClick = {} ) @@ -170,7 +171,7 @@ private fun SearchToolbarPreview( @Composable @Preview private fun SearchToolbarAmoledPreview( - @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List + @PreviewParameter(SuggestionDbPreviewParameterProvider::class) suggestions: List ) { MoviesTheme( theme = AppTheme.Amoled @@ -185,9 +186,7 @@ private fun SearchToolbarAmoledPreview( onCloseClick = {}, onInputText = {}, suggestions = suggestions, - onSuggestionClick = {}, searchHistoryMovies = emptyList(), - onHistoryMovieClick = {}, onHistoryMovieRemoveClick = {}, onClearHistoryClick = {} ) diff --git a/feature/search-impl/src/main/res/values-ru/strings.xml b/feature/search-impl-kmp/src/androidMain/res/values-ru/strings.xml similarity index 100% rename from feature/search-impl/src/main/res/values-ru/strings.xml rename to feature/search-impl-kmp/src/androidMain/res/values-ru/strings.xml diff --git a/feature/search-impl/src/main/res/values/strings.xml b/feature/search-impl-kmp/src/androidMain/res/values/strings.xml similarity index 100% rename from feature/search-impl/src/main/res/values/strings.xml rename to feature/search-impl-kmp/src/androidMain/res/values/strings.xml diff --git a/feature/search-impl/build.gradle.kts b/feature/search-impl/build.gradle.kts deleted file mode 100644 index 1c8baae08..000000000 --- a/feature/search-impl/build.gradle.kts +++ /dev/null @@ -1,70 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.search_impl" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" - ) - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - api(project(":core:navigation")) - api(project(":core:ui")) - implementation(project(":core:common")) - implementation(project(":core:interactor")) - implementation(project(":core:network")) - implementation(project(":core:notifications")) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/search-impl/src/main/AndroidManifest.xml b/feature/search-impl/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/search-impl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/SearchViewModel.kt b/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/SearchViewModel.kt deleted file mode 100644 index 75f75e0ee..000000000 --- a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/SearchViewModel.kt +++ /dev/null @@ -1,97 +0,0 @@ -package org.michaelbel.movies.search - -import androidx.paging.PagingData -import androidx.paging.cachedIn -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.network.connectivity.NetworkManager -import org.michaelbel.movies.network.connectivity.NetworkStatus -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.entity.SuggestionDb - -@HiltViewModel -class SearchViewModel @Inject constructor( - private val interactor: Interactor, - networkManager: NetworkManager, -): BaseViewModel() { - - val networkStatus: StateFlow = networkManager.status - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = NetworkStatus.Unavailable - ) - - val currentFeedView: StateFlow = interactor.currentFeedView - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = runBlocking { interactor.currentFeedView.first() } - ) - - val suggestionsFlow: StateFlow> = interactor.suggestions() - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = emptyList() - ) - - val searchHistoryMoviesFlow: StateFlow> = interactor.moviesFlow(MovieDb.MOVIES_SEARCH_HISTORY, Int.MAX_VALUE) - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = emptyList() - ) - - private val _query: MutableStateFlow = MutableStateFlow("") - private val query: StateFlow = _query.asStateFlow() - - private val _active: MutableStateFlow = MutableStateFlow(true) - val active: StateFlow = _active.asStateFlow() - - val pagingDataFlow: Flow> = query - .flatMapLatest { query -> interactor.moviesPagingData(query) } - .cachedIn(this) - - init { - loadSuggestions() - } - - fun onChangeSearchQuery(query: String) { - _query.value = query - } - - fun onChangeActiveState(state: Boolean) { - _active.value = state - } - - fun onSaveToHistory(movieId: Int) = launch { - val movie: MovieDb = interactor.movie(query.value, movieId) - interactor.insertMovie(MovieDb.MOVIES_SEARCH_HISTORY, movie) - } - - fun onRemoveFromHistory(movieId: Int) = launch { - interactor.removeMovie(MovieDb.MOVIES_SEARCH_HISTORY, movieId) - } - - fun onClearSearchHistory() = launch { - interactor.removeMovies(MovieDb.MOVIES_SEARCH_HISTORY) - } - - private fun loadSuggestions() = launch { - interactor.updateSuggestions() - } -} \ No newline at end of file diff --git a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchEmpty.kt b/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchEmpty.kt deleted file mode 100644 index 1e9da42f1..000000000 --- a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchEmpty.kt +++ /dev/null @@ -1,87 +0,0 @@ -package org.michaelbel.movies.search.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.search_impl.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SearchEmpty( - modifier: Modifier = Modifier -) { - ConstraintLayout( - modifier = modifier - ) { - val (image, text) = createRefs() - - Icon( - imageVector = MoviesIcons.ManageSearch, - contentDescription = MoviesContentDescription.None, - modifier = Modifier.constrainAs(image) { - width = Dimension.value(36.dp) - height = Dimension.value(36.dp) - start.linkTo(parent.start) - top.linkTo(parent.top) - end.linkTo(parent.end) - bottom.linkTo(parent.bottom, 8.dp) - }, - tint = MaterialTheme.colorScheme.error - ) - - Text( - text = stringResource(R.string.search_results_empty), - modifier = Modifier.constrainAs(text) { - width = Dimension.fillToConstraints - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(image.bottom, 8.dp) - end.linkTo(parent.end, 16.dp) - }, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } -} - -@Composable -@DevicePreviews -private fun SearchEmptyPreview() { - MoviesTheme { - SearchEmpty( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SearchEmptyAmoledPreview() { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SearchEmpty( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt b/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt deleted file mode 100644 index 209f2a31d..000000000 --- a/feature/search-impl/src/main/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt +++ /dev/null @@ -1,226 +0,0 @@ -package org.michaelbel.movies.search.ui - -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.collectAsLazyPagingItems -import java.net.UnknownHostException -import kotlinx.coroutines.launch -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException -import org.michaelbel.movies.common.exceptions.PageEmptyException -import org.michaelbel.movies.network.connectivity.NetworkStatus -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.persistence.database.entity.SuggestionDb -import org.michaelbel.movies.search.SearchViewModel -import org.michaelbel.movies.ui.compose.page.PageContent -import org.michaelbel.movies.ui.compose.page.PageFailure -import org.michaelbel.movies.ui.compose.page.PageLoading -import org.michaelbel.movies.ui.ktx.clickableWithoutRipple -import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets -import org.michaelbel.movies.ui.ktx.isFailure -import org.michaelbel.movies.ui.ktx.isLoading -import org.michaelbel.movies.ui.ktx.refreshThrowable -import org.michaelbel.movies.ui.R as UiR - -@Composable -fun SearchRoute( - onBackClick: () -> Unit, - onNavigateToDetails: (String, Int) -> Unit, - modifier: Modifier = Modifier, - viewModel: SearchViewModel = hiltViewModel() -) { - val pagingItems: LazyPagingItems = viewModel.pagingDataFlow.collectAsLazyPagingItems() - val currentFeedView: FeedView by viewModel.currentFeedView.collectAsStateWithLifecycle() - val networkStatus: NetworkStatus by viewModel.networkStatus.collectAsStateWithLifecycle() - val suggestions: List by viewModel.suggestionsFlow.collectAsStateWithLifecycle() - val searchHistoryMovies: List by viewModel.searchHistoryMoviesFlow.collectAsStateWithLifecycle() - val active: Boolean by viewModel.active.collectAsStateWithLifecycle() - - SearchScreenContent( - pagingItems = pagingItems, - networkStatus = networkStatus, - currentFeedView = currentFeedView, - suggestions = suggestions, - searchHistoryMovies = searchHistoryMovies, - onBackClick = onBackClick, - onNavigateToDetails = onNavigateToDetails, - onChangeSearchQuery = viewModel::onChangeSearchQuery, - onSaveMovieToHistory = viewModel::onSaveToHistory, - onRemoveMovieFromHistory = viewModel::onRemoveFromHistory, - onHistoryClear = viewModel::onClearSearchHistory, - active = active, - onChangeActiveState = viewModel::onChangeActiveState, - modifier = modifier - ) -} - -@Composable -private fun SearchScreenContent( - pagingItems: LazyPagingItems, - networkStatus: NetworkStatus, - currentFeedView: FeedView, - suggestions: List, - searchHistoryMovies: List, - onBackClick: () -> Unit, - onNavigateToDetails: (String, Int) -> Unit, - onChangeSearchQuery: (String) -> Unit, - onSaveMovieToHistory: (Int) -> Unit, - onRemoveMovieFromHistory: (Int) -> Unit, - onHistoryClear: () -> Unit, - active: Boolean, - onChangeActiveState: (Boolean) -> Unit, - modifier: Modifier = Modifier -) { - val scope = rememberCoroutineScope() - val lazyListState = rememberLazyListState() - val lazyGridState = rememberLazyGridState() - val lazyStaggeredGridState = rememberLazyStaggeredGridState() - val snackbarHostState = remember { SnackbarHostState() } - val focusRequester = remember { FocusRequester() } - - val onShowSnackbar: (String) -> Unit = { message -> - scope.launch { - snackbarHostState.showSnackbar( - message = message, - duration = SnackbarDuration.Long - ) - } - } - - if (pagingItems.isFailure && pagingItems.refreshThrowable is ApiKeyNotNullException) { - onShowSnackbar(stringResource(UiR.string.error_api_key_null)) - } - - if (networkStatus == NetworkStatus.Available && pagingItems.isFailure && pagingItems.refreshThrowable is UnknownHostException) { - pagingItems.retry() - } - - var query: String by rememberSaveable { mutableStateOf("") } - - val searchBarHorizontalPadding: Dp by animateDpAsState( - targetValue = if (active) 0.dp else 8.dp, - label = "" - ) - - Scaffold( - modifier = modifier, - containerColor = MaterialTheme.colorScheme.primaryContainer - ) { innerPadding -> - Column { - SearchToolbar( - query = query, - onQueryChange = { text -> - query = text - }, - onSearch = { - onChangeSearchQuery(query) - onChangeActiveState(false) - }, - active = active, - onActiveChange = { state -> - onChangeActiveState(state) - }, - onBackClick = onBackClick, - onCloseClick = { - onChangeActiveState(query.isNotEmpty()) - query = "" - }, - onInputText = { text -> - query = text - onChangeSearchQuery(text) - }, - suggestions = suggestions, - onSuggestionClick = { suggestion -> - query = suggestion.title - onChangeSearchQuery(suggestion.title) - onChangeActiveState(false) - }, - searchHistoryMovies = searchHistoryMovies, - onHistoryMovieClick = { title -> - query = title - onChangeSearchQuery(title) - onChangeActiveState(false) - }, - onHistoryMovieRemoveClick = onRemoveMovieFromHistory, - onClearHistoryClick = onHistoryClear, - modifier = Modifier - .padding(horizontal = searchBarHorizontalPadding) - .windowInsetsPadding(displayCutoutWindowInsets) - .fillMaxWidth() - .focusRequester(focusRequester) - ) - - when { - pagingItems.isLoading -> { - PageLoading( - feedView = currentFeedView, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) - ) - } - pagingItems.isFailure -> { - if (pagingItems.refreshThrowable is PageEmptyException) { - SearchEmpty( - modifier = Modifier - .windowInsetsPadding(displayCutoutWindowInsets) - .fillMaxSize() - ) - } else { - PageFailure( - modifier = Modifier - .padding(innerPadding) - .windowInsetsPadding(displayCutoutWindowInsets) - .fillMaxSize() - .clickableWithoutRipple(pagingItems::retry) - ) - } - } - else -> { - PageContent( - feedView = currentFeedView, - lazyListState = lazyListState, - lazyGridState = lazyGridState, - lazyStaggeredGridState = lazyStaggeredGridState, - pagingItems = pagingItems, - onMovieClick = { movieList, movieId -> - onSaveMovieToHistory(movieId) - onNavigateToDetails(movieList, movieId) - }, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) - ) - } - } - } - } - - LaunchedEffect(focusRequester) { - focusRequester.requestFocus() - } -} \ No newline at end of file diff --git a/feature/search-kmp/.gitignore b/feature/search-kmp/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/feature/search-kmp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/search-kmp/build.gradle.kts b/feature/search-kmp/build.gradle.kts new file mode 100644 index 000000000..80e54252c --- /dev/null +++ b/feature/search-kmp/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:navigation-kmp")) + api(project(":feature:search-impl-kmp")) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.material) + implementation(compose.material3) + implementation(libs.precompose) + } + } +} + +android { + namespace = "org.michaelbel.movies.search_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/search-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt b/feature/search-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt new file mode 100644 index 000000000..9a08b4721 --- /dev/null +++ b/feature/search-kmp/src/androidMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt @@ -0,0 +1,29 @@ +package org.michaelbel.movies.search + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import androidx.navigation.navDeepLink +import org.michaelbel.movies.search.ui.SearchRoute +import org.michaelbel.movies.ui.shortcuts.INTENT_ACTION_SEARCH + +fun NavController.navigateToSearch() { + navigate(SearchDestination.route) +} + +fun NavGraphBuilder.searchGraph( + navigateBack: () -> Unit, + navigateToDetails: (String, Int) -> Unit, +) { + composable( + route = SearchDestination.route, + deepLinks = listOf( + navDeepLink { uriPattern = INTENT_ACTION_SEARCH } + ) + ) { + SearchRoute( + onBackClick = navigateBack, + onNavigateToDetails = navigateToDetails + ) + } +} \ No newline at end of file diff --git a/feature/search/src/main/kotlin/org/michaelbel/movies/search/SearchDestination.kt b/feature/search-kmp/src/commonMain/kotlin/org/michaelbel/movies/search/SearchDestination.kt similarity index 75% rename from feature/search/src/main/kotlin/org/michaelbel/movies/search/SearchDestination.kt rename to feature/search-kmp/src/commonMain/kotlin/org/michaelbel/movies/search/SearchDestination.kt index 1db0b3a26..cd3daec79 100644 --- a/feature/search/src/main/kotlin/org/michaelbel/movies/search/SearchDestination.kt +++ b/feature/search-kmp/src/commonMain/kotlin/org/michaelbel/movies/search/SearchDestination.kt @@ -2,7 +2,7 @@ package org.michaelbel.movies.search import org.michaelbel.movies.navigation.MoviesNavigationDestination -object SearchDestination: MoviesNavigationDestination { +internal object SearchDestination: MoviesNavigationDestination { override val route: String = "search" diff --git a/feature/search-kmp/src/desktopMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt b/feature/search-kmp/src/desktopMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt new file mode 100644 index 000000000..86aae134d --- /dev/null +++ b/feature/search-kmp/src/desktopMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt @@ -0,0 +1,20 @@ +package org.michaelbel.movies.search + +import androidx.compose.material.Text +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.RouteBuilder + +fun Navigator.navigateToSearch() { + navigate(SearchDestination.route) +} + +fun RouteBuilder.searchGraph( + navigateBack: () -> Unit, + navigateToDetails: (String, Int) -> Unit, +) { + scene( + route = SearchDestination.route + ) { + Text("Feed") + } +} \ No newline at end of file diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts deleted file mode 100644 index bbbc8d6ef..000000000 --- a/feature/search/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.search" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":feature:search-impl")) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/search/src/main/AndroidManifest.xml b/feature/search/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/search/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/search/src/main/kotlin/org/michaelbel/movies/search/SearchNavigation.kt b/feature/search/src/main/kotlin/org/michaelbel/movies/search/SearchNavigation.kt deleted file mode 100644 index cfc299b82..000000000 --- a/feature/search/src/main/kotlin/org/michaelbel/movies/search/SearchNavigation.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.michaelbel.movies.search - -import androidx.navigation.NavController -import androidx.navigation.NavDeepLink -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import androidx.navigation.navDeepLink -import org.michaelbel.movies.search.ui.SearchRoute -import org.michaelbel.movies.ui.shortcuts.INTENT_ACTION_SEARCH - -fun NavController.navigateToSearch() { - navigate(SearchDestination.route) -} - -fun NavGraphBuilder.searchGraph( - navigateBack: () -> Unit, - navigateToDetails: (String, Int) -> Unit, -) { - composable( - route = SearchDestination.route, - deepLinks = listOf( - navDeepLink { uriPattern = INTENT_ACTION_SEARCH } - ) - ) { - SearchRoute( - onBackClick = navigateBack, - onNavigateToDetails = navigateToDetails - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl-kmp/.gitignore b/feature/settings-impl-kmp/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/feature/settings-impl-kmp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/settings-impl-kmp/build.gradle.kts b/feature/settings-impl-kmp/build.gradle.kts new file mode 100644 index 000000000..1106208e0 --- /dev/null +++ b/feature/settings-impl-kmp/build.gradle.kts @@ -0,0 +1,77 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + jvm("desktop") { + compilations.all { + kotlinOptions { + jvmTarget = rootProject.extra.get("jvmTarget") as String + } + } + } + + sourceSets { + commonMain.dependencies { + implementation(project(":core:interactor-kmp")) + implementation(project(":core:navigation-kmp")) + implementation(project(":core:ui-kmp")) + implementation(project(":core:widget-kmp")) + implementation(project(":core:common-kmp")) + implementation(compose.components.resources) + implementation(compose.foundation) + implementation(compose.material3) + implementation(libs.bundles.constraintlayout.common) + implementation(libs.bundles.koin.common) + } + androidMain.dependencies { + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.desktop.currentOs) + } + } +} + +android { + namespace = "org.michaelbel.movies.settings_impl_kmp" + sourceSets["main"].res.srcDirs("src/androidMain/res") + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + buildConfig = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + targetCompatibility = JavaVersion.toVersion(rootProject.extra.get("jvmTarget") as String) + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt new file mode 100644 index 000000000..83df47e1e --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt @@ -0,0 +1,139 @@ +package org.michaelbel.movies.settings + +import android.app.Activity +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.DefaultLifecycleObserver +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.biometric.BiometricController +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.localization.LocaleController +import org.michaelbel.movies.common.localization.model.AppLanguage +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.common.version.AppVersionData +import org.michaelbel.movies.common.viewmodel.BaseViewModel +import org.michaelbel.movies.interactor.Interactor +import org.michaelbel.movies.platform.Flavor +import org.michaelbel.movies.platform.app.AppService +import org.michaelbel.movies.platform.review.ReviewService +import org.michaelbel.movies.platform.update.UpdateListener +import org.michaelbel.movies.platform.update.UpdateService +import org.michaelbel.movies.settings_impl_kmp.BuildConfig + +class SettingsViewModel( + biometricController: BiometricController, + private val interactor: Interactor, + private val localeController: LocaleController, + private val reviewService: ReviewService, + private val updateService: UpdateService, + appService: AppService +): BaseViewModel(), DefaultLifecycleObserver { + + val isReviewFeatureEnabled = appService.flavor == Flavor.Gms + + val isUpdateFeatureEnabled = appService.flavor == Flavor.Gms + + val themeData: StateFlow = interactor.themeData + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = ThemeData.Default + ) + + val currentFeedView: StateFlow = interactor.currentFeedView + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = FeedView.FeedList + ) + + val currentMovieList: StateFlow = interactor.currentMovieList + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = MovieList.NowPlaying() + ) + + val isBiometricFeatureEnabled: StateFlow = biometricController.isBiometricAvailable + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = false + ) + + val isBiometricEnabled: StateFlow = interactor.isBiometricEnabled + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = false + ) + + val appVersionData: StateFlow = flowOf(AppVersionData(appService.flavor.name)) + .stateIn( + scope = this, + started = SharingStarted.Lazily, + initialValue = AppVersionData.Empty + ) + + var isUpdateAvailable by mutableStateOf(false) + + init { + fetchUpdateAvailable() + } + + fun selectLanguage(language: AppLanguage) = launch { + localeController.selectLanguage(language) + } + + fun selectTheme(theme: AppTheme) = launch { + interactor.selectTheme(theme) + } + + fun selectFeedView(feedView: FeedView) = launch { + interactor.selectFeedView(feedView) + } + + fun selectMovieList(movieList: MovieList) = launch { + interactor.selectMovieList(movieList) + } + + fun setDynamicColors(value: Boolean) = launch { + interactor.setDynamicColors(value) + } + + fun setPaletteKey(paletteKey: Int) = launch { + interactor.setPaletteKey(paletteKey) + } + + fun setSeedColor(seedColor: Int) = launch { + interactor.setSeedColor(seedColor) + } + + fun setBiometricEnabled(enabled: Boolean) = launch { + interactor.setBiometricEnabled(enabled) + } + + fun requestReview(activity: Activity) { + reviewService.requestReview(activity) + } + + fun requestUpdate(activity: Activity) { + updateService.startUpdate(activity) + } + + private fun fetchUpdateAvailable() { + isUpdateAvailable = BuildConfig.DEBUG + updateService.setUpdateAvailableListener(object: UpdateListener { + override fun onAvailable(result: Boolean) { + isUpdateAvailable = result + } + }) + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.kt b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.kt new file mode 100644 index 000000000..061745916 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.kt @@ -0,0 +1,19 @@ +package org.michaelbel.movies.settings.di + +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.di.biometricKoinModule +import org.michaelbel.movies.common.localization.di.localeKoinModule +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.network.connectivity.di.networkManagerKoinModule +import org.michaelbel.movies.settings.SettingsViewModel + +val settingsKoinModule = module { + includes( + biometricKoinModule, + interactorKoinModule, + localeKoinModule, + networkManagerKoinModule + ) + viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get()) } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/AppVersionDataKtx.kt b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/AppVersionDataKtx.kt new file mode 100644 index 000000000..13d5f9e81 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/AppVersionDataKtx.kt @@ -0,0 +1,27 @@ +@file:Suppress("DEPRECATION") + +package org.michaelbel.movies.settings.ktx + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build +import org.michaelbel.movies.settings_impl_kmp.BuildConfig + +private val Context.packageInfo: PackageInfo + get() { + return if (Build.VERSION.SDK_INT >= 33) { + packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0L)) + } else { + packageManager.getPackageInfo(packageName, 0) + } + } + +internal val Context.versionName: String + get() = packageInfo.versionName + +internal val Context.versionCode: Long + get() = if (Build.VERSION.SDK_INT >= 28) packageInfo.longVersionCode else packageInfo.versionCode.toLong() + +internal val isDebug: Boolean + get() = BuildConfig.DEBUG \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt new file mode 100644 index 000000000..a332da122 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.settings.ktx + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import org.michaelbel.movies.settings_impl_kmp.R + +internal actual val SettingsGenderText: String + @Composable get() = stringResource(R.string.settings_gender) \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/model/Features.kt b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/model/Features.kt new file mode 100644 index 000000000..05035c9d3 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/model/Features.kt @@ -0,0 +1,49 @@ +package org.michaelbel.movies.settings.model + +import android.os.Build +import com.google.android.material.color.DynamicColors + +internal actual val isLanguageFeatureEnabled: Boolean + get() = true + +internal actual val isThemeFeatureEnabled: Boolean + get() = true + +internal actual val isFeedViewFeatureEnabled: Boolean + get() = true + +internal actual val isMovieListFeatureEnabled: Boolean + get() = true + +internal actual val isGenderFeatureEnabled: Boolean + get() = Build.VERSION.SDK_INT >= 34 + +internal actual val isDynamicColorsFeatureEnabled: Boolean + get() = DynamicColors.isDynamicColorAvailable() + +internal actual val isNotificationsFeatureEnabled: Boolean + get() = Build.VERSION.SDK_INT >= 33 + +internal actual val isBiometricFeatureEnabled: Boolean + get() = true + +internal actual val isWidgetFeatureEnabled: Boolean + get() = true + +internal actual val isTileFeatureEnabled: Boolean + get() = Build.VERSION.SDK_INT >= 24 + +internal actual val isAppIconFeatureEnabled: Boolean + get() = true + +internal actual val isGithubFeatureEnabled: Boolean + get() = true + +internal actual val isReviewAppFeatureEnabled: Boolean + get() = true + +internal actual val isUpdateAppFeatureEnabled: Boolean + get() = true + +internal actual val isAboutFeatureEnabled: Boolean + get() = true \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.kt b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.kt new file mode 100644 index 000000000..1e8f46244 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.kt @@ -0,0 +1,274 @@ +@file:OptIn(ExperimentalResourceApi::class, ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ui + +import android.Manifest +import android.app.Activity +import android.app.GrammaticalInflectionManager +import android.app.StatusBarManager +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.graphics.drawable.Icon +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.common.browser.openUrl +import org.michaelbel.movies.common.gender.GrammaticalGender +import org.michaelbel.movies.common.ktx.notificationManager +import org.michaelbel.movies.common.localization.model.AppLanguage +import org.michaelbel.movies.settings.SettingsViewModel +import org.michaelbel.movies.settings.ktx.iconSnackbarTextRes +import org.michaelbel.movies.settings.ktx.isDebug +import org.michaelbel.movies.settings.ktx.versionCode +import org.michaelbel.movies.settings.ktx.versionName +import org.michaelbel.movies.settings.model.SettingsData +import org.michaelbel.movies.settings.model.isAboutFeatureEnabled +import org.michaelbel.movies.settings.model.isAppIconFeatureEnabled +import org.michaelbel.movies.settings.model.isBiometricFeatureEnabled +import org.michaelbel.movies.settings.model.isDynamicColorsFeatureEnabled +import org.michaelbel.movies.settings.model.isFeedViewFeatureEnabled +import org.michaelbel.movies.settings.model.isGenderFeatureEnabled +import org.michaelbel.movies.settings.model.isGithubFeatureEnabled +import org.michaelbel.movies.settings.model.isLanguageFeatureEnabled +import org.michaelbel.movies.settings.model.isMovieListFeatureEnabled +import org.michaelbel.movies.settings.model.isNotificationsFeatureEnabled +import org.michaelbel.movies.settings.model.isReviewAppFeatureEnabled +import org.michaelbel.movies.settings.model.isThemeFeatureEnabled +import org.michaelbel.movies.settings.model.isTileFeatureEnabled +import org.michaelbel.movies.settings.model.isUpdateAppFeatureEnabled +import org.michaelbel.movies.settings.model.isWidgetFeatureEnabled +import org.michaelbel.movies.ui.appicon.IconAlias +import org.michaelbel.movies.ui.appicon.enabledIcon +import org.michaelbel.movies.ui.appicon.setIcon +import org.michaelbel.movies.ui.icons.MoviesAndroidIcons +import org.michaelbel.movies.ui.ktx.appNotificationSettingsIntent +import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets +import org.michaelbel.movies.ui.lifecycle.OnResume +import org.michaelbel.movies.ui.strings.MoviesStrings +import org.michaelbel.movies.ui.tile.MoviesTileService +import org.michaelbel.movies.widget.ktx.pin + +@Composable +fun SettingsRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, + viewModel: SettingsViewModel = koinViewModel() +) { + val currentLanguage = AppLanguage.transform(stringResource(MoviesStrings.language_code)) + val themeData by viewModel.themeData.collectAsStateWithLifecycle() + val currentFeedView by viewModel.currentFeedView.collectAsStateWithLifecycle() + val currentMovieList by viewModel.currentMovieList.collectAsStateWithLifecycle() + val isBiometricFeatureAvailable by viewModel.isBiometricFeatureEnabled.collectAsStateWithLifecycle() + val isBiometricEnabled by viewModel.isBiometricEnabled.collectAsStateWithLifecycle() + val appVersionData by viewModel.appVersionData.collectAsStateWithLifecycle() + + val context = LocalContext.current + val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + val toolbarColor = MaterialTheme.colorScheme.primary.toArgb() + val tileTitleLabel = stringResource(MoviesStrings.tile_title) + val tileMessage = stringResource(MoviesStrings.settings_tile_error_already_added) + + val appWidgetManager by remember { mutableStateOf(AppWidgetManager.getInstance(context)) } + val appWidgetProvider by remember { mutableStateOf(appWidgetManager.getInstalledProvidersForPackage(context.packageName, null).first()) } + + val notificationManager by remember { mutableStateOf(context.notificationManager) } + var areNotificationsEnabled by remember { mutableStateOf(notificationManager.areNotificationsEnabled()) } + + val scope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } + val permissionMessage = stringResource(MoviesStrings.settings_post_notifications_should_request) + val permissionAction = stringResource(MoviesStrings.settings_action_go) + val onShowPermissionSnackbar: () -> Unit = { + scope.launch { + val result = snackbarHostState.showSnackbar( + message = permissionMessage, + actionLabel = permissionAction, + duration = SnackbarDuration.Long + ) + if (result == SnackbarResult.ActionPerformed) { + resultContract.launch(context.appNotificationSettingsIntent) + } + } + } + + val postNotificationsPermission = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { granted -> + if (granted) { + areNotificationsEnabled = notificationManager.areNotificationsEnabled() + } else { + val shouldRequest = (context as Activity).shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) + if (!shouldRequest) { + onShowPermissionSnackbar() + } + } + } + + OnResume { + areNotificationsEnabled = notificationManager.areNotificationsEnabled() + } + + val grammaticalInflectionManager by remember { mutableStateOf(context.getSystemService(GrammaticalInflectionManager::class.java)) } + val grammaticalGender by remember { mutableStateOf(grammaticalInflectionManager.applicationGrammaticalGender) } + val currentGrammaticalGender by remember { mutableStateOf(GrammaticalGender.transform(grammaticalGender)) } + + val onShowSnackbar: (String) -> Unit = { message -> + scope.launch { + snackbarHostState.showSnackbar( + message = message, + duration = SnackbarDuration.Short + ) + } + } + + val messageRed = stringResource(MoviesStrings.settings_app_launcher_icon_changed_to, stringResource(IconAlias.Red.iconSnackbarTextRes)) + val messagePurple = stringResource(MoviesStrings.settings_app_launcher_icon_changed_to, stringResource(IconAlias.Purple.iconSnackbarTextRes)) + val messageBrown = stringResource(MoviesStrings.settings_app_launcher_icon_changed_to, stringResource(IconAlias.Brown.iconSnackbarTextRes)) + val messageAmoled = stringResource(MoviesStrings.settings_app_launcher_icon_changed_to, stringResource(IconAlias.Amoled.iconSnackbarTextRes)) + + SettingsScreenContent( + settingsData = SettingsData( + onBackClick = onBackClick, + languageData = SettingsData.ListData( + isFeatureEnabled = isLanguageFeatureEnabled, + current = currentLanguage, + onSelect = viewModel::selectLanguage + ), + themeData = SettingsData.ListData( + isFeatureEnabled = isThemeFeatureEnabled, + current = themeData.appTheme, + onSelect = viewModel::selectTheme + ), + feedViewData = SettingsData.ListData( + isFeatureEnabled = isFeedViewFeatureEnabled, + current = currentFeedView, + onSelect = viewModel::selectFeedView + ), + movieListData = SettingsData.ListData( + isFeatureEnabled = isMovieListFeatureEnabled, + current = currentMovieList, + onSelect = viewModel::selectMovieList + ), + genderData = SettingsData.ListData( + isFeatureEnabled = isGenderFeatureEnabled, + current = currentGrammaticalGender, + onSelect = { gender -> + grammaticalInflectionManager.setRequestedApplicationGrammaticalGender(GrammaticalGender.value(gender)) + } + ), + dynamicColorsData = SettingsData.DynamicColorsData( + isFeatureEnabled = isDynamicColorsFeatureEnabled, + isEnabled = themeData.dynamicColors, + onChange = viewModel::setDynamicColors + ), + paletteColorsData = SettingsData.PaletteColorsData( + isFeatureEnabled = isDynamicColorsFeatureEnabled, + isDynamicColorsEnabled = themeData.dynamicColors, + paletteKey = themeData.paletteKey, + seedColor = themeData.seedColor, + onChange = { localDynamicColors, localPaletteKey, localSeedColor -> + viewModel.run { + setDynamicColors(localDynamicColors) + setPaletteKey(localPaletteKey) + setSeedColor(localSeedColor) + } + } + ), + notificationsData = SettingsData.NotificationsData( + isFeatureEnabled = isNotificationsFeatureEnabled, + isEnabled = areNotificationsEnabled, + onClick = { + if (areNotificationsEnabled) { + resultContract.launch(context.appNotificationSettingsIntent) + } else { + postNotificationsPermission.launch(Manifest.permission.POST_NOTIFICATIONS) + } + }, + onNavigateToAppNotificationSettings = { + resultContract.launch(context.appNotificationSettingsIntent) + } + ), + biometricData = SettingsData.BiometricData( + isFeatureEnabled = isBiometricFeatureEnabled && isBiometricFeatureAvailable, + isEnabled = isBiometricEnabled, + onChange = viewModel::setBiometricEnabled + ), + widgetData = SettingsData.WidgetData( + isFeatureEnabled = isWidgetFeatureEnabled, + onRequest = { appWidgetProvider.pin(context) } + ), + tileData = SettingsData.TileData( + isFeatureEnabled = isTileFeatureEnabled, + onRequest = { + val statusBarManager = ContextCompat.getSystemService(context, StatusBarManager::class.java) + statusBarManager?.requestAddTileService( + ComponentName(context, MoviesTileService::class.java), + tileTitleLabel, + Icon.createWithResource(context, MoviesAndroidIcons.MovieFilter24), + context.mainExecutor + ) { result -> + when (result) { + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED -> { + onShowSnackbar(tileMessage) + } + } + } + } + ), + appIconData = SettingsData.ListData( + isFeatureEnabled = isAppIconFeatureEnabled, + current = context.enabledIcon, + onSelect = { icon -> + val message = when (icon) { + IconAlias.Red -> messageRed + IconAlias.Purple -> messagePurple + IconAlias.Brown -> messageBrown + IconAlias.Amoled -> messageAmoled + } + onShowSnackbar(message) + context.setIcon(icon) + } + ), + githubData = SettingsData.GithubData( + isFeatureEnabled = isGithubFeatureEnabled, + onClick = { url -> openUrl(resultContract, toolbarColor, url) } + ), + reviewAppData = SettingsData.ReviewAppData( + isFeatureEnabled = isReviewAppFeatureEnabled && viewModel.isReviewFeatureEnabled, + onRequest = { viewModel.requestReview(context as Activity) } + ), + updateAppData = SettingsData.UpdateAppData( + isFeatureEnabled = isUpdateAppFeatureEnabled && viewModel.isUpdateFeatureEnabled && viewModel.isUpdateAvailable, + onRequest = { viewModel.requestUpdate(context as Activity) } + ), + aboutData = SettingsData.AboutData( + isFeatureEnabled = isAboutFeatureEnabled, + versionName = context.versionName, + versionCode = context.versionCode, + flavor = appVersionData.flavor, + isDebug = isDebug + ) + ), + windowInsets = displayCutoutWindowInsets, + snackbarHostState = snackbarHostState, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/res/values-ru-feminine/strings.xml b/feature/settings-impl-kmp/src/androidMain/res/values-ru-feminine/strings.xml new file mode 100644 index 000000000..6a9c12a52 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/res/values-ru-feminine/strings.xml @@ -0,0 +1,3 @@ + + Эй, девушка, выбери род + \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/res/values-ru-masculine/strings.xml b/feature/settings-impl-kmp/src/androidMain/res/values-ru-masculine/strings.xml new file mode 100644 index 000000000..f904e451e --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/res/values-ru-masculine/strings.xml @@ -0,0 +1,3 @@ + + Эй, мужик, выбери род + \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/res/values-ru-neuter/strings.xml b/feature/settings-impl-kmp/src/androidMain/res/values-ru-neuter/strings.xml new file mode 100644 index 000000000..7e0a51d74 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/res/values-ru-neuter/strings.xml @@ -0,0 +1,3 @@ + + Эй, чел, выбери род + \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/res/values-ru/strings.xml b/feature/settings-impl-kmp/src/androidMain/res/values-ru/strings.xml new file mode 100644 index 000000000..bda9c1fbc --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/res/values-ru/strings.xml @@ -0,0 +1,3 @@ + + Род + \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/androidMain/res/values/strings.xml b/feature/settings-impl-kmp/src/androidMain/res/values/strings.xml new file mode 100644 index 000000000..8f84274e7 --- /dev/null +++ b/feature/settings-impl-kmp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + Gender + \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt new file mode 100644 index 000000000..67e68b5f1 --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt @@ -0,0 +1,3 @@ +package org.michaelbel.movies.settings.ktx + +internal expect val SettingsGenderText: String \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/IconAliasKtx.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/IconAliasKtx.kt new file mode 100644 index 000000000..60372f4cd --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/IconAliasKtx.kt @@ -0,0 +1,16 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ktx + +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.StringResource +import org.michaelbel.movies.ui.appicon.IconAlias +import org.michaelbel.movies.ui.strings.MoviesStrings + +internal val IconAlias.iconSnackbarTextRes: StringResource + get() = when (this) { + is IconAlias.Red -> MoviesStrings.settings_app_launcher_icon_red + is IconAlias.Purple -> MoviesStrings.settings_app_launcher_icon_purple + is IconAlias.Brown -> MoviesStrings.settings_app_launcher_icon_brown + is IconAlias.Amoled -> MoviesStrings.settings_app_launcher_icon_amoled + } \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/SealedStringKtx.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/SealedStringKtx.kt new file mode 100644 index 000000000..fcde2a9f2 --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ktx/SealedStringKtx.kt @@ -0,0 +1,40 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ktx + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.SealedString +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.gender.GrammaticalGender +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.localization.model.AppLanguage +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.strings.MoviesStrings + +internal val SealedString.stringText: String + @Composable get() = when (this) { + is AppLanguage.English -> stringResource(MoviesStrings.settings_language_en) + is AppLanguage.Russian -> stringResource(MoviesStrings.settings_language_ru) + + is AppTheme.NightNo -> stringResource(MoviesStrings.settings_theme_light) + is AppTheme.NightYes -> stringResource(MoviesStrings.settings_theme_dark) + is AppTheme.FollowSystem -> stringResource(MoviesStrings.settings_theme_system) + is AppTheme.Amoled -> stringResource(MoviesStrings.settings_theme_amoled) + + is FeedView.FeedList -> stringResource(MoviesStrings.settings_appearance_list) + is FeedView.FeedGrid -> stringResource(MoviesStrings.settings_appearance_grid) + + is GrammaticalGender.NotSpecified -> stringResource(MoviesStrings.settings_gender_not_specified) + is GrammaticalGender.Neutral -> stringResource(MoviesStrings.settings_gender_neutral) + is GrammaticalGender.Feminine -> stringResource(MoviesStrings.settings_gender_feminine) + is GrammaticalGender.Masculine -> stringResource(MoviesStrings.settings_gender_masculine) + + is MovieList.NowPlaying -> stringResource(MoviesStrings.settings_movie_list_now_playing) + is MovieList.Popular -> stringResource(MoviesStrings.settings_movie_list_popular) + is MovieList.TopRated -> stringResource(MoviesStrings.settings_movie_list_top_rated) + is MovieList.Upcoming -> stringResource(MoviesStrings.settings_movie_list_upcoming) + + else -> "" + } \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/model/Features.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/model/Features.kt new file mode 100644 index 000000000..5ec71fb6d --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/model/Features.kt @@ -0,0 +1,17 @@ +package org.michaelbel.movies.settings.model + +internal expect val isLanguageFeatureEnabled: Boolean +internal expect val isThemeFeatureEnabled: Boolean +internal expect val isFeedViewFeatureEnabled: Boolean +internal expect val isMovieListFeatureEnabled: Boolean +internal expect val isGenderFeatureEnabled: Boolean +internal expect val isDynamicColorsFeatureEnabled: Boolean +internal expect val isNotificationsFeatureEnabled: Boolean +internal expect val isBiometricFeatureEnabled: Boolean +internal expect val isWidgetFeatureEnabled: Boolean +internal expect val isTileFeatureEnabled: Boolean +internal expect val isAppIconFeatureEnabled: Boolean +internal expect val isGithubFeatureEnabled: Boolean +internal expect val isReviewAppFeatureEnabled: Boolean +internal expect val isUpdateAppFeatureEnabled: Boolean +internal expect val isAboutFeatureEnabled: Boolean \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/model/SettingsData.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/model/SettingsData.kt new file mode 100644 index 000000000..f84b2e16f --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/model/SettingsData.kt @@ -0,0 +1,112 @@ +package org.michaelbel.movies.settings.model + +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.gender.GrammaticalGender +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.localization.model.AppLanguage +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.appicon.IconAlias + +data class SettingsData( + val onBackClick: () -> Unit, + val languageData: ListData, + val themeData: ListData, + val feedViewData: ListData, + val movieListData: ListData, + val genderData: ListData, + val dynamicColorsData: DynamicColorsData, + val paletteColorsData: PaletteColorsData, + val notificationsData: NotificationsData, + val biometricData: BiometricData, + val widgetData: WidgetData, + val tileData: TileData, + val appIconData: ListData, + val githubData: GithubData, + val reviewAppData: ReviewAppData, + val updateAppData: UpdateAppData, + val aboutData: AboutData +) { + interface Featured { + val isFeatureEnabled: Boolean + } + + interface Listed: Featured { + val current: T + val onSelect: (T) -> Unit + } + + interface Requested: Featured { + val onRequest: () -> Unit + } + + interface Changed: Featured { + val isEnabled: Boolean + val onChange: (Boolean) -> Unit + } + + data class ListData( + override val isFeatureEnabled: Boolean, + override val current: T, + override val onSelect: (T) -> Unit + ): Listed + + data class DynamicColorsData( + override val isFeatureEnabled: Boolean, + override val isEnabled: Boolean, + override val onChange: (Boolean) -> Unit + ): Changed + + data class PaletteColorsData( + override val isFeatureEnabled: Boolean, + val isDynamicColorsEnabled: Boolean, + val paletteKey: Int, + val seedColor: Int, + val onChange: (Boolean, Int, Int) -> Unit + ): Featured + + data class NotificationsData( + override val isFeatureEnabled: Boolean, + val isEnabled: Boolean, + val onClick: () -> Unit, + val onNavigateToAppNotificationSettings: () -> Unit + ): Featured + + data class BiometricData( + override val isFeatureEnabled: Boolean, + override val isEnabled: Boolean, + override val onChange: (Boolean) -> Unit + ): Changed + + data class WidgetData( + override val isFeatureEnabled: Boolean, + override val onRequest: () -> Unit + ): Requested + + data class TileData( + override val isFeatureEnabled: Boolean, + override val onRequest: () -> Unit + ): Requested + + data class GithubData( + override val isFeatureEnabled: Boolean, + val onClick: (String) -> Unit + ): Featured + + data class ReviewAppData( + override val isFeatureEnabled: Boolean, + override val onRequest: () -> Unit + ): Requested + + data class UpdateAppData( + override val isFeatureEnabled: Boolean, + override val onRequest: () -> Unit + ): Requested + + data class AboutData( + override val isFeatureEnabled: Boolean, + val versionName: String, + val versionCode: Long, + val flavor: String, + val isDebug: Boolean + ): Featured +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsPaletteColorsBox.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsPaletteColorsBox.kt new file mode 100644 index 000000000..eca04c25d --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsPaletteColorsBox.kt @@ -0,0 +1,92 @@ +@file:OptIn(ExperimentalFoundationApi::class) + +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.MaterialTheme +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.graphics.toArgb +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.ThemeData +import org.michaelbel.movies.settings.ui.common.SettingPaletteColor +import org.michaelbel.movies.ui.color.PaletteStyle +import org.michaelbel.movies.ui.color.TonalPalettes.Companion.toTonalPalettes +import org.michaelbel.movies.ui.pagerindicator.HorizontalPagerIndicator +import org.michaelbel.movies.ui.theme.colorList +import org.michaelbel.movies.ui.theme.paletteStyles + +@Composable +internal fun SettingsPaletteColorsBox( + isDynamicColorsEnabled: Boolean, + paletteKey: Int, + seedColor: Int, + onChange: (Boolean, Int, Int) -> Unit +) { + val pageCount = colorList.size + 1 + val pagerState = rememberPagerState( + initialPage = if (paletteKey == ThemeData.STYLE_MONOCHROME) pageCount else colorList.indexOf(Color(seedColor)).run { if (this == -1) 0 else this } + ) { pageCount } + + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + HorizontalPager( + modifier = Modifier.fillMaxWidth(), + state = pagerState, + contentPadding = PaddingValues(horizontal = 12.dp) + ) { page -> + if (page < pageCount - 1) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + paletteStyles.subList(ThemeData.STYLE_TONAL_SPOT, ThemeData.STYLE_MONOCHROME).forEachIndexed { index, style -> + val color = colorList[page] + val tonalPalettes by remember { mutableStateOf(color.toTonalPalettes(style)) } + SettingPaletteColor( + tonalPalettes = tonalPalettes, + isSelected = !isDynamicColorsEnabled && paletteKey == index && seedColor == color.toArgb(), + onClick = { onChange(false, index, color.toArgb()) } + ) + } + } + } else { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + SettingPaletteColor( + tonalPalettes = Color.Black.toTonalPalettes(PaletteStyle.Monochrome), + isSelected = !isDynamicColorsEnabled && paletteKey == ThemeData.STYLE_MONOCHROME, + onClick = { onChange(false, ThemeData.STYLE_MONOCHROME, Color.Black.toArgb()) } + ) + } + } + } + + HorizontalPagerIndicator( + pagerState = pagerState, + modifier = Modifier.padding(vertical = 12.dp), + activeColor = MaterialTheme.colorScheme.primary, + inactiveColor = if (isSystemInDarkTheme()) MaterialTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.outlineVariant, + indicatorWidth = 6.dp, + indicatorHeight = 6.dp, + spacing = 2.dp + ) + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt new file mode 100644 index 000000000..fc2c5f575 --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt @@ -0,0 +1,457 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.MOVIES_GITHUB_URL +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.gender.GrammaticalGender +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.localization.model.AppLanguage +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.settings.ktx.SettingsGenderText +import org.michaelbel.movies.settings.ktx.stringText +import org.michaelbel.movies.settings.model.SettingsData +import org.michaelbel.movies.settings.ui.common.SettingAppIcon +import org.michaelbel.movies.settings.ui.common.SettingItem +import org.michaelbel.movies.settings.ui.common.SettingSwitchItem +import org.michaelbel.movies.settings.ui.common.SettingsDialog +import org.michaelbel.movies.ui.appicon.IconAlias +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.strings.MoviesStrings + +@Composable +internal fun SettingsScreenContent( + settingsData: SettingsData, + windowInsets: WindowInsets, + snackbarHostState: SnackbarHostState, + modifier: Modifier = Modifier +) { + val scope = rememberCoroutineScope() + val topAppBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + state = rememberTopAppBarState(), + canScroll = { true } + ) + val lazyListState = rememberLazyListState() + val onScrollToTop: () -> Unit = { + scope.launch { lazyListState.animateScrollToItem(0) } + } + + Scaffold( + modifier = modifier + .fillMaxWidth() + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), + topBar = { + SettingsToolbar( + topAppBarScrollBehavior = topAppBarScrollBehavior, + onNavigationIconClick = settingsData.onBackClick, + onClick = onScrollToTop + ) + }, + bottomBar = { + if (settingsData.aboutData.isFeatureEnabled) { + SettingsVersionBox( + aboutData = settingsData.aboutData, + modifier = Modifier + .navigationBarsPadding() + .windowInsetsPadding(windowInsets) + ) + } + }, + snackbarHost = { + SnackbarHost( + hostState = snackbarHostState + ) + }, + containerColor = MaterialTheme.colorScheme.primaryContainer + ) { innerPadding -> + LazyColumn( + modifier = Modifier + .navigationBarsPadding() + .windowInsetsPadding(windowInsets), + state = lazyListState, + contentPadding = innerPadding + ) { + if (settingsData.languageData.isFeatureEnabled) { + item { + var languageDialog by remember { mutableStateOf(false) } + + if (languageDialog) { + SettingsDialog( + icon = MoviesIcons.Language, + title = stringResource(MoviesStrings.settings_language), + items = AppLanguage.VALUES, + currentItem = settingsData.languageData.current, + onItemSelect = settingsData.languageData.onSelect, + onDismissRequest = { languageDialog = false } + ) + } + + SettingItem( + title = stringResource(MoviesStrings.settings_language), + description = settingsData.languageData.current.stringText, + icon = MoviesIcons.Language, + onClick = { languageDialog = true } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.themeData.isFeatureEnabled) { + item { + var themeDialog by remember { mutableStateOf(false) } + + if (themeDialog) { + SettingsDialog( + icon = MoviesIcons.ThemeLightDark, + title = stringResource(MoviesStrings.settings_theme), + items = AppTheme.VALUES, + currentItem = settingsData.themeData.current, + onItemSelect = settingsData.themeData.onSelect, + onDismissRequest = { themeDialog = false } + ) + } + + SettingItem( + title = stringResource(MoviesStrings.settings_theme), + description = settingsData.themeData.current.stringText, + icon = MoviesIcons.ThemeLightDark, + onClick = { themeDialog = true } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.feedViewData.isFeatureEnabled) { + item { + var appearanceDialog by remember { mutableStateOf(false) } + + if (appearanceDialog) { + SettingsDialog( + icon = MoviesIcons.GridView, + title = stringResource(MoviesStrings.settings_appearance), + items = FeedView.VALUES, + currentItem = settingsData.feedViewData.current, + onItemSelect = settingsData.feedViewData.onSelect, + onDismissRequest = { appearanceDialog = false } + ) + } + + SettingItem( + title = stringResource(MoviesStrings.settings_appearance), + description = settingsData.feedViewData.current.stringText, + icon = MoviesIcons.GridView, + onClick = { appearanceDialog = true } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.movieListData.isFeatureEnabled) { + item { + var movieListDialog by remember { mutableStateOf(false) } + + if (movieListDialog) { + SettingsDialog( + icon = MoviesIcons.LocalMovies, + title = stringResource(MoviesStrings.settings_movie_list), + items = MovieList.VALUES, + currentItem = settingsData.movieListData.current, + onItemSelect = settingsData.movieListData.onSelect, + onDismissRequest = { movieListDialog = false } + ) + } + + SettingItem( + title = stringResource(MoviesStrings.settings_movie_list), + description = settingsData.movieListData.current.stringText, + icon = MoviesIcons.LocalMovies, + onClick = { movieListDialog = true } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.genderData.isFeatureEnabled) { + item { + var genderDialog by remember { mutableStateOf(false) } + if (genderDialog) { + SettingsDialog( + icon = MoviesIcons.Cat, + title = stringResource(MoviesStrings.settings_gender), + items = GrammaticalGender.VALUES, + currentItem = settingsData.genderData.current, + onItemSelect = settingsData.genderData.onSelect, + onDismissRequest = { genderDialog = false } + ) + } + + SettingItem( + title = SettingsGenderText, + description = settingsData.genderData.current.stringText, + icon = MoviesIcons.Cat, + onClick = { genderDialog = true } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.dynamicColorsData.isFeatureEnabled) { + item { + SettingSwitchItem( + title = stringResource(MoviesStrings.settings_dynamic_colors), + description = stringResource(MoviesStrings.settings_dynamic_colors_description), + icon = MoviesIcons.Palette, + checked = settingsData.dynamicColorsData.isEnabled, + onClick = { settingsData.dynamicColorsData.onChange(!settingsData.dynamicColorsData.isEnabled) } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.paletteColorsData.isFeatureEnabled) { + item { + Text( + text = stringResource(MoviesStrings.settings_palette_colors), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + } + item { + SettingsPaletteColorsBox( + isDynamicColorsEnabled = settingsData.paletteColorsData.isDynamicColorsEnabled, + paletteKey = settingsData.paletteColorsData.paletteKey, + seedColor = settingsData.paletteColorsData.seedColor, + onChange = settingsData.paletteColorsData.onChange + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.notificationsData.isFeatureEnabled) { + item { + SettingSwitchItem( + title = stringResource(MoviesStrings.settings_post_notifications), + description = stringResource(if (settingsData.notificationsData.isEnabled) MoviesStrings.settings_post_notifications_granted else MoviesStrings.settings_post_notifications_denied), + icon = MoviesIcons.Notifications, + checked = settingsData.notificationsData.isEnabled, + onClick = settingsData.notificationsData.onClick + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.biometricData.isFeatureEnabled) { + item { + SettingSwitchItem( + title = stringResource(MoviesStrings.settings_lock_app), + description = stringResource(if (settingsData.biometricData.isEnabled) MoviesStrings.settings_biometric_added else MoviesStrings.settings_biometric_not_added), + icon = MoviesIcons.Fingerprint, + checked = settingsData.biometricData.isEnabled, + onClick = { settingsData.biometricData.onChange(!settingsData.biometricData.isEnabled) } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.widgetData.isFeatureEnabled) { + item { + SettingItem( + title = stringResource(MoviesStrings.settings_app_widget), + description = stringResource(MoviesStrings.settings_app_widget_description, stringResource(MoviesStrings.appwidget_description)), + icon = MoviesIcons.Widgets, + onClick = settingsData.widgetData.onRequest + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.tileData.isFeatureEnabled) { + item { + SettingItem( + title = stringResource(MoviesStrings.settings_tile), + description = stringResource(MoviesStrings.settings_tile_description), + icon = MoviesIcons.ViewAgenda, + onClick = settingsData.tileData.onRequest + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.appIconData.isFeatureEnabled) { + item { + Text( + text = stringResource(MoviesStrings.settings_app_launcher_icon), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + } + item { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + SettingAppIcon( + iconAlias = IconAlias.Red, + isEnabled = settingsData.appIconData.current == IconAlias.Red, + onClick = settingsData.appIconData.onSelect + ) + + SettingAppIcon( + iconAlias = IconAlias.Purple, + isEnabled = settingsData.appIconData.current == IconAlias.Purple, + onClick = settingsData.appIconData.onSelect + ) + + SettingAppIcon( + iconAlias = IconAlias.Brown, + isEnabled = settingsData.appIconData.current == IconAlias.Brown, + onClick = settingsData.appIconData.onSelect + ) + + SettingAppIcon( + iconAlias = IconAlias.Amoled, + isEnabled = settingsData.appIconData.current == IconAlias.Amoled, + onClick = settingsData.appIconData.onSelect + ) + } + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.githubData.isFeatureEnabled) { + item { + SettingItem( + title = stringResource(MoviesStrings.settings_github), + description = stringResource(MoviesStrings.settings_github_description), + icon = MoviesIcons.Github, + onClick = { settingsData.githubData.onClick(MOVIES_GITHUB_URL) } + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.reviewAppData.isFeatureEnabled) { + item { + SettingItem( + title = stringResource(MoviesStrings.settings_review), + description = stringResource(MoviesStrings.settings_review_description), + icon = MoviesIcons.GooglePlay, + onClick = settingsData.reviewAppData.onRequest + ) + } + item { + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + thickness = .1.dp, + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + if (settingsData.updateAppData.isFeatureEnabled) { + item { + SettingItem( + title = stringResource(MoviesStrings.settings_update), + description = stringResource(MoviesStrings.settings_update_description), + icon = MoviesIcons.SystemUpdate, + onClick = settingsData.updateAppData.onRequest + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt new file mode 100644 index 000000000..a4399f5c2 --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt @@ -0,0 +1,76 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.compose.iconbutton.BackIcon +import org.michaelbel.movies.ui.ktx.clickableWithoutRipple +import org.michaelbel.movies.ui.ktx.modifierDisplayCutoutWindowInsets +import org.michaelbel.movies.ui.strings.MoviesStrings +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun SettingsToolbar( + modifier: Modifier = Modifier, + topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), + onNavigationIconClick: () -> Unit, + onClick: () -> Unit, +) { + LargeTopAppBar( + title = { + Text( + text = stringResource(MoviesStrings.settings_title), + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + }, + modifier = modifier.clickableWithoutRipple { onClick() }, + navigationIcon = { + BackIcon( + onClick = onNavigationIconClick, + modifier = Modifier.then(modifierDisplayCutoutWindowInsets) + ) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + scrolledContainerColor = MaterialTheme.colorScheme.inversePrimary + ), + scrollBehavior = topAppBarScrollBehavior + ) +} + +@Composable +/*@DevicePreviews*/ +private fun SettingsToolbarPreview() { + MoviesTheme { + SettingsToolbar( + modifier = Modifier.statusBarsPadding(), + onNavigationIconClick = {}, + onClick = {} + ) + } +} + +@Composable +/*@Preview*/ +private fun SettingsToolbarAmoledPreview() { + MoviesTheme( + theme = AppTheme.Amoled + ) { + SettingsToolbar( + modifier = Modifier.statusBarsPadding(), + onNavigationIconClick = {}, + onClick = {} + ) + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt new file mode 100644 index 000000000..c24ff0c46 --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt @@ -0,0 +1,119 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.common.version.AppVersionData +import org.michaelbel.movies.settings.model.SettingsData +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.strings.MoviesStrings +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun SettingsVersionBox( + aboutData: SettingsData.AboutData, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.primaryContainer), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = MoviesIcons.MovieFilter, + contentDescription = MoviesContentDescriptionCommon.None, + modifier = Modifier.padding(vertical = 2.dp), + tint = MaterialTheme.colorScheme.primary + ) + + Text( + text = stringResource(MoviesStrings.settings_app_version_name, aboutData.versionName), + modifier = Modifier.padding(start = 4.dp), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + Text( + text = stringResource(MoviesStrings.settings_app_version_code, aboutData.versionCode), + modifier = Modifier.padding(start = 2.dp), + style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.primary) + ) + + Text( + text = aboutData.flavor, + modifier = Modifier.padding(start = 2.dp), + style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + if (aboutData.isDebug) { + Text( + text = stringResource(MoviesStrings.settings_app_debug), + modifier = Modifier.padding(start = 2.dp), + style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + } + } +} + +@Composable +/*@DevicePreviews*/ +private fun SettingsVersionBoxPreview( + /*@PreviewParameter(VersionPreviewParameterProvider::class)*/ appVersionData: AppVersionData +) { + MoviesTheme { + SettingsVersionBox( + aboutData = SettingsData.AboutData( + isFeatureEnabled = true, + versionName = "1.0.0", + versionCode = 1, + flavor = "GMS", + isDebug = true, + ), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} + +@Composable +/*@Preview*/ +private fun SettingsVersionBoxAmoledPreview( + /*@PreviewParameter(VersionPreviewParameterProvider::class)*/ appVersionData: AppVersionData +) { + MoviesTheme( + theme = AppTheme.Amoled + ) { + SettingsVersionBox( + aboutData = SettingsData.AboutData( + isFeatureEnabled = true, + versionName = "1.0.0", + versionCode = 1, + flavor = "GMS", + isDebug = true, + ), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingAppIcon.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingAppIcon.kt new file mode 100644 index 000000000..d230b53ae --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingAppIcon.kt @@ -0,0 +1,122 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ui.common + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon +import org.michaelbel.movies.ui.appicon.IconAlias +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun RowScope.SettingAppIcon( + iconAlias: IconAlias, + isEnabled: Boolean, + onClick: (IconAlias) -> Unit, + modifier: Modifier = Modifier +) { + val containerSize by animateDpAsState( + targetValue = if (isEnabled) 28.dp else 0.dp, + label = "containerSize" + ) + val iconSize by animateDpAsState( + targetValue = if (isEnabled) 16.dp else 0.dp, + label = "iconSize" + ) + + Box( + modifier = modifier + .padding(4.dp) + .sizeIn(maxHeight = 80.dp, maxWidth = 80.dp, minHeight = 64.dp, minWidth = 64.dp) + .weight(1F, false) + .aspectRatio(1F) + .clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.inversePrimary) + .clickable { onClick(iconAlias) } + ) { + Icon( + painter = painterResource(iconAlias.iconRes), + contentDescription = stringResource(MoviesContentDescriptionCommon.AppIcon), + modifier = Modifier + .padding(8.dp) + .clip(CircleShape) + .align(Alignment.Center), + tint = Color.Unspecified + ) + + Box( + modifier = Modifier + .align(Alignment.Center) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer) + .size(containerSize) + ) { + Icon( + imageVector = MoviesIcons.Check, + contentDescription = MoviesContentDescriptionCommon.None, + modifier = Modifier + .size(iconSize) + .align(Alignment.Center), + tint = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } +} + +@Composable +/*@DevicePreviews*/ +private fun SettingAppIconPreview( + /*@PreviewParameter(IconAliasPreviewParameterProvider::class)*/ iconAlias: IconAlias +) { + MoviesTheme { + Row { + SettingAppIcon( + iconAlias = iconAlias, + isEnabled = true, + onClick = {} + ) + } + } +} + +@Composable +/*@Preview*/ +private fun SettingAppIconAmoledPreview( + /*@PreviewParameter(IconAliasPreviewParameterProvider::class)*/ iconAlias: IconAlias +) { + MoviesTheme( + theme = AppTheme.Amoled + ) { + Row { + SettingAppIcon( + iconAlias = iconAlias, + isEnabled = true, + onClick = {} + ) + } + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingDialog.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingDialog.kt new file mode 100644 index 000000000..6dbe64291 --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingDialog.kt @@ -0,0 +1,146 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ui.common + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.common.SealedString +import org.michaelbel.movies.settings.ktx.stringText +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon +import org.michaelbel.movies.ui.strings.MoviesStrings + +@Composable +internal fun SettingsDialog( + icon: ImageVector, + title: String, + items: List, + currentItem: T, + onItemSelect: (T) -> Unit, + onDismissRequest: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismissRequest, + confirmButton = { + TextButton( + onClick = onDismissRequest + ) { + Text( + text = stringResource(MoviesStrings.settings_action_cancel), + style = MaterialTheme.typography.labelLarge.copy(MaterialTheme.colorScheme.primary) + ) + } + }, + icon = { + Icon( + imageVector = icon, + contentDescription = MoviesContentDescriptionCommon.None + ) + }, + title = { + Text( + text = title, + style = MaterialTheme.typography.headlineSmall.copy(MaterialTheme.colorScheme.onSurface) + ) + }, + text = { + val scrollState = rememberScrollState() + + Column( + modifier = Modifier.verticalScroll(scrollState) + ) { + items.forEach { item -> + Row( + modifier = Modifier + .fillMaxWidth() + .height(52.dp) + .clickable { + onItemSelect(item) + onDismissRequest() + }, + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = currentItem == item, + onClick = null, + colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.primary, + unselectedColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = .6F) + ), + modifier = Modifier.padding(start = 16.dp) + ) + + Text( + text = item.stringText, + modifier = Modifier.padding(start = 8.dp), + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) + } + } + } + }, + shape = RoundedCornerShape(28.dp), + containerColor = MaterialTheme.colorScheme.surface, + iconContentColor = MaterialTheme.colorScheme.secondary, + titleContentColor = MaterialTheme.colorScheme.onSurface + ) +} + +/* +@Composable +@DevicePreviews +private fun SettingDialogPreview( + @PreviewParameter(AppearancePreviewParameterProvider::class) appLanguage: AppLanguage +) { + MoviesTheme { + SettingsDialog( + icon = MoviesIcons.Language, + title = stringResource(R.string.settings_language), + items = AppLanguage.VALUES, + currentItem = appLanguage, + onItemSelect = {}, + onDismissRequest = {} + ) + } +} + +@Composable +@Preview +private fun SettingDialogAmoledPreview( + @PreviewParameter(LanguagePreviewParameterProvider::class) appLanguage: AppLanguage +) { + MoviesTheme( + theme = AppTheme.Amoled + ) { + SettingsDialog( + icon = MoviesIcons.Language, + title = stringResource(R.string.settings_language), + items = AppLanguage.VALUES, + currentItem = appLanguage, + onItemSelect = {}, + onDismissRequest = {} + ) + } +}*/ diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingItem.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingItem.kt new file mode 100644 index 000000000..dd2de2591 --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingItem.kt @@ -0,0 +1,91 @@ +package org.michaelbel.movies.settings.ui.common + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun SettingItem( + title: String, + description: String, + icon: ImageVector, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 8.dp, vertical = 20.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = icon, + contentDescription = MoviesContentDescriptionCommon.None, + modifier = Modifier + .padding(start = 8.dp, end = 16.dp) + .size(24.dp), + tint = MaterialTheme.colorScheme.onPrimaryContainer + ) + + Column( + modifier = Modifier.weight(1F) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + Text( + text = description, + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.primary) + ) + } + } +} + +@Composable +/*@DevicePreviews*/ +private fun SettingItemPreview() { + MoviesTheme { + SettingItem( + title = "Title", + description = "Description", + icon = MoviesIcons.Language, + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} + +@Composable +/*@Preview*/ +private fun SettingItemAmoledPreview() { + MoviesTheme( + theme = AppTheme.Amoled + ) { + SettingItem( + title = "Title", + description = "Description", + icon = MoviesIcons.Language, + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingPaletteColor.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingPaletteColor.kt new file mode 100644 index 000000000..b7162411d --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingPaletteColor.kt @@ -0,0 +1,104 @@ +package org.michaelbel.movies.settings.ui.common + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon +import org.michaelbel.movies.ui.color.TonalPalettes +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.theme.a1 +import org.michaelbel.movies.ui.theme.a2 +import org.michaelbel.movies.ui.theme.a3 + +@Composable +internal fun RowScope.SettingPaletteColor( + tonalPalettes: TonalPalettes, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val containerSize by animateDpAsState( + targetValue = if (isSelected) 28.dp else 0.dp, + label = "containerSize" + ) + val iconSize by animateDpAsState( + targetValue = if (isSelected) 16.dp else 0.dp, + label = "iconSize" + ) + + Box( + modifier = modifier + .padding(4.dp) + .sizeIn(maxHeight = 80.dp, maxWidth = 80.dp, minHeight = 64.dp, minWidth = 64.dp) + .weight(1F, false) + .aspectRatio(1F) + .clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.surfaceContainer) + .clickable { onClick() } + ) { + val color1 = 80.a1(tonalPalettes) + val color2 = 90.a2(tonalPalettes) + val color3 = 60.a3(tonalPalettes) + + Box( + modifier = Modifier.fillMaxSize() + ) { + Box( + modifier = modifier + .size(48.dp) + .clip(CircleShape) + .drawBehind { drawCircle(color1) } + .align(Alignment.Center) + ) { + Box( + modifier = Modifier + .background(color2) + .align(Alignment.BottomStart) + .size(24.dp) + ) + + Box( + modifier = Modifier + .background(color3) + .align(Alignment.BottomEnd) + .size(24.dp) + ) + + Box( + modifier = Modifier + .align(Alignment.Center) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primaryContainer) + .size(containerSize) + ) { + Icon( + imageVector = MoviesIcons.Check, + contentDescription = MoviesContentDescriptionCommon.None, + modifier = Modifier + .size(iconSize) + .align(Alignment.Center), + tint = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + } + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingSwitchItem.kt b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingSwitchItem.kt new file mode 100644 index 000000000..3c2c442ce --- /dev/null +++ b/feature/settings-impl-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/common/SettingSwitchItem.kt @@ -0,0 +1,200 @@ +package org.michaelbel.movies.settings.ui.common + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon +import org.michaelbel.movies.ui.compose.SwitchCheckIcon +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +internal fun SettingSwitchItem( + title: String, + description: String, + icon: ImageVector, + checked: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + ConstraintLayout( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() } + .padding(vertical = 20.dp) + ) { + val (iconRef, texts, switch) = createRefs() + + Icon( + imageVector = icon, + contentDescription = MoviesContentDescriptionCommon.None, + modifier = Modifier.constrainAs(iconRef) { + width = Dimension.value(24.dp) + height = Dimension.value(24.dp) + start.linkTo(parent.start, 16.dp) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + }, + tint = MaterialTheme.colorScheme.onPrimaryContainer + ) + + Column( + modifier = Modifier.constrainAs(texts) { + width = Dimension.fillToConstraints + height = Dimension.wrapContent + start.linkTo(iconRef.end, 16.dp) + top.linkTo(parent.top) + end.linkTo(switch.start, 16.dp) + bottom.linkTo(parent.bottom) + } + ) { + Text( + text = title, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + Text( + text = description, + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.primary) + ) + } + + Switch( + checked = checked, + onCheckedChange = null, + modifier = Modifier.constrainAs(switch) { + width = Dimension.wrapContent + height = Dimension.wrapContent + top.linkTo(parent.top) + end.linkTo(parent.end, 16.dp) + bottom.linkTo(parent.bottom) + }, + thumbContent = if (checked) { { SwitchCheckIcon() } } else null, + colors = SwitchDefaults.colors( + checkedTrackColor = MaterialTheme.colorScheme.surfaceTint, + checkedIconColor = MaterialTheme.colorScheme.surfaceTint + ) + ) + } +} + +@Composable +internal fun SettingSwitchItem( + title: String, + description: String, + icon: Painter, + checked: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + ConstraintLayout( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() } + .padding(horizontal = 8.dp, vertical = 20.dp) + ) { + val (iconRef, texts, switch) = createRefs() + + Icon( + painter = icon, + contentDescription = MoviesContentDescriptionCommon.None, + modifier = Modifier.constrainAs(iconRef) { + width = Dimension.value(24.dp) + height = Dimension.value(24.dp) + start.linkTo(parent.start, 16.dp) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + }, + tint = MaterialTheme.colorScheme.onPrimaryContainer + ) + + Column( + modifier = Modifier.constrainAs(texts) { + width = Dimension.fillToConstraints + height = Dimension.wrapContent + start.linkTo(iconRef.end, 16.dp) + top.linkTo(parent.top) + end.linkTo(switch.start, 16.dp) + bottom.linkTo(parent.bottom) + } + ) { + Text( + text = title, + style = MaterialTheme.typography.titleLarge.copy(MaterialTheme.colorScheme.onPrimaryContainer) + ) + + Text( + text = description, + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.primary) + ) + } + + Switch( + checked = checked, + onCheckedChange = null, + modifier = Modifier.constrainAs(switch) { + width = Dimension.wrapContent + height = Dimension.wrapContent + top.linkTo(parent.top) + end.linkTo(parent.end, 16.dp) + bottom.linkTo(parent.bottom) + }, + thumbContent = if (checked) { { SwitchCheckIcon() } } else null, + colors = SwitchDefaults.colors( + checkedTrackColor = MaterialTheme.colorScheme.surfaceTint, + checkedIconColor = MaterialTheme.colorScheme.surfaceTint + ) + ) + } +} + +@Composable +/*@DevicePreviews*/ +private fun SettingSwitchItemPreview( + /*@PreviewParameter(BooleanPreviewParameterProvider::class)*/ checked: Boolean +) { + MoviesTheme { + SettingSwitchItem( + title = "Title", + description = "Description", + icon = MoviesIcons.Language, + checked = checked, + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} + +@Composable +/*@Preview*/ +private fun SettingSwitchItemAmoledPreview( + /*@PreviewParameter(BooleanPreviewParameterProvider::class)*/ checked: Boolean +) { + MoviesTheme( + theme = AppTheme.Amoled + ) { + SettingSwitchItem( + title = "Title", + description = "Description", + icon = MoviesIcons.Language, + checked = checked, + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt b/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt new file mode 100644 index 000000000..f6f739e19 --- /dev/null +++ b/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/ktx/GenderStringKtx.kt @@ -0,0 +1,11 @@ +@file:OptIn(ExperimentalResourceApi::class) + +package org.michaelbel.movies.settings.ktx + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.ui.strings.MoviesStrings + +internal actual val SettingsGenderText: String + @Composable get() = stringResource(MoviesStrings.settings_gender) \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/model/Features.kt b/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/model/Features.kt new file mode 100644 index 000000000..bb963b767 --- /dev/null +++ b/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/model/Features.kt @@ -0,0 +1,46 @@ +package org.michaelbel.movies.settings.model + +internal actual val isLanguageFeatureEnabled: Boolean + get() = false + +internal actual val isThemeFeatureEnabled: Boolean + get() = false + +internal actual val isFeedViewFeatureEnabled: Boolean + get() = false + +internal actual val isMovieListFeatureEnabled: Boolean + get() = false + +internal actual val isGenderFeatureEnabled: Boolean + get() = false + +internal actual val isDynamicColorsFeatureEnabled: Boolean + get() = false + +internal actual val isNotificationsFeatureEnabled: Boolean + get() = false + +internal actual val isBiometricFeatureEnabled: Boolean + get() = false + +internal actual val isWidgetFeatureEnabled: Boolean + get() = false + +internal actual val isTileFeatureEnabled: Boolean + get() = false + +internal actual val isAppIconFeatureEnabled: Boolean + get() = false + +internal actual val isGithubFeatureEnabled: Boolean + get() = true + +internal actual val isReviewAppFeatureEnabled: Boolean + get() = false + +internal actual val isUpdateAppFeatureEnabled: Boolean + get() = false + +internal actual val isAboutFeatureEnabled: Boolean + get() = true \ No newline at end of file diff --git a/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.kt b/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.kt new file mode 100644 index 000000000..3ed8c9be9 --- /dev/null +++ b/feature/settings-impl-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.kt @@ -0,0 +1,128 @@ +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.common.browser.openUrl +import org.michaelbel.movies.common.gender.GrammaticalGender +import org.michaelbel.movies.common.list.MovieList +import org.michaelbel.movies.common.localization.model.AppLanguage +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.settings.model.SettingsData +import org.michaelbel.movies.settings.model.isAboutFeatureEnabled +import org.michaelbel.movies.settings.model.isAppIconFeatureEnabled +import org.michaelbel.movies.settings.model.isBiometricFeatureEnabled +import org.michaelbel.movies.settings.model.isDynamicColorsFeatureEnabled +import org.michaelbel.movies.settings.model.isFeedViewFeatureEnabled +import org.michaelbel.movies.settings.model.isGenderFeatureEnabled +import org.michaelbel.movies.settings.model.isGithubFeatureEnabled +import org.michaelbel.movies.settings.model.isLanguageFeatureEnabled +import org.michaelbel.movies.settings.model.isMovieListFeatureEnabled +import org.michaelbel.movies.settings.model.isNotificationsFeatureEnabled +import org.michaelbel.movies.settings.model.isReviewAppFeatureEnabled +import org.michaelbel.movies.settings.model.isThemeFeatureEnabled +import org.michaelbel.movies.settings.model.isTileFeatureEnabled +import org.michaelbel.movies.settings.model.isUpdateAppFeatureEnabled +import org.michaelbel.movies.settings.model.isWidgetFeatureEnabled +import org.michaelbel.movies.ui.appicon.IconAlias + +@Composable +fun SettingsRoute( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + val snackbarHostState = remember { SnackbarHostState() } + + SettingsScreenContent( + settingsData = SettingsData( + onBackClick = onBackClick, + languageData = SettingsData.ListData( + isFeatureEnabled = isLanguageFeatureEnabled, + current = AppLanguage.English(), + onSelect = {} + ), + themeData = SettingsData.ListData( + isFeatureEnabled = isThemeFeatureEnabled, + current = AppTheme.FollowSystem, + onSelect = {} + ), + feedViewData = SettingsData.ListData( + isFeatureEnabled = isFeedViewFeatureEnabled, + current = FeedView.FeedList, + onSelect = {} + ), + movieListData = SettingsData.ListData( + isFeatureEnabled = isMovieListFeatureEnabled, + current = MovieList.NowPlaying(), + onSelect = {} + ), + genderData = SettingsData.ListData( + isFeatureEnabled = isGenderFeatureEnabled, + current = GrammaticalGender.NotSpecified(), + onSelect = {} + ), + dynamicColorsData = SettingsData.DynamicColorsData( + isFeatureEnabled = isDynamicColorsFeatureEnabled, + isEnabled = false, + onChange = {} + ), + paletteColorsData = SettingsData.PaletteColorsData( + isFeatureEnabled = false, + isDynamicColorsEnabled = false, + paletteKey = 0, + seedColor = 0, + onChange = { _,_,_ -> } + ), + notificationsData = SettingsData.NotificationsData( + isFeatureEnabled = isNotificationsFeatureEnabled, + isEnabled = false, + onClick = {}, + onNavigateToAppNotificationSettings = {} + ), + biometricData = SettingsData.BiometricData( + isFeatureEnabled = isBiometricFeatureEnabled, + isEnabled = false, + onChange = {} + ), + widgetData = SettingsData.WidgetData( + isFeatureEnabled = isWidgetFeatureEnabled, + onRequest = {} + ), + tileData = SettingsData.TileData( + isFeatureEnabled = isTileFeatureEnabled, + onRequest = {} + ), + appIconData = SettingsData.ListData( + isFeatureEnabled = isAppIconFeatureEnabled, + current = IconAlias.Red, + onSelect = {} + ), + githubData = SettingsData.GithubData( + isFeatureEnabled = isGithubFeatureEnabled, + onClick = { url -> openUrl(url) } + ), + reviewAppData = SettingsData.ReviewAppData( + isFeatureEnabled = isReviewAppFeatureEnabled, + onRequest = {} + ), + updateAppData = SettingsData.UpdateAppData( + isFeatureEnabled = isUpdateAppFeatureEnabled, + onRequest = {} + ), + aboutData = SettingsData.AboutData( + isFeatureEnabled = isAboutFeatureEnabled, + versionName = "1.0.0", + versionCode = 1L, + flavor = "FOSS", + isDebug = false + ) + ), + windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp), + snackbarHostState = snackbarHostState, + modifier = modifier + ) +} \ No newline at end of file diff --git a/feature/settings-impl/build.gradle.kts b/feature/settings-impl/build.gradle.kts deleted file mode 100644 index 7ad3083f5..000000000 --- a/feature/settings-impl/build.gradle.kts +++ /dev/null @@ -1,69 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - id("movies-android-hilt") -} - -android { - namespace = "org.michaelbel.movies.settings_impl" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" - ) - } - - buildFeatures { - buildConfig = true - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":core:platform-services:interactor")) - api(project(":core:navigation")) - api(project(":core:common")) - api(project(":core:ui")) - implementation(project(":core:interactor")) - - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.ext.junit.ktx) - androidTestImplementation(libs.androidx.espresso.core) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) - androidTestImplementation(libs.androidx.benchmark.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBoxTest.kt deleted file mode 100644 index 3b026cb3c..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBoxTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.unit.dp -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsDynamicColorsBoxTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsDynamicColorsBox() { - composeTestRule.setContent { - MoviesTheme { - SettingsDynamicColorsBox( - isDynamicColorsEnabled = IS_DYNAMIC_COLORS_ENABLED, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable {} - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Text", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Switch", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - } - - private companion object { - private const val IS_DYNAMIC_COLORS_ENABLED = true - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBoxTest.kt deleted file mode 100644 index 4b76af759..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBoxTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.unit.dp -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.common.localization.model.AppLanguage -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsLanguageBoxTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsLanguageBox() { - composeTestRule.setContent { - MoviesTheme { - SettingsLanguageBox( - currentLanguage = CURRENT_LANGUAGE, - onLanguageSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasClickAction() - - composeTestRule - .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "ValueText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - } - - private companion object { - private val CURRENT_LANGUAGE: AppLanguage = AppLanguage.English - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBoxTest.kt deleted file mode 100644 index f59cdb449..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBoxTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.unit.dp -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsPostNotificationsBoxTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsPostNotificationsBox() { - composeTestRule.setContent { - MoviesTheme { - SettingsPostNotificationsBox( - modifier = Modifier - .fillMaxWidth() - .height(52.dp), - onShowPermissionSnackbar = {} - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Text", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Switch", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBoxTest.kt deleted file mode 100644 index 70d9a03c1..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBoxTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.unit.dp -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsReviewBoxTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsReviewBox() { - composeTestRule.setContent { - MoviesTheme { - SettingsReviewBox( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable {} - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Text", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBoxTest.kt deleted file mode 100644 index fdc71ba7f..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBoxTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.unit.dp -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsThemeBoxTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsThemeBox() { - composeTestRule.setContent { - MoviesTheme { - SettingsThemeBox( - currentTheme = CURRENT_THEME, - onThemeSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasClickAction() - - composeTestRule - .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "ValueText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - } - - private companion object { - private val CURRENT_THEME = AppTheme.FollowSystem - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialogTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialogTest.kt deleted file mode 100644 index 35b19445d..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialogTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsThemeDialogTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsThemeDialog() { - composeTestRule.setContent { - MoviesTheme { - SettingThemeDialog( - currentTheme = CURRENT_THEME, - onThemeSelect = {}, - onDismissRequest = {} - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "ConfirmTextButton", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasClickAction() - - composeTestRule - .onNodeWithTag(testTag = "ConfirmText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Icon", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Title", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Content", useUnmergedTree = true) - .assertIsDisplayed() - } - - private companion object { - private val CURRENT_THEME = AppTheme.FollowSystem - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbarTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbarTest.kt deleted file mode 100644 index 0cefa30e6..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbarTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assertHasClickAction -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsToolbarTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsToolbar() { - composeTestRule.setContent { - MoviesTheme { - SettingsToolbar( - modifier = Modifier.statusBarsPadding(), - onNavigationIconClick = {} - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "TopAppBar", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "BackIconButton", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasClickAction() - - composeTestRule - .onNodeWithTag(testTag = "BackImage", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt deleted file mode 100644 index 477269559..000000000 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assertHasNoClickAction -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.ComposeContentTestRule -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithTag -import org.junit.Rule -import org.junit.Test -import org.michaelbel.movies.common.version.AppVersionData -import org.michaelbel.movies.ui.theme.MoviesTheme - -internal class SettingsVersionBoxTest { - - @get:Rule - val composeTestRule: ComposeContentTestRule = createComposeRule() - - @Test - fun testSettingsVersionBox() { - composeTestRule.setContent { - MoviesTheme { - SettingsVersionBox( - appVersionData = APP_VERSION_DATA, - modifier = Modifier - .navigationBarsPadding() - .fillMaxWidth() - ) - } - } - - composeTestRule - .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "Icon", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - - composeTestRule - .onNodeWithTag(testTag = "ValueText", useUnmergedTree = true) - .assertIsDisplayed() - .assertHasNoClickAction() - } - - private companion object { - private val APP_VERSION_DATA: AppVersionData = AppVersionData( - version = "1.0.0", - code = 1L, - flavor = "GMS", - isDebug = true - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/AndroidManifest.xml b/feature/settings-impl/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/settings-impl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt deleted file mode 100644 index 3dc848496..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.michaelbel.movies.settings - -import android.app.Activity -import android.os.Build -import androidx.lifecycle.DefaultLifecycleObserver -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.localization.LocaleController -import org.michaelbel.movies.common.localization.model.AppLanguage -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.version.AppVersionData -import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.platform.review.ReviewService -import javax.inject.Inject - -@HiltViewModel -class SettingsViewModel @Inject constructor( - private val interactor: Interactor, - private val localeController: LocaleController, - private val reviewService: ReviewService -): BaseViewModel(), DefaultLifecycleObserver { - - val isDynamicColorsFeatureEnabled: Boolean = Build.VERSION.SDK_INT >= 31 - - val isPostNotificationsFeatureEnabled: Boolean = Build.VERSION.SDK_INT >= 33 - - val currentTheme: StateFlow = interactor.currentTheme - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = AppTheme.FollowSystem - ) - - val currentFeedView: StateFlow = interactor.currentFeedView - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = FeedView.FeedList - ) - - val currentMovieList: StateFlow = interactor.currentMovieList - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = MovieList.NowPlaying - ) - - val dynamicColors: StateFlow = interactor.dynamicColors - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = false - ) - - val isPlayServicesAvailable: StateFlow = interactor.isPlayServicesAvailable - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = false - ) - - val appVersionData: StateFlow = interactor.appVersionData - .stateIn( - scope = this, - started = SharingStarted.Lazily, - initialValue = AppVersionData.Empty - ) - - fun selectLanguage(language: AppLanguage) = launch { - localeController.selectLanguage(language) - } - - fun selectTheme(theme: AppTheme) = launch { - interactor.selectTheme(theme) - } - - fun selectFeedView(feedView: FeedView) = launch { - interactor.selectFeedView(feedView) - } - - fun selectMovieList(movieList: MovieList) = launch { - interactor.selectMovieList(movieList) - } - - fun setDynamicColors(value: Boolean) = launch { - interactor.setDynamicColors(value) - } - - fun requestReview(activity: Activity) { - reviewService.requestReview(activity) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/AppLanguageKtx.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/AppLanguageKtx.kt deleted file mode 100644 index 3bd647685..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/AppLanguageKtx.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.michaelbel.movies.settings.ktx - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.common.localization.model.AppLanguage -import org.michaelbel.movies.settings_impl.R - -internal val AppLanguage.languageText: String - @Composable get() = when (this) { - is AppLanguage.English -> stringResource(R.string.settings_language_en) - is AppLanguage.Russian -> stringResource(R.string.settings_language_ru) - } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/FeedViewKtx.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/FeedViewKtx.kt deleted file mode 100644 index 4c17fed6c..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/FeedViewKtx.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.michaelbel.movies.settings.ktx - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.settings_impl.R - -internal val FeedView.feedViewText: String - @Composable get() = when (this) { - is FeedView.FeedList -> stringResource(R.string.settings_appearance_list) - is FeedView.FeedGrid -> stringResource(R.string.settings_appearance_grid) - } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/IconAliasKtx.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/IconAliasKtx.kt deleted file mode 100644 index 2b5848f87..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/IconAliasKtx.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.michaelbel.movies.settings.ktx - -import android.content.Context -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.appicon.IconAlias -import org.michaelbel.movies.ui.appicon.isEnabled - -internal val IconAlias.iconText: String - @Composable get() = when (this) { - is IconAlias.Red -> stringResource(R.string.settings_app_launcher_icon_red) - is IconAlias.Purple -> stringResource(R.string.settings_app_launcher_icon_purple) - is IconAlias.Brown -> stringResource(R.string.settings_app_launcher_icon_brown) - is IconAlias.Amoled -> stringResource(R.string.settings_app_launcher_icon_amoled) - } - -internal fun IconAlias.iconSnackbarText(context: Context): String { - return when (this) { - is IconAlias.Red -> context.getString(R.string.settings_app_launcher_icon_red) - is IconAlias.Purple -> context.getString(R.string.settings_app_launcher_icon_purple) - is IconAlias.Brown -> context.getString(R.string.settings_app_launcher_icon_brown) - is IconAlias.Amoled -> context.getString(R.string.settings_app_launcher_icon_amoled) - } -} - -@Composable -internal fun IconAlias.backgroundColor(context: Context): Color { - return when { - context.isEnabled(this) -> MaterialTheme.colorScheme.inversePrimary - else -> MaterialTheme.colorScheme.primaryContainer - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/MovieListKtx.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/MovieListKtx.kt deleted file mode 100644 index 9848b8a8c..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/MovieListKtx.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.settings.ktx - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.settings_impl.R - -internal val MovieList.listText: String - @Composable get() = when (this) { - is MovieList.NowPlaying -> stringResource(R.string.settings_movie_list_now_playing) - is MovieList.Popular -> stringResource(R.string.settings_movie_list_popular) - is MovieList.TopRated -> stringResource(R.string.settings_movie_list_top_rated) - is MovieList.Upcoming -> stringResource(R.string.settings_movie_list_upcoming) - } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/SystemThemeKtx.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/SystemThemeKtx.kt deleted file mode 100644 index e182e3438..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ktx/SystemThemeKtx.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.michaelbel.movies.settings.ktx - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings_impl.R - -internal val AppTheme.themeText: String - @Composable get() = when (this) { - is AppTheme.NightNo -> stringResource(R.string.settings_theme_light) - is AppTheme.NightYes -> stringResource(R.string.settings_theme_dark) - is AppTheme.FollowSystem -> stringResource(R.string.settings_theme_system) - is AppTheme.Amoled -> stringResource(R.string.settings_theme_amoled) - } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/AppIconBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/AppIconBox.kt deleted file mode 100644 index 3bdd7a5ff..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/AppIconBox.kt +++ /dev/null @@ -1,122 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ChainStyle -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.backgroundColor -import org.michaelbel.movies.settings.ktx.iconText -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.appicon.IconAlias -import org.michaelbel.movies.ui.appicon.isEnabled -import org.michaelbel.movies.ui.ktx.context -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.IconAliasPreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun AppIconBox( - iconAlias: IconAlias, - modifier: Modifier = Modifier -) { - ConstraintLayout( - modifier = modifier - ) { - val (icon, radio, text) = createRefs() - createHorizontalChain(radio, text, chainStyle = ChainStyle.Packed) - - Icon( - painter = painterResource(iconAlias.iconRes), - contentDescription = stringResource(MoviesContentDescription.AppIcon), - modifier = Modifier - .constrainAs(icon) { - width = Dimension.value(56.dp) - height = Dimension.value(56.dp) - start.linkTo(parent.start, 8.dp) - top.linkTo(parent.top, 8.dp) - end.linkTo(parent.end, 8.dp) - } - .clip(CircleShape), - tint = Color.Unspecified - ) - - RadioButton( - selected = context.isEnabled(iconAlias), - onClick = null, - modifier = Modifier.constrainAs(radio) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start) - top.linkTo(icon.bottom, 8.dp) - end.linkTo(text.start) - bottom.linkTo(parent.bottom, 8.dp) - } - ) - - Text( - text = iconAlias.iconText, - modifier = Modifier - .constrainAs(text) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(radio.end) - top.linkTo(radio.top) - end.linkTo(parent.end) - bottom.linkTo(radio.bottom) - } - .padding(start = 2.dp), - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } -} - -@Composable -@DevicePreviews -private fun AppIconBoxPreview( - @PreviewParameter(IconAliasPreviewParameterProvider::class) iconAlias: IconAlias -) { - MoviesTheme { - AppIconBox( - iconAlias = iconAlias, - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(iconAlias.backgroundColor(context)) - ) - } -} - -@Composable -@Preview -private fun AppIconBoxAmoledPreview( - @PreviewParameter(IconAliasPreviewParameterProvider::class) iconAlias: IconAlias -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - AppIconBox( - iconAlias = iconAlias, - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(iconAlias.backgroundColor(context)) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppIconBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppIconBox.kt deleted file mode 100644 index d6b261a35..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppIconBox.kt +++ /dev/null @@ -1,148 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.backgroundColor -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.appicon.IconAlias -import org.michaelbel.movies.ui.appicon.setIcon -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsAppIconBox( - onAppIconChanged: (IconAlias) -> Unit, - modifier: Modifier = Modifier -) { - val context = LocalContext.current - - fun changeAppIcon(iconAlias: IconAlias) { - onAppIconChanged(iconAlias) - context.setIcon(iconAlias) - } - - ConstraintLayout( - modifier = modifier - ) { - val (title, redBox, purpleBox, brownBox, amoledBox) = createRefs() - - Text( - text = stringResource(R.string.settings_app_launcher_icon), - modifier = Modifier.constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top, 8.dp) - }, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - AppIconBox( - iconAlias = IconAlias.Red, - modifier = Modifier - .constrainAs(redBox) { - height = Dimension.wrapContent - start.linkTo(parent.start, 8.dp) - top.linkTo(title.bottom, 8.dp) - end.linkTo(purpleBox.start, 4.dp) - bottom.linkTo(parent.bottom, 16.dp) - } - .fillMaxWidth(.20F) - .clip(RoundedCornerShape(8.dp)) - .background(IconAlias.Red.backgroundColor(context)) - .clickable { changeAppIcon(IconAlias.Red) } - ) - - AppIconBox( - iconAlias = IconAlias.Purple, - modifier = Modifier - .constrainAs(purpleBox) { - height = Dimension.wrapContent - start.linkTo(redBox.end, 4.dp) - top.linkTo(title.bottom, 8.dp) - end.linkTo(brownBox.start, 4.dp) - bottom.linkTo(parent.bottom, 16.dp) - } - .fillMaxWidth(.20F) - .clip(RoundedCornerShape(8.dp)) - .background(IconAlias.Purple.backgroundColor(context)) - .clickable { changeAppIcon(IconAlias.Purple) } - ) - - AppIconBox( - iconAlias = IconAlias.Brown, - modifier = Modifier - .constrainAs(brownBox) { - height = Dimension.wrapContent - start.linkTo(purpleBox.end, 4.dp) - top.linkTo(title.bottom, 8.dp) - end.linkTo(amoledBox.start, 4.dp) - bottom.linkTo(parent.bottom, 16.dp) - } - .fillMaxWidth(.20F) - .clip(RoundedCornerShape(8.dp)) - .background(IconAlias.Brown.backgroundColor(context)) - .clickable { changeAppIcon(IconAlias.Brown) } - ) - - AppIconBox( - iconAlias = IconAlias.Amoled, - modifier = Modifier - .constrainAs(amoledBox) { - height = Dimension.wrapContent - start.linkTo(brownBox.end, 4.dp) - top.linkTo(title.bottom, 8.dp) - end.linkTo(parent.end, 8.dp) - bottom.linkTo(parent.bottom, 16.dp) - } - .fillMaxWidth(.20F) - .clip(RoundedCornerShape(8.dp)) - .background(IconAlias.Amoled.backgroundColor(context)) - .clickable { changeAppIcon(IconAlias.Amoled) } - ) - } -} - -@Composable -@DevicePreviews -private fun SettingsAppIconBoxPreview() { - MoviesTheme { - SettingsAppIconBox( - onAppIconChanged = {}, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsAppIconBoxAmoledPreview() { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsAppIconBox( - onAppIconChanged = {}, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceBox.kt deleted file mode 100644 index cc5d066ed..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceBox.kt +++ /dev/null @@ -1,125 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -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.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.feedViewText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.AppearancePreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsAppearanceBox( - currentFeedView: FeedView, - onFeedViewSelect: (FeedView) -> Unit, - modifier: Modifier = Modifier, -) { - var feedViewDialog: Boolean by remember { mutableStateOf(false) } - - if (feedViewDialog) { - SettingsAppearanceDialog( - currentFeedView = currentFeedView, - onFeedViewSelect = onFeedViewSelect, - onDismissRequest = { - feedViewDialog = false - } - ) - } - - ConstraintLayout( - modifier = modifier - .clickable { - feedViewDialog = true - } - .testTag("ConstraintLayout") - ) { - val (title, value) = createRefs() - - Text( - text = stringResource(R.string.settings_appearance), - modifier = Modifier - .constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("TitleText"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - Text( - text = currentFeedView.feedViewText, - modifier = Modifier - .constrainAs(value) { - width = Dimension.wrapContent - height = Dimension.wrapContent - top.linkTo(parent.top) - end.linkTo(parent.end, 16.dp) - bottom.linkTo(parent.bottom) - } - .testTag("ValueText"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - } -} - -@Composable -@DevicePreviews -private fun SettingsAppearanceBoxPreview( - @PreviewParameter(AppearancePreviewParameterProvider::class) feedView: FeedView -) { - MoviesTheme { - SettingsAppearanceBox( - currentFeedView = feedView, - onFeedViewSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsAppearanceBoxAmoledPreview( - @PreviewParameter(AppearancePreviewParameterProvider::class) feedView: FeedView -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsAppearanceBox( - currentFeedView = feedView, - onFeedViewSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceDialog.kt deleted file mode 100644 index 39fc985c8..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceDialog.kt +++ /dev/null @@ -1,161 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.RadioButtonDefaults -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.feedViewText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.AppearancePreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsAppearanceDialog( - currentFeedView: FeedView, - onFeedViewSelect: (FeedView) -> Unit, - onDismissRequest: () -> Unit -) { - AlertDialog( - onDismissRequest = onDismissRequest, - confirmButton = { - TextButton( - onClick = onDismissRequest, - modifier = Modifier.testTag("ConfirmTextButton") - ) { - Text( - text = stringResource(R.string.settings_action_cancel), - modifier = Modifier.testTag("ConfirmText"), - style = MaterialTheme.typography.labelLarge.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - } - }, - icon = { - Icon( - imageVector = MoviesIcons.GridView, - contentDescription = stringResource(MoviesContentDescription.AppearanceIcon), - modifier = Modifier.testTag("Icon") - ) - }, - title = { - Text( - text = stringResource(R.string.settings_appearance), - modifier = Modifier.testTag("Title"), - style = MaterialTheme.typography.headlineSmall.copy( - color = MaterialTheme.colorScheme.onSurface - ) - ) - }, - text = { - SettingAppearanceDialogContent( - currentFeedView = currentFeedView, - onFeedViewSelect = { feedView -> - onFeedViewSelect(feedView) - onDismissRequest() - }, - modifier = Modifier.testTag("Content") - ) - }, - shape = RoundedCornerShape(28.dp), - containerColor = MaterialTheme.colorScheme.surface, - iconContentColor = MaterialTheme.colorScheme.secondary, - titleContentColor = MaterialTheme.colorScheme.onSurface - ) -} - -@Composable -private fun SettingAppearanceDialogContent( - currentFeedView: FeedView, - onFeedViewSelect: (FeedView) -> Unit, - modifier: Modifier = Modifier -) { - val scrollState = rememberScrollState() - - Column( - modifier = modifier.verticalScroll(scrollState) - ) { - FeedView.VALUES.forEach { feedView: FeedView -> - Row( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable { onFeedViewSelect(feedView) }, - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = currentFeedView == feedView, - onClick = null, - colors = RadioButtonDefaults.colors( - selectedColor = MaterialTheme.colorScheme.primary, - unselectedColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6F) - ), - modifier = Modifier.padding(start = 16.dp) - ) - - Text( - text = feedView.feedViewText, - modifier = Modifier.padding(start = 8.dp), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } - } - } -} - -@Composable -@DevicePreviews -private fun SettingsAppearanceDialogPreview( - @PreviewParameter(AppearancePreviewParameterProvider::class) feeView: FeedView -) { - MoviesTheme { - SettingsAppearanceDialog( - currentFeedView = feeView, - onFeedViewSelect = {}, - onDismissRequest = {} - ) - } -} - -@Composable -@Preview -private fun SettingsAppearanceDialogAmoledPreview( - @PreviewParameter(AppearancePreviewParameterProvider::class) feeView: FeedView -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsAppearanceDialog( - currentFeedView = feeView, - onFeedViewSelect = {}, - onDismissRequest = {} - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBox.kt deleted file mode 100644 index 1880d337c..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBox.kt +++ /dev/null @@ -1,105 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.compose.SwitchCheckIcon -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.BooleanPreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsDynamicColorsBox( - isDynamicColorsEnabled: Boolean, - modifier: Modifier = Modifier -) { - ConstraintLayout( - modifier = modifier.testTag("ConstraintLayout") - ) { - val (title, value) = createRefs() - - Text( - text = stringResource(R.string.settings_dynamic_colors), - modifier = Modifier - .constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("Text"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - Switch( - checked = isDynamicColorsEnabled, - onCheckedChange = null, - modifier = Modifier - .constrainAs(value) { - width = Dimension.wrapContent - height = Dimension.wrapContent - top.linkTo(parent.top) - end.linkTo(parent.end, 16.dp) - bottom.linkTo(parent.bottom) - } - .testTag("Switch"), - thumbContent = if (isDynamicColorsEnabled) { { SwitchCheckIcon() } } else null, - colors = SwitchDefaults.colors( - checkedTrackColor = MaterialTheme.colorScheme.surfaceTint, - checkedIconColor = MaterialTheme.colorScheme.surfaceTint - ) - ) - } -} - -@Composable -@DevicePreviews -private fun SettingsDynamicColorsBoxPreview( - @PreviewParameter(BooleanPreviewParameterProvider::class) isEnabled: Boolean -) { - MoviesTheme { - SettingsDynamicColorsBox( - isDynamicColorsEnabled = isEnabled, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsDynamicColorsBoxAmoledPreview( - @PreviewParameter(BooleanPreviewParameterProvider::class) isEnabled: Boolean -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsDynamicColorsBox( - isDynamicColorsEnabled = isEnabled, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBox.kt deleted file mode 100644 index e6b3556c4..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBox.kt +++ /dev/null @@ -1,158 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.widthIn -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -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.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.localization.model.AppLanguage -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.languageText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.LanguagePreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsLanguageBox( - currentLanguage: AppLanguage, - onLanguageSelect: (AppLanguage) -> Unit, - modifier: Modifier = Modifier, -) { - var languageDropdown by remember { mutableStateOf(false) } - - ConstraintLayout( - modifier = modifier - .clickable { languageDropdown = true } - .testTag("ConstraintLayout") - ) { - val (icon, title, value) = createRefs() - - Icon( - imageVector = MoviesIcons.Language, - contentDescription = stringResource(MoviesContentDescription.LanguageIcon), - modifier = Modifier - .constrainAs(icon) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("Icon"), - tint = MaterialTheme.colorScheme.onPrimaryContainer - ) - - Text( - text = stringResource(R.string.settings_language), - modifier = Modifier - .constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(icon.end, 8.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("TitleText"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - Box( - modifier = Modifier - .constrainAs(value) { - width = Dimension.wrapContent - height = Dimension.wrapContent - top.linkTo(parent.top) - end.linkTo(parent.end, 16.dp) - bottom.linkTo(parent.bottom) - } - .testTag("ValueText"), - ) { - Text( - text = currentLanguage.languageText, - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - - DropdownMenu( - expanded = languageDropdown, - onDismissRequest = { languageDropdown = false }, - offset = DpOffset(x = 0.dp, y = (-48).dp), - modifier = Modifier.widthIn(min = 112.dp, max = 280.dp) - ) { - AppLanguage.VALUES.forEach { appLanguage -> - DropdownMenuItem( - text = { - Text( - text = appLanguage.languageText, - style = MaterialTheme.typography.bodyLarge - ) - }, - onClick = { onLanguageSelect(appLanguage) } - ) - } - } - } - } -} - -@Composable -@DevicePreviews -private fun SettingsLanguageBoxPreview( - @PreviewParameter(LanguagePreviewParameterProvider::class) language: AppLanguage -) { - MoviesTheme { - SettingsLanguageBox( - currentLanguage = language, - onLanguageSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsLanguageBoxAmoledPreview( - @PreviewParameter(LanguagePreviewParameterProvider::class) language: AppLanguage -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsLanguageBox( - currentLanguage = language, - onLanguageSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListBox.kt deleted file mode 100644 index 91aa9daa7..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListBox.kt +++ /dev/null @@ -1,125 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -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.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.listText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.MovieListPreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsMovieListBox( - currentMovieList: MovieList, - onMovieListSelect: (MovieList) -> Unit, - modifier: Modifier = Modifier, -) { - var movieListDialog: Boolean by remember { mutableStateOf(false) } - - if (movieListDialog) { - SettingsMovieListDialog( - currentMovieList = currentMovieList, - onMovieListSelect = onMovieListSelect, - onDismissRequest = { - movieListDialog = false - } - ) - } - - ConstraintLayout( - modifier = modifier - .clickable { - movieListDialog = true - } - .testTag("ConstraintLayout") - ) { - val (title, value) = createRefs() - - Text( - text = stringResource(R.string.settings_movie_list), - modifier = Modifier - .constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("TitleText"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - Text( - text = currentMovieList.listText, - modifier = Modifier - .constrainAs(value) { - width = Dimension.wrapContent - height = Dimension.wrapContent - top.linkTo(parent.top) - end.linkTo(parent.end, 16.dp) - bottom.linkTo(parent.bottom) - } - .testTag("ValueText"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - } -} - -@Composable -@DevicePreviews -private fun SettingsMovieListBoxPreview( - @PreviewParameter(MovieListPreviewParameterProvider::class) movieList: MovieList -) { - MoviesTheme { - SettingsMovieListBox( - currentMovieList = movieList, - onMovieListSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsMovieListBoxAmoledPreview( - @PreviewParameter(MovieListPreviewParameterProvider::class) movieList: MovieList -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsMovieListBox( - currentMovieList = movieList, - onMovieListSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListDialog.kt deleted file mode 100644 index 04476499f..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListDialog.kt +++ /dev/null @@ -1,167 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.RadioButtonDefaults -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.listText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.MovieListPreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsMovieListDialog( - currentMovieList: MovieList, - onMovieListSelect: (MovieList) -> Unit, - onDismissRequest: () -> Unit -) { - AlertDialog( - onDismissRequest = onDismissRequest, - confirmButton = { - TextButton( - onClick = onDismissRequest, - modifier = Modifier.testTag("ConfirmTextButton") - ) { - Text( - text = stringResource(R.string.settings_action_cancel), - modifier = Modifier.testTag("ConfirmText"), - style = MaterialTheme.typography.labelLarge.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - } - }, - icon = { - Icon( - imageVector = MoviesIcons.LocalMovies, - contentDescription = MoviesContentDescription.None, - modifier = Modifier.testTag("Icon") - ) - }, - title = { - Text( - text = stringResource(R.string.settings_movie_list), - modifier = Modifier.testTag("Title"), - style = MaterialTheme.typography.headlineSmall.copy( - color = MaterialTheme.colorScheme.onSurface - ) - ) - }, - text = { - SettingMovieListDialogContent( - currentMovieList = currentMovieList, - onMovieListSelect = { movieList -> - onMovieListSelect(movieList) - onDismissRequest() - }, - modifier = Modifier.testTag("Content") - ) - }, - shape = RoundedCornerShape(28.dp), - containerColor = MaterialTheme.colorScheme.surface, - iconContentColor = MaterialTheme.colorScheme.secondary, - titleContentColor = MaterialTheme.colorScheme.onSurface - ) -} - -@Composable -private fun SettingMovieListDialogContent( - currentMovieList: MovieList, - onMovieListSelect: (MovieList) -> Unit, - modifier: Modifier = Modifier -) { - val movieLists: List = listOf( - MovieList.NowPlaying, - MovieList.Popular, - MovieList.TopRated, - MovieList.Upcoming - ) - val scrollState = rememberScrollState() - - Column( - modifier = modifier.verticalScroll(scrollState) - ) { - movieLists.forEach { movieList -> - Row( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable { onMovieListSelect(movieList) }, - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = currentMovieList == movieList, - onClick = null, - colors = RadioButtonDefaults.colors( - selectedColor = MaterialTheme.colorScheme.primary, - unselectedColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6F) - ), - modifier = Modifier.padding(start = 16.dp) - ) - - Text( - text = movieList.listText, - modifier = Modifier.padding(start = 8.dp), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } - } - } -} - -@Composable -@DevicePreviews -private fun SettingsMovieListDialogPreview( - @PreviewParameter(MovieListPreviewParameterProvider::class) movieList: MovieList -) { - MoviesTheme { - SettingsMovieListDialog( - currentMovieList = movieList, - onMovieListSelect = {}, - onDismissRequest = {} - ) - } -} - -@Composable -@Preview -private fun SettingsMovieListDialogAmoledPreview( - @PreviewParameter(MovieListPreviewParameterProvider::class) movieList: MovieList -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsMovieListDialog( - currentMovieList = movieList, - onMovieListSelect = {}, - onDismissRequest = {} - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBox.kt deleted file mode 100644 index 012f39e07..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBox.kt +++ /dev/null @@ -1,110 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import android.Manifest -import android.app.Activity -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.clickable -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults -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.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.ktx.notificationManager -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.compose.SwitchCheckIcon -import org.michaelbel.movies.ui.ktx.appNotificationSettingsIntent -import org.michaelbel.movies.ui.lifecycle.OnResume - -@Composable -fun SettingsPostNotificationsBox( - onShowPermissionSnackbar: () -> Unit, - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val notificationManager = context.notificationManager - var areNotificationsEnabled by remember { mutableStateOf(notificationManager.areNotificationsEnabled()) } - - val resultContract = rememberLauncherForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) {} - - val postNotificationsPermission = rememberLauncherForActivityResult( - ActivityResultContracts.RequestPermission() - ) { granted -> - if (granted) { - areNotificationsEnabled = notificationManager.areNotificationsEnabled() - } else { - val shouldRequest: Boolean = (context as Activity).shouldShowRequestPermissionRationale( - Manifest.permission.POST_NOTIFICATIONS - ) - if (!shouldRequest) { - onShowPermissionSnackbar() - } - } - } - - OnResume { - areNotificationsEnabled = notificationManager.areNotificationsEnabled() - } - - ConstraintLayout( - modifier = modifier - .clickable { - if (areNotificationsEnabled) { - resultContract.launch(context.appNotificationSettingsIntent) - } else { - postNotificationsPermission.launch(Manifest.permission.POST_NOTIFICATIONS) - } - } - .testTag("ConstraintLayout") - ) { - val (title, value) = createRefs() - - Text( - text = stringResource(R.string.settings_post_notifications), - modifier = Modifier - .constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("Text"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - Switch( - checked = areNotificationsEnabled, - onCheckedChange = null, - modifier = Modifier - .constrainAs(value) { - width = Dimension.wrapContent - height = Dimension.wrapContent - top.linkTo(parent.top) - end.linkTo(parent.end, 16.dp) - bottom.linkTo(parent.bottom) - } - .testTag("Switch"), - thumbContent = if (areNotificationsEnabled) { { SwitchCheckIcon() } } else null, - colors = SwitchDefaults.colors( - checkedTrackColor = MaterialTheme.colorScheme.surfaceTint, - checkedIconColor = MaterialTheme.colorScheme.surfaceTint - ) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBox.kt deleted file mode 100644 index 61053dd62..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBox.kt +++ /dev/null @@ -1,74 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsReviewBox( - modifier: Modifier = Modifier -) { - ConstraintLayout( - modifier = modifier.testTag("ConstraintLayout") - ) { - val (title) = createRefs() - - Text( - text = stringResource(R.string.settings_review), - modifier = Modifier - .constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("Text"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } -} - -@Composable -@DevicePreviews -private fun SettingsReviewBoxPreview() { - MoviesTheme { - SettingsReviewBox( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsReviewBoxAmoledPreview() { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsReviewBox( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt deleted file mode 100644 index e346a67a4..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt +++ /dev/null @@ -1,344 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import android.app.Activity -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.Divider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import kotlinx.coroutines.launch -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.localization.model.AppLanguage -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.version.AppVersionData -import org.michaelbel.movies.settings.SettingsViewModel -import org.michaelbel.movies.settings.ktx.iconSnackbarText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.ktx.appNotificationSettingsIntent -import org.michaelbel.movies.ui.ktx.clickableWithoutRipple -import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme -import org.michaelbel.movies.ui.R as UiR - -@Composable -fun SettingsRoute( - onBackClick: () -> Unit, - modifier: Modifier = Modifier, - viewModel: SettingsViewModel = hiltViewModel() -) { - val currentLanguage = AppLanguage.transform(stringResource(UiR.string.language_code)) - val currentTheme by viewModel.currentTheme.collectAsStateWithLifecycle() - val currentFeedView by viewModel.currentFeedView.collectAsStateWithLifecycle() - val currentMovieList by viewModel.currentMovieList.collectAsStateWithLifecycle() - val dynamicColors by viewModel.dynamicColors.collectAsStateWithLifecycle() - val isPlayServicesAvailable by viewModel.isPlayServicesAvailable.collectAsStateWithLifecycle() - val appVersionData by viewModel.appVersionData.collectAsStateWithLifecycle() - - SettingsScreenContent( - onBackClick = onBackClick, - currentLanguage = currentLanguage, - onLanguageSelect = viewModel::selectLanguage, - currentTheme = currentTheme, - onThemeSelect = viewModel::selectTheme, - currentFeedView = currentFeedView, - onFeedViewSelect = viewModel::selectFeedView, - currentMovieList = currentMovieList, - onMovieListSelect = viewModel::selectMovieList, - isDynamicColorsFeatureEnabled = viewModel.isDynamicColorsFeatureEnabled, - dynamicColors = dynamicColors, - onSetDynamicColors = viewModel::setDynamicColors, - isPostNotificationsFeatureEnabled = viewModel.isPostNotificationsFeatureEnabled, - isPlayServicesAvailable = isPlayServicesAvailable, - onRequestReview = viewModel::requestReview, - appVersionData = appVersionData, - modifier = modifier - ) -} - -@Composable -private fun SettingsScreenContent( - onBackClick: () -> Unit, - currentLanguage: AppLanguage, - onLanguageSelect: (AppLanguage) -> Unit, - currentTheme: AppTheme, - onThemeSelect: (AppTheme) -> Unit, - currentFeedView: FeedView, - onFeedViewSelect: (FeedView) -> Unit, - currentMovieList: MovieList, - onMovieListSelect: (MovieList) -> Unit, - isDynamicColorsFeatureEnabled: Boolean, - dynamicColors: Boolean, - onSetDynamicColors: (Boolean) -> Unit, - isPostNotificationsFeatureEnabled: Boolean, - isPlayServicesAvailable: Boolean, - onRequestReview: (Activity) -> Unit, - appVersionData: AppVersionData, - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val scope = rememberCoroutineScope() - val snackbarHostState = remember { SnackbarHostState() } - val topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val lazyListState = rememberLazyListState() - val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} - - val onShowPermissionSnackbar: () -> Unit = { - scope.launch { - val result: SnackbarResult = snackbarHostState.showSnackbar( - message = context.getString(R.string.settings_post_notifications_should_request), - actionLabel = context.getString(R.string.settings_action_go), - duration = SnackbarDuration.Long - ) - if (result == SnackbarResult.ActionPerformed) { - resultContract.launch(context.appNotificationSettingsIntent) - } - } - } - - val onShowSnackbar: (String) -> Unit = { message -> - scope.launch { - snackbarHostState.showSnackbar( - message = message, - duration = SnackbarDuration.Short - ) - } - } - - val onScrollToTop: () -> Unit = { - scope.launch { - lazyListState.animateScrollToItem(0) - } - } - - fun onLaunchReviewFlow() { - when { - !isPlayServicesAvailable -> onShowSnackbar(context.getString(R.string.settings_error_play_services_not_available)) - else -> onRequestReview(context as Activity) - } - } - - Scaffold( - modifier = modifier.nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), - topBar = { - SettingsToolbar( - modifier = Modifier - .fillMaxWidth() - .clickableWithoutRipple { onScrollToTop() }, - topAppBarScrollBehavior = topAppBarScrollBehavior, - onNavigationIconClick = onBackClick - ) - }, - bottomBar = { - SettingsVersionBox( - appVersionData = appVersionData, - modifier = Modifier - .navigationBarsPadding() - .windowInsetsPadding(displayCutoutWindowInsets) - .fillMaxWidth() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - }, - snackbarHost = { - SnackbarHost( - hostState = snackbarHostState - ) - }, - containerColor = MaterialTheme.colorScheme.primaryContainer - ) { innerPadding -> - LazyColumn( - modifier = Modifier - .navigationBarsPadding() - .windowInsetsPadding(displayCutoutWindowInsets), - state = lazyListState, - contentPadding = innerPadding - ) { - item { - SettingsLanguageBox( - currentLanguage = currentLanguage, - onLanguageSelect = onLanguageSelect, - modifier = Modifier.fillMaxWidth().height(52.dp) - ) - } - item { - Divider( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } - item { - SettingsThemeBox( - currentTheme = currentTheme, - onThemeSelect = onThemeSelect, - modifier = Modifier.fillMaxWidth().height(52.dp) - ) - } - item { - Divider( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } - item { - SettingsAppearanceBox( - currentFeedView = currentFeedView, - onFeedViewSelect = onFeedViewSelect, - modifier = Modifier.fillMaxWidth().height(52.dp) - ) - } - item { - Divider( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } - item { - SettingsMovieListBox( - currentMovieList = currentMovieList, - onMovieListSelect = onMovieListSelect, - modifier = Modifier.fillMaxWidth().height(52.dp) - ) - } - item { - Divider( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } - item { - if (isDynamicColorsFeatureEnabled) { - SettingsDynamicColorsBox( - isDynamicColorsEnabled = dynamicColors, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable { onSetDynamicColors(!dynamicColors) } - ) - } - } - item { - Divider( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } - item { - if (isPostNotificationsFeatureEnabled) { - SettingsPostNotificationsBox( - onShowPermissionSnackbar = onShowPermissionSnackbar, - modifier = Modifier.fillMaxWidth().height(52.dp) - ) - } - } - item { - Divider( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } - item { - SettingsReviewBox( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable { onLaunchReviewFlow() } - ) - } - item { - Divider( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } - item { - SettingsAppIconBox( - onAppIconChanged = { iconAlias -> - onShowSnackbar(context.getString(R.string.settings_app_launcher_icon_changed_to, iconAlias.iconSnackbarText(context))) - }, - modifier = Modifier.fillMaxWidth() - ) - } - } - } -} - -@Composable -@DevicePreviews -private fun SettingsScreenContentPreview() { - MoviesTheme { - SettingsScreenContent( - onBackClick = {}, - currentLanguage = AppLanguage.English, - onLanguageSelect = {}, - currentTheme = AppTheme.NightNo, - onThemeSelect = {}, - currentFeedView = FeedView.FeedList, - onFeedViewSelect = {}, - currentMovieList = MovieList.NowPlaying, - onMovieListSelect = {}, - isDynamicColorsFeatureEnabled = true, - dynamicColors = true, - onSetDynamicColors = {}, - isPostNotificationsFeatureEnabled = true, - isPlayServicesAvailable = true, - onRequestReview = {}, - appVersionData = AppVersionData.Empty, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsScreenContentAmoledPreview() { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsScreenContent( - onBackClick = {}, - currentLanguage = AppLanguage.English, - onLanguageSelect = {}, - currentTheme = AppTheme.NightNo, - onThemeSelect = {}, - currentFeedView = FeedView.FeedList, - onFeedViewSelect = {}, - currentMovieList = MovieList.NowPlaying, - onMovieListSelect = {}, - isDynamicColorsFeatureEnabled = true, - dynamicColors = true, - onSetDynamicColors = {}, - isPostNotificationsFeatureEnabled = true, - isPlayServicesAvailable = true, - onRequestReview = {}, - appVersionData = AppVersionData.Empty, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBox.kt deleted file mode 100644 index a286c31ee..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBox.kt +++ /dev/null @@ -1,124 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.MaterialTheme -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.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.themeText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.ThemePreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsThemeBox( - currentTheme: AppTheme, - onThemeSelect: (AppTheme) -> Unit, - modifier: Modifier = Modifier -) { - var themeDialog: Boolean by remember { mutableStateOf(false) } - - if (themeDialog) { - SettingThemeDialog( - currentTheme = currentTheme, - onThemeSelect = onThemeSelect, - onDismissRequest = { - themeDialog = false - } - ) - } - - ConstraintLayout( - modifier = modifier - .clickable { - themeDialog = true - } - .testTag("ConstraintLayout") - ) { - val (title, value) = createRefs() - - Text( - text = stringResource(R.string.settings_theme), - modifier = Modifier - .constrainAs(title) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 16.dp) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - } - .testTag("TitleText"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - Text( - text = currentTheme.themeText, - modifier = Modifier - .constrainAs(value) { - width = Dimension.wrapContent - height = Dimension.wrapContent - top.linkTo(parent.top) - end.linkTo(parent.end, 16.dp) - bottom.linkTo(parent.bottom) - } - .testTag("ValueText"), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - } -} - -@Composable -@DevicePreviews -private fun SettingsThemeBoxPreview( - @PreviewParameter(ThemePreviewParameterProvider::class) theme: AppTheme -) { - MoviesTheme { - SettingsThemeBox( - currentTheme = theme, - onThemeSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsThemeBoxAmoledPreview( - @PreviewParameter(ThemePreviewParameterProvider::class) theme: AppTheme -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsThemeBox( - currentTheme = theme, - onThemeSelect = {}, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt deleted file mode 100644 index f955f068d..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt +++ /dev/null @@ -1,161 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.RadioButtonDefaults -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings.ktx.themeText -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.ThemePreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -internal fun SettingThemeDialog( - currentTheme: AppTheme, - onThemeSelect: (AppTheme) -> Unit, - onDismissRequest: () -> Unit -) { - AlertDialog( - onDismissRequest = onDismissRequest, - confirmButton = { - TextButton( - onClick = onDismissRequest, - modifier = Modifier.testTag("ConfirmTextButton") - ) { - Text( - text = stringResource(R.string.settings_action_cancel), - modifier = Modifier.testTag("ConfirmText"), - style = MaterialTheme.typography.labelLarge.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - } - }, - icon = { - Icon( - painter = painterResource(MoviesIcons.ThemeLightDark), - contentDescription = stringResource(MoviesContentDescription.ThemeIcon), - modifier = Modifier.testTag("Icon") - ) - }, - title = { - Text( - text = stringResource(R.string.settings_theme), - modifier = Modifier.testTag("Title"), - style = MaterialTheme.typography.headlineSmall.copy( - color = MaterialTheme.colorScheme.onSurface - ) - ) - }, - text = { - SettingThemeDialogContent( - currentTheme = currentTheme, - onThemeSelect = { theme -> - onThemeSelect(theme) - onDismissRequest() - }, - modifier = Modifier.testTag("Content") - ) - }, - shape = RoundedCornerShape(28.dp), - containerColor = MaterialTheme.colorScheme.surface, - iconContentColor = MaterialTheme.colorScheme.secondary, - titleContentColor = MaterialTheme.colorScheme.onSurface - ) -} - -@Composable -private fun SettingThemeDialogContent( - currentTheme: AppTheme, - onThemeSelect: (AppTheme) -> Unit, - modifier: Modifier = Modifier -) { - val scrollState = rememberScrollState() - - Column( - modifier = modifier.verticalScroll(scrollState) - ) { - AppTheme.VALUES.forEach { theme: AppTheme -> - Row( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable { onThemeSelect(theme) }, - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = currentTheme == theme, - onClick = null, - colors = RadioButtonDefaults.colors( - selectedColor = MaterialTheme.colorScheme.primary, - unselectedColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6F) - ), - modifier = Modifier.padding(start = 16.dp) - ) - - Text( - text = theme.themeText, - modifier = Modifier.padding(start = 8.dp), - style = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } - } - } -} - -@Composable -@DevicePreviews -private fun SettingThemeDialogPreview( - @PreviewParameter(ThemePreviewParameterProvider::class) theme: AppTheme -) { - MoviesTheme { - SettingThemeDialog( - currentTheme = theme, - onThemeSelect = {}, - onDismissRequest = {} - ) - } -} - -@Composable -@Preview -private fun SettingThemeDialogAmoledPreview( - @PreviewParameter(ThemePreviewParameterProvider::class) theme: AppTheme -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingThemeDialog( - currentTheme = theme, - onThemeSelect = {}, - onDismissRequest = {} - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt deleted file mode 100644 index d09dc2cee..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt +++ /dev/null @@ -1,79 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.compose.iconbutton.BackIcon -import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -internal fun SettingsToolbar( - modifier: Modifier = Modifier, - topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), - onNavigationIconClick: () -> Unit, -) { - TopAppBar( - title = { - Text( - text = stringResource(R.string.settings_title), - modifier = Modifier.testTag("TitleText"), - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - }, - modifier = modifier.testTag("TopAppBar"), - navigationIcon = { - BackIcon( - onClick = onNavigationIconClick, - modifier = Modifier - .windowInsetsPadding(displayCutoutWindowInsets) - .testTag("BackIconButton") - ) - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - scrolledContainerColor = MaterialTheme.colorScheme.inversePrimary - ), - scrollBehavior = topAppBarScrollBehavior - ) -} - -@Composable -@DevicePreviews -private fun SettingsToolbarPreview() { - MoviesTheme { - SettingsToolbar( - modifier = Modifier.statusBarsPadding(), - onNavigationIconClick = {} - ) - } -} - -@Composable -@Preview -private fun SettingsToolbarAmoledPreview() { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsToolbar( - modifier = Modifier.statusBarsPadding(), - onNavigationIconClick = {} - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt deleted file mode 100644 index 27d0913a5..000000000 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt +++ /dev/null @@ -1,164 +0,0 @@ -package org.michaelbel.movies.settings.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ChainStyle -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.version.AppVersionData -import org.michaelbel.movies.settings_impl.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.preview.provider.VersionPreviewParameterProvider -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun SettingsVersionBox( - appVersionData: AppVersionData, - modifier: Modifier = Modifier -) { - ConstraintLayout( - modifier = modifier.testTag("ConstraintLayout") - ) { - val (icon, version, code, flavor, debug) = createRefs() - createHorizontalChain(icon, version, code, flavor, debug, chainStyle = ChainStyle.Packed) - - Icon( - imageVector = MoviesIcons.MovieFilter, - contentDescription = MoviesContentDescription.None, - modifier = Modifier - .constrainAs(icon) { - width = Dimension.value(24.dp) - height = Dimension.value(24.dp) - start.linkTo(parent.start) - top.linkTo(parent.top) - end.linkTo(version.start) - bottom.linkTo(parent.bottom) - } - .testTag("Icon"), - tint = MaterialTheme.colorScheme.primary - ) - - Text( - text = stringResource(R.string.settings_app_version_name, appVersionData.version), - modifier = Modifier - .constrainAs(version) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(icon.end) - top.linkTo(icon.top) - end.linkTo(code.start) - bottom.linkTo(icon.bottom) - } - .padding(start = 4.dp) - .testTag("TitleText"), - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - Text( - text = stringResource(R.string.settings_app_version_code, appVersionData.code), - modifier = Modifier - .constrainAs(code) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(version.end) - top.linkTo(icon.top) - end.linkTo(flavor.start) - bottom.linkTo(icon.bottom) - } - .padding(start = 2.dp) - .testTag("ValueText"), - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.primary - ) - ) - - Text( - text = appVersionData.flavor, - modifier = Modifier - .constrainAs(flavor) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(code.end) - top.linkTo(icon.top) - end.linkTo(if (appVersionData.isDebug) debug.start else parent.end) - bottom.linkTo(icon.bottom) - } - .padding(start = 2.dp) - .testTag("FlavorText"), - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - - if (appVersionData.isDebug) { - Text( - text = stringResource(R.string.settings_app_debug), - modifier = Modifier - .constrainAs(debug) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(flavor.end) - top.linkTo(icon.top) - end.linkTo(parent.end) - bottom.linkTo(icon.bottom) - } - .padding(start = 2.dp) - .testTag("DebugText"), - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } - } -} - -@Composable -@DevicePreviews -private fun SettingsVersionBoxPreview( - @PreviewParameter(VersionPreviewParameterProvider::class) appVersionData: AppVersionData -) { - MoviesTheme { - SettingsVersionBox( - appVersionData = appVersionData, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} - -@Composable -@Preview -private fun SettingsVersionBoxAmoledPreview( - @PreviewParameter(VersionPreviewParameterProvider::class) appVersionData: AppVersionData -) { - MoviesTheme( - theme = AppTheme.Amoled - ) { - SettingsVersionBox( - appVersionData = appVersionData, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/main/res/values-ru/strings.xml b/feature/settings-impl/src/main/res/values-ru/strings.xml deleted file mode 100644 index a77b2c4b5..000000000 --- a/feature/settings-impl/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,36 +0,0 @@ - - Настройки - Тема - По умолчанию - Светлая - Темная - Amoled - Цвета обоев - Уведомления - Оценить приложение - Уведомления отключены. Активируйте их в настройках приложения - Перейти - Сервисы Google play недоступны - Приложение должно быть загружено из Google Play - Язык - English - Русский - Вид - Список - Сетка - Список фильмов - Премьеры - Популярные - Топ - Скоро - Иконка Приложения - Красная - Фиолетовая - Коричневая - Amoled - Иконка приложения изменена на %s - Movies v%s - (%s) - Debug - Отмена - \ No newline at end of file diff --git a/feature/settings-impl/src/main/res/values/strings.xml b/feature/settings-impl/src/main/res/values/strings.xml deleted file mode 100644 index 8f682fa3b..000000000 --- a/feature/settings-impl/src/main/res/values/strings.xml +++ /dev/null @@ -1,36 +0,0 @@ - - Settings - Theme - Follow System - Light - Dark - Amoled - Dynamic Colors - Notifications - Review - Notifications are disabled. Please go to app settings to activate them - Go - Google play services are not available - Your version of app must be downloaded from Google Play - Language - English - Русский - Appearance - List - Grid - Movie List - Now Playing - Popular - Top Rated - Upcoming - App Icon - Red - Purple - Brown - Amoled - App icon changed to %s - Movies v%s - (%s) - Debug - Cancel - \ No newline at end of file diff --git a/feature/settings-kmp/.gitignore b/feature/settings-kmp/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/feature/settings-kmp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/settings-kmp/build.gradle.kts b/feature/settings-kmp/build.gradle.kts new file mode 100644 index 000000000..1556294ce --- /dev/null +++ b/feature/settings-kmp/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget { + compilations.all { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + } + } + jvm("desktop") + + sourceSets { + commonMain.dependencies { + implementation(project(":core:navigation-kmp")) + implementation(project(":core:ui-kmp")) + api(project(":feature:settings-impl-kmp")) + } + val desktopMain by getting + desktopMain.dependencies { + implementation(compose.material) + implementation(compose.material3) + implementation(libs.precompose) + } + } +} + +android { + namespace = "org.michaelbel.movies.settings_kmp" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + + lint { + quiet = true + abortOnError = false + ignoreWarnings = true + checkDependencies = true + lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") + } +} \ No newline at end of file diff --git a/feature/settings/src/main/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt b/feature/settings-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt similarity index 100% rename from feature/settings/src/main/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt rename to feature/settings-kmp/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt diff --git a/feature/settings/src/main/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt b/feature/settings-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt similarity index 100% rename from feature/settings/src/main/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt rename to feature/settings-kmp/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt diff --git a/feature/settings-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt b/feature/settings-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt new file mode 100644 index 000000000..199b770e8 --- /dev/null +++ b/feature/settings-kmp/src/desktopMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.settings + +import moe.tlaster.precompose.navigation.Navigator +import moe.tlaster.precompose.navigation.RouteBuilder +import org.michaelbel.movies.settings.ui.SettingsRoute + +fun Navigator.navigateToSettings() { + navigate(SettingsDestination.route) +} + +fun RouteBuilder.settingsGraph( + navigateBack: () -> Unit +) { + scene( + route = SettingsDestination.route + ) { + SettingsRoute( + onBackClick = navigateBack + ) + } +} \ No newline at end of file diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts deleted file mode 100644 index c11ef7cbf..000000000 --- a/feature/settings/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) -} - -android { - namespace = "org.michaelbel.movies.settings" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} - -dependencies { - implementation(project(":feature:settings-impl")) - - lintChecks(libs.lint.checks) -} \ No newline at end of file diff --git a/feature/settings/src/main/AndroidManifest.xml b/feature/settings/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/feature/settings/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index c44943ed7..6d104d72a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,6 @@ android.defaults.buildFeatures.resValues=false # https://d.android.com/reference/tools/gradle-api/8.0/com/android/build/api/dsl/BuildFeatures#shaders() # Flag to enable Shader compilation. # Default value is true. -android.defaults.buildFeatures.shaders=false \ No newline at end of file +android.defaults.buildFeatures.shaders=false + +xcodeproj=./ios-app \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5b121c075..07181b69c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,64 +1,69 @@ # Compose to Kotlin Compatibility Map: https://d.android.com/jetpack/androidx/releases/compose-kotlin + [versions] min-sdk = "23" compile-sdk = "34" target-sdk = "34" -gradle = "8.2.2" -kotlin = "1.9.22" -kotlin-ksp = "1.9.22-1.0.17" -kotlin-coroutines = "1.7.3" -kotlin-serialization-json = "1.6.2" -detekt = "1.23.5" -spotless = "6.25.0" +jdk = "17" +agp = "8.3.1" +kotlin = "1.9.23" +kotlinx-coroutines = "1.8.0" +kotlinx-datetime = "0.5.0" +kotlinx-serialization-json = "1.6.3" +google-ksp = "1.9.23-1.0.19" google-services = "4.4.1" +google-services-base = "18.3.0" +google-services-instantapps = "18.0.1" +google-play-core-ktx = "1.8.1" +google-material = "1.11.0" +google-material-compose-theme-adapter = "1.2.1" +google-firebase-analytics-ktx = "21.6.1" +google-firebase-appdistribution = "4.2.0" +google-firebase-config-ktx = "21.6.3" +google-firebase-crashlytics-plugin = "2.9.9" +google-firebase-crashlytics-ktx = "18.6.3" +google-firebase-messaging-ktx = "23.4.1" huawei-services = "1.6.0.300" -gms-play-services-ads = "22.6.0" -gms-play-services-base = "18.3.0" -gms-play-services-instantapps = "18.0.1" -firebase-analytics-ktx = "21.5.1" -firebase-appdistribution = "4.0.1" -firebase-config-ktx = "21.6.1" -firebase-crashlytics-plugin = "2.9.9" -firebase-crashlytics-ktx = "18.6.2" -firebase-messaging-ktx = "23.4.1" -play-core-ktx = "1.8.1" -accompanist = "0.34.0" -material = "1.11.0" -material-compose-theme-adapter = "1.2.1" -hilt = "2.50" -androidx-compose-foundation = "1.6.1" -androidx-compose-runtime = "1.6.1" -androidx-compose-ui = "1.6.1" -androidx-compose-compiler = "1.5.9" -androidx-compose-material = "1.6.1" -androidx-compose-material3 = "1.2.0" androidx-appcompat = "1.6.1" androidx-activity = "1.8.2" androidx-autofill = "1.1.0" -androidx-browser = "1.7.0" +androidx-benchmark = "1.2.3" +androidx-biometric-ktx = "1.2.0-alpha05" +androidx-browser = "1.8.0" +androidx-collection = "1.4.0" +androidx-compose-animation = "1.6.4" +androidx-compose-foundation = "1.6.4" +androidx-compose-runtime = "1.6.4" +androidx-compose-ui = "1.6.4" +androidx-compose-compiler = "1.5.11" +androidx-compose-material-icons-extended = "1.6.4" +androidx-compose-material3 = "1.2.1" androidx-core-ktx = "1.12.0" androidx-core-splashscreen = "1.0.1" -androidx-constraintlayout = "1.0.1" +androidx-datastore = "1.0.0" +androidx-datastore-core-okio-jvm = "1.1.0-beta02" androidx-lifecycle = "2.7.0" -androidx-hilt-navigation-compose = "1.1.0" -androidx-hilt-work = "1.1.0" +androidx-lifecycle-viewmodel = "2.8.0-alpha03" +androidx-glance = "1.0.0" +androidx-media3 = "1.3.0" androidx-navigation = "2.7.7" -androidx-paging = "3.2.1" -androidx-datastore = "1.0.0" -androidx-startup = "1.1.1" androidx-palette-ktx = "1.0.0" +androidx-paging = "3.2.1" +androidx-profile-installer = "1.3.1" androidx-room = "2.6.1" +androidx-startup = "1.1.1" androidx-test = "1.5.2" androidx-test-ext = "1.1.5" -androidx-test-uiautomator = "2.2.0" -androidx-espresso-core = "3.5.1" -androidx-benchmark = "1.2.3" -androidx-profile-installer = "1.3.1" +androidx-test-uiautomator = "2.3.0" +androidx-test-espresso = "3.5.1" +androidx-test-espresso-device = "1.0.0-alpha08" androidx-work = "2.9.0" -coil = "2.5.0" +compose = "1.6.1" +detekt = "1.23.6" +spotless = "6.25.0" +coil = "2.6.0" +coil3 = "3.0.0-alpha06" okhttp = "4.12.0" -retrofit = "2.9.0" -retrofit-converter-serialization = "1.0.0" chucker = "4.0.0" timber = "5.0.1" javapoet = "1.13.0" @@ -66,80 +71,119 @@ junit = "4.13.2" lint-checks = "1.3.1" palantir-git = "3.0.0" flaker = "0.1.2" +ktor = "2.3.9" +leakcanary = "3.0-alpha-1" +robolectric = "4.11.1" +mockito = "5.11.0" +kaspresso = "1.5.5" +mockk = "1.13.10" +turbine = "1.1.0" +barista = "4.3.0" +kotest = "5.8.1" +sqldelight = "2.0.1" +koin = "3.5.3" +precompose = "1.6.0-rc05" +constraintlayout-compose-multiplatform = "0.3.1" +kmp-viewmodel = "0.7.1" [libraries] -kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } -kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlin-coroutines" } -kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" } -kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serialization-json" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } -gms-play-services-ads = { module = "com.google.android.gms:play-services-ads", version.ref = "gms-play-services-ads" } -gms-play-services-base = { module = "com.google.android.gms:play-services-base", version.ref = "gms-play-services-base" } -gms-play-services-instantapps = { module = "com.google.android.gms:play-services-instantapps", version.ref = "gms-play-services-instantapps" } -firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx", version.ref = "firebase-analytics-ktx" } -firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx", version.ref = "firebase-config-ktx" } -firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx", version.ref = "firebase-crashlytics-ktx" } -firebase-messaging-ktx = { module = "com.google.firebase:firebase-messaging-ktx", version.ref = "firebase-messaging-ktx" } -play-core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "play-core-ktx" } -accompanist-appcompat-theme = { module = "com.google.accompanist:accompanist-appcompat-theme", version.ref = "accompanist" } -accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist" } -accompanist-insets-ui = { module = "com.google.accompanist:accompanist-insets-ui", version.ref = "accompanist" } -accompanist-navigation-animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" } -material = { module = "com.google.android.material:material", version.ref = "material" } -material-compose-theme-adapter = { module = "com.google.android.material:compose-theme-adapter", version.ref = "material-compose-theme-adapter" } -hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } -hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } +google-services-base = { module = "com.google.android.gms:play-services-base", version.ref = "google-services-base" } +google-services-instantapps = { module = "com.google.android.gms:play-services-instantapps", version.ref = "google-services-instantapps" } +google-play-core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "google-play-core-ktx" } +google-material = { module = "com.google.android.material:material", version.ref = "google-material" } +google-material-compose-theme-adapter = { module = "com.google.android.material:compose-theme-adapter", version.ref = "google-material-compose-theme-adapter" } +google-firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx", version.ref = "google-firebase-analytics-ktx" } +google-firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx", version.ref = "google-firebase-config-ktx" } +google-firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx", version.ref = "google-firebase-crashlytics-ktx" } +google-firebase-messaging-ktx = { module = "com.google.firebase:firebase-messaging-ktx", version.ref = "google-firebase-messaging-ktx" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +androidx-appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "androidx-appcompat" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-autofill = { module = "androidx.autofill:autofill", version.ref = "androidx-autofill" } +androidx-benchmark-junit = { module = "androidx.benchmark:benchmark-junit4", version.ref = "androidx-benchmark" } +androidx-benchmark-macro-junit = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidx-benchmark" } +androidx-biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometric-ktx" } +androidx-browser = { module = "androidx.browser:browser", version.ref = "androidx-browser" } +androidx-collection = { module = "androidx.collection:collection", version.ref = "androidx-collection" } +androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "androidx-compose-animation" } androidx-compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "androidx-compose-compiler" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidx-compose-foundation" } androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout", version.ref = "androidx-compose-foundation" } -androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "androidx-compose-material" } +androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "androidx-compose-material-icons-extended" } androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "androidx-compose-material3" } androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "androidx-compose-runtime" } androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidx-compose-ui" } androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "androidx-compose-ui" } -androidx-compose-ui-viewbinding = { module = "androidx.compose.ui:ui-viewbinding", version.ref = "androidx-compose-ui" } androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-compose-ui" } androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-compose-ui" } androidx-compose-ui-util = { module = "androidx.compose.ui:ui-util", version.ref = "androidx-compose-ui" } -androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } -androidx-appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "androidx-appcompat" } -androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } -androidx-autofill = { module = "androidx.autofill:autofill", version.ref = "androidx-autofill" } -androidx-browser = { module = "androidx.browser:browser", version.ref = "androidx-browser" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" } androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" } -androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "androidx-constraintlayout" } +androidx-datastore-core = { module = "androidx.datastore:datastore-core", version.ref = "androidx-datastore" } +androidx-datastore-core-okio-jvm = { module = "androidx.datastore:datastore-core-okio-jvm", version.ref = "androidx-datastore-core-okio-jvm" } +androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" } +androidx-datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "androidx-datastore" } +androidx-glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "androidx-glance" } +androidx-glance-material3 = { module = "androidx.glance:glance-material3", version.ref = "androidx-glance" } androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle-viewmodel" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" } -androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidx-hilt-navigation-compose" } -androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx-hilt-work" } -androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx-hilt-work" } +androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" } +androidx-media3-exoplayer-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "androidx-media3" } +androidx-media3-exoplayer-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "androidx-media3" } +androidx-media3-exoplayer-smoothstreaming = { module = "androidx.media3:media3-exoplayer-smoothstreaming", version.ref = "androidx-media3" } +androidx-media3-exoplayer-rtsp = { module = "androidx.media3:media3-exoplayer-rtsp", version.ref = "androidx-media3" } +androidx-media3-exoplayer-midi = { module = "androidx.media3:media3-exoplayer-midi", version.ref = "androidx-media3" } +androidx-media3-exoplayer-ima = { module = "androidx.media3:media3-exoplayer-ima", version.ref = "androidx-media3" } +androidx-media3-datasource-cronet = { module = "androidx.media3:media3-datasource-cronet", version.ref = "androidx-media3" } +androidx-media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "androidx-media3" } +androidx-media3-datasource-rtmp = { module = "androidx.media3:media3-datasource-rtmp", version.ref = "androidx-media3" } +androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "androidx-media3" } +androidx-media3-ui-leanback = { module = "androidx.media3:media3-ui-leanback", version.ref = "androidx-media3" } +androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "androidx-media3" } +androidx-media3-extractor = { module = "androidx.media3:media3-extractor", version.ref = "androidx-media3" } +androidx-media3-cast = { module = "androidx.media3:media3-cast", version.ref = "androidx-media3" } +androidx-media3-exoplayer-workmanager = { module = "androidx.media3:media3-exoplayer-workmanager", version.ref = "androidx-media3" } +androidx-media3-transformer = { module = "androidx.media3:media3-transformer", version.ref = "androidx-media3" } +androidx-media3-effect = { module = "androidx.media3:media3-effect", version.ref = "androidx-media3" } +androidx-media3-muxer = { module = "androidx.media3:media3-muxer", version.ref = "androidx-media3" } +androidx-media3-test-utils = { module = "androidx.media3:media3-test-utils", version.ref = "androidx-media3" } +androidx-media3-test-utils-robolectric = { module = "androidx.media3:media3-test-utils-robolectric", version.ref = "androidx-media3" } +androidx-media3-container = { module = "androidx.media3:media3-container", version.ref = "androidx-media3" } +androidx-media3-database = { module = "androidx.media3:media3-database", version.ref = "androidx-media3" } +androidx-media3-decoder = { module = "androidx.media3:media3-decoder", version.ref = "androidx-media3" } +androidx-media3-datasource = { module = "androidx.media3:media3-datasource", version.ref = "androidx-media3" } +androidx-media3-common = { module = "androidx.media3:media3-common", version.ref = "androidx-media3" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } -androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" } -androidx-datastore-core = { module = "androidx.datastore:datastore-core", version.ref = "androidx-datastore" } -androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" } -androidx-datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "androidx-datastore" } -androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" } androidx-palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "androidx-palette-ktx" } +androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" } +androidx-profile-installer = { module = "androidx.profileinstaller:profileinstaller", version.ref = "androidx-profile-installer" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "androidx-room" } +androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" } androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test" } androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" } androidx-test-ext-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-ext" } -androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso-core" } -androidx-benchmark-junit = { module = "androidx.benchmark:benchmark-junit4", version.ref = "androidx-benchmark" } -androidx-benchmark-macro-junit = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidx-benchmark" } +androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } +espresso-test-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "androidx-test-espresso" } +androidx-test-espresso-device = { module = "androidx.test.espresso:espresso-device", version.ref = "androidx-test-espresso-device" } +androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "androidx-test-espresso" } androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidx-test-uiautomator" } -androidx-profile-installer = { module = "androidx.profileinstaller:profileinstaller", version.ref = "androidx-profile-installer" } androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } +coil3-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" } okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } -retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } -retrofit-converter-serialization = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit-converter-serialization" } chucker-library = { module = "com.github.chuckerteam.chucker:library", version.ref = "chucker" } chucker-library-no-op = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } @@ -148,22 +192,58 @@ junit = { module = "junit:junit", version.ref = "junit" } lint-checks = { module = "com.slack.lint.compose:compose-lint-checks", version.ref = "lint-checks" } flaker-android-okhttp = { module = "io.github.rotbolt:flaker-android-okhttp", version.ref = "flaker" } flaker-android-okhttp-noop = { module = "io.github.rotbolt:flaker-android-okhttp-noop", version.ref = "flaker" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } +ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } +robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } +kaspresso = { module = "com.kaspersky.android-components:kaspresso", version.ref = "kaspresso" } +kaspresso-allure-support = { module = "com.kaspersky.android-components:kaspresso-allure-support", version.ref = "kaspresso" } +kaspresso-compose-support = { module = "com.kaspersky.android-components:kaspresso-compose-support", version.ref = "kaspresso" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } +mockk-agent = { module = "io.mockk:mockk-agent", version.ref = "mockk" } +turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } +barista = { module = "com.adevinta.android:barista", version.ref = "barista" } +kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } +kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } +kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } +sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" } +sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } +sqldelight-coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } +sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" } +sqldelight-sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } +koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } +koin-androidx-workmanager = { module = "io.insert-koin:koin-androidx-workmanager", version.ref = "koin" } +koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +koin-core-jvm = { module = "io.insert-koin:koin-core-jvm", version.ref = "koin" } +koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } +koin-test-junit4 = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" } +precompose = { module = "moe.tlaster:precompose", version.ref = "precompose" } +constraintlayout-compose-multiplatform = { module = "tech.annexflow.compose:constraintlayout-compose-multiplatform", version.ref = "constraintlayout-compose-multiplatform" } +kmp-viewmodel = { module = "io.github.hoc081098:kmp-viewmodel", version.ref = "kmp-viewmodel" } -gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "gradle" } +gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } [bundles] -accompanist = [ - "accompanist-appcompat-theme", - "accompanist-drawablepainter", - "accompanist-insets-ui", - "accompanist-navigation-animation" +kotlinx-coroutines-common = [ + "kotlinx-coroutines-core" ] -appcompat = [ +kotlinx-coroutines-android = [ + "kotlinx-coroutines-android" +] +androidx-appcompat = [ "androidx-appcompat", "androidx-appcompat-resources" ] -compose = [ +androidx-compose = [ + "androidx-compose-animation", "androidx-compose-compiler", "androidx-compose-foundation", "androidx-compose-foundation-layout", @@ -172,58 +252,130 @@ compose = [ "androidx-compose-runtime", "androidx-compose-ui", "androidx-compose-ui-tooling", - "androidx-compose-ui-viewbinding", "androidx-compose-ui-util" ] -datastore = [ +androidx-glance = [ + "androidx-glance-appwidget", + "androidx-glance-material3" +] +androidx-media3 = [ + "androidx-media3-exoplayer", + "androidx-media3-exoplayer-dash", + "androidx-media3-exoplayer-hls", + "androidx-media3-exoplayer-smoothstreaming", + "androidx-media3-exoplayer-rtsp", + "androidx-media3-exoplayer-midi", + "androidx-media3-exoplayer-ima", + "androidx-media3-datasource-cronet", + "androidx-media3-datasource-okhttp", + "androidx-media3-datasource-rtmp", + "androidx-media3-ui", + "androidx-media3-ui-leanback", + "androidx-media3-session", + "androidx-media3-extractor", + "androidx-media3-cast", + "androidx-media3-exoplayer-workmanager", + "androidx-media3-transformer", + "androidx-media3-effect", + "androidx-media3-muxer", + "androidx-media3-test-utils", + "androidx-media3-test-utils-robolectric", + "androidx-media3-container", + "androidx-media3-database", + "androidx-media3-decoder", + "androidx-media3-datasource", + "androidx-media3-common" +] +androidx-room = [ + "androidx-room-runtime", + "androidx-room-ktx", + "androidx-room-paging" +] +androidx-test-espresso = [ + "androidx-test-espresso-core", + "espresso-test-espresso-contrib", + "androidx-test-espresso-device", + "androidx-test-espresso-idling-resource" +] +datastore-common = [ "androidx-datastore-core", - "androidx-datastore-preferences", "androidx-datastore-preferences-core" ] -gms = [ -# "gms-play-services-ads", - "gms-play-services-base", - "gms-play-services-instantapps" +datastore-android = [ + "androidx-datastore-preferences" +] +datastore-desktop = [ + "androidx-datastore-core-okio-jvm" ] -kotlin-coroutines = [ - "kotlin-coroutines-android", - "kotlin-coroutines-core" +lifecycle-common = [ + "androidx-lifecycle-viewmodel" ] -lifecycle = [ +lifecycle-android = [ "androidx-lifecycle-runtime-compose", - "androidx-lifecycle-viewmodel-compose", + #"androidx-lifecycle-viewmodel-compose", "androidx-lifecycle-process" ] -firebase = [ - "firebase-analytics-ktx", - "firebase-config-ktx", - "firebase-crashlytics-ktx", - "firebase-messaging-ktx" +paging-common = [ + "androidx-paging-compose" ] -material = [ - "material", - "material-compose-theme-adapter" +google-firebase = [ + "google-firebase-analytics-ktx", + "google-firebase-config-ktx", + "google-firebase-crashlytics-ktx", + "google-firebase-messaging-ktx" ] -room = [ - "androidx-room-runtime", - "androidx-room-ktx", - "androidx-room-paging" +google-material = [ + "google-material", + "google-material-compose-theme-adapter" +] +google-services = [ + "google-services-base", + "google-services-instantapps" +] +ktor-common = [ + "ktor-client-core", + "ktor-client-cio", + "ktor-client-content-negotiation", + "ktor-serialization-kotlinx-json" +] +ktor-android = [ + "ktor-client-okhttp", + "ktor-client-android", +] +koin-common = [ + "koin-core" +] +koin-android = [ + "koin-android", + "koin-androidx-compose", + "koin-androidx-workmanager" +] +coil-common = [ + "coil3-compose" +] +constraintlayout-common = [ + "constraintlayout-compose-multiplatform" ] [plugins] -kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "kotlin-ksp" } +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +android-dynamic-feature = { id = "com.android.dynamic-feature", version.ref = "agp" } +android-test = { id = "com.android.test", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +compose = { id = "org.jetbrains.compose", version.ref = "compose" } +google-ksp = { id = "com.google.devtools.ksp", version.ref = "google-ksp" } google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } +google-firebase-appdistribution = { id = "com.google.firebase.appdistribution", version.ref = "google-firebase-appdistribution" } +google-firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "google-firebase-crashlytics-plugin" } huawei-services = { id = "com.huawei.agconnect", version.ref = "huawei-services" } -firebase-appdistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebase-appdistribution" } -firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-plugin" } +sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } -hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } androidx-navigation-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "androidx-navigation" } -application = { id = "com.android.application", version.ref = "gradle" } -library = { id = "com.android.library", version.ref = "gradle" } -dynamic-feature = { id = "com.android.dynamic-feature", version.ref = "gradle" } -test = { id = "com.android.test", version.ref = "gradle" } palantir-git = { id = "com.palantir.git-version", version.ref = "palantir-git" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca5a3b6ad..29d27311e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,5 @@ -#Sun Apr 04 22:00:34 MSK 2021 -# https://developer.android.com/build/releases/gradle-plugin distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/instant/build.gradle.kts b/instant/build.gradle.kts index 7ee1713c7..08f7ffc04 100644 --- a/instant/build.gradle.kts +++ b/instant/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - alias(libs.plugins.dynamic.feature) - alias(libs.plugins.kotlin) + alias(libs.plugins.android.dynamic.feature) + alias(libs.plugins.kotlin.android) } android { @@ -17,7 +17,7 @@ android { } productFlavors { - /*create("foss") { + /*create("foss") { // todo Uncomment to create a signed release dimension = "version" } create("hms") { @@ -33,17 +33,16 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.toVersion(libs.versions.jdk.get().toInt()) + targetCompatibility = JavaVersion.toVersion(libs.versions.jdk.get().toInt()) } } dependencies { - implementation(project(":android-app")) - implementation(project(":core:common")) - implementation(project(":core:ui")) - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.appcompat) - implementation(libs.androidx.compose.material3) - implementation(libs.gms.play.services.instantapps) + implementation(project(":androidApp")) + implementation(project(":core:common-kmp")) + implementation(project(":core:ui-kmp")) + implementation(libs.bundles.androidx.appcompat) + implementation(libs.bundles.androidx.compose) + implementation(libs.google.services.instantapps) } \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 000000000..f8218e05a --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,391 @@ + // !$*UTF8*$! + { + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + + /* Begin PBXBuildFile section */ +058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; +058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; + 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; + /* End PBXBuildFile section */ + + /* Begin PBXCopyFilesBuildPhase section */ + 7555FFB4242A642300829871 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + /* End PBXCopyFilesBuildPhase section */ + + /* Begin PBXFileReference section */ + 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; +058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + /* End PBXFileReference section */ + + /* Begin PBXFrameworksBuildPhase section */ + 7555FF78242A565900829871 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + /* End PBXFrameworksBuildPhase section */ + + /* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; +}; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + 7555FF7D242A565900829871 /* iosApp */, + 7555FF7C242A565900829871 /* Products */, + 7555FFB0242A642200829871 /* Frameworks */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* iosApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 058557BA273AAA24004C7B11 /* Assets.xcassets */, + 7555FF82242A565900829871 /* ContentView.swift */, + 7555FF8C242A565B00829871 /* Info.plist */, + 2152FB032600AC8F00CF470E /* iOSApp.swift */, + 058557D7273AAEEB004C7B11 /* Preview Content */, + ); + path = iosApp; + sourceTree = ""; + }; + 7555FFB0242A642200829871 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + /* End PBXGroup section */ + + /* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + 7555FFB5242A651A00829871 /* ShellScript */, + 7555FF77242A565900829871 /* Sources */, + 7555FF78242A565900829871 /* Frameworks */, + 7555FF79242A565900829871 /* Resources */, + 7555FFB4242A642300829871 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + productName = iosApp; + productReference = 7555FF7B242A565900829871 /* iosApp.app */; + productType = "com.apple.product-type.application"; + }; + /* End PBXNativeTarget section */ + + /* Begin PBXProject section */ + 7555FF73242A565900829871 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = orgName; + TargetAttributes = { + 7555FF7A242A565900829871 = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7555FF72242A565900829871; + productRefGroup = 7555FF7C242A565900829871 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7555FF7A242A565900829871 /* iosApp */, + ); + }; + /* End PBXProject section */ + + /* Begin PBXResourcesBuildPhase section */ + 7555FF79242A565900829871 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + /* End PBXResourcesBuildPhase section */ + + /* Begin PBXShellScriptBuildPhase section */ + 7555FFB5242A651A00829871 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; + }; + /* End PBXShellScriptBuildPhase section */ + + /* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, + 7555FF83242A565900829871 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + /* End PBXSourcesBuildPhase section */ + + /* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7555FFA4242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7555FFA6242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)" + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + shared, + ); + PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)" + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + shared, + ); + PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + /* End XCBuildConfiguration section */ + + /* Begin XCConfigurationList section */ + 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA3242A565B00829871 /* Debug */, + 7555FFA4242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA6242A565B00829871 /* Debug */, + 7555FFA7242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + /* End XCConfigurationList section */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; + } \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/mihailbelyj.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/mihailbelyj.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..ed735f8dd Binary files /dev/null and b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/mihailbelyj.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iosApp/iosApp.xcodeproj/xcuserdata/mihailbelyj.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/iosApp.xcodeproj/xcuserdata/mihailbelyj.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..56b5955f8 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/xcuserdata/mihailbelyj.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + iosApp.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..ee7e3ca03 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..3c81c2e1f --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images": [ + { + "filename": "movies-icon.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/movies-icon.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/movies-icon.png new file mode 100644 index 000000000..d1d0f6802 Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/movies-icon.png differ diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 000000000..4aa7c5350 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift new file mode 100644 index 000000000..6a15a0953 --- /dev/null +++ b/iosApp/iosApp/ContentView.swift @@ -0,0 +1,16 @@ +import SwiftUI +import shared + +struct ContentView: View { + let greet = Greeting().greet() + + var body: some View { + Text(greet) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} \ No newline at end of file diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 000000000..8044709cf --- /dev/null +++ b/iosApp/iosApp/Info.plist @@ -0,0 +1,48 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UILaunchScreen + + + \ No newline at end of file diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..4aa7c5350 --- /dev/null +++ b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift new file mode 100644 index 000000000..0648e8602 --- /dev/null +++ b/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index 7677de015..f19087244 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,39 @@ +[]() +
+ en + ru +
+ +
+
+
+
+ Movies = -[![check-pr-badge](https://github.com/michaelbel/movies/actions/workflows/check_pr.yml/badge.svg?branch=develop)](https://github.com/michaelbel/movies/actions/workflows/check_pr.yml) -[![google-play-downloads](https://PlayBadges.pavi2410.me/badge/downloads?id=org.michaelbel.moviemade)](https://play.google.com/store/apps/details?id=org.michaelbel.moviemade) -[![paypal-badge](https://img.shields.io/badge/Donate-Paypal-FF5252.svg)](https://paypal.me/michaelbel) -[![last-commit-badge](https://img.shields.io/github/last-commit/michaelbel/moviemade?color=FF5252)](https://github.com/michaelbel/moviemade/commits) +[![](https://img.shields.io/badge/Android-000000.svg?style=for-the-badge&logo=Android)](https://github.com/michaelbel/movies) + + +[![](https://github.com/michaelbel/movies/actions/workflows/check_pr.yml/badge.svg?branch=develop)](https://github.com/michaelbel/movies/actions/workflows/check_pr.yml) +[![](https://PlayBadges.pavi2410.me/badge/downloads?id=org.michaelbel.moviemade)](https://play.google.com/store/apps/details?id=org.michaelbel.moviemade) +[![](https://img.shields.io/badge/Donate-Paypal-FF5252.svg)](https://paypal.me/michaelbel) +[![](https://img.shields.io/github/last-commit/michaelbel/moviemade?color=FF5252)](https://github.com/michaelbel/movies/commits) -Movies - easy way to discover popular movies. This is a simple TMDb client for Android +Movies - easy way to discover popular movies. This is a simple TMDb client. -
+The goal of this project is to implement an app that provides a consistent user experience on Android Phones, Android Tablets, Android Auto, Android Wear, Android TV, iOS, Desktop (MacOS, Windows, Linux) and Web. Used Material3, Compose, Kotlin and Multiplatform. + +The app is currently in development. Android app is already available. + +## Screenshots +

@@ -17,25 +42,44 @@ Movies - easy way to discover popular movies. This is a simple TMDb client for A - - -

- -## Build - -Take a look at `local.properties` and fill it with [your own](https://developers.themoviedb.org/3/getting-started/introduction) tmdb_api_key like this: + + + + + + +

+ +## Requirements +Take a look at `local.properties` and fill it with [your own](https://developers.themoviedb.org/3/getting-started/introduction) TMDB API key like this: ```gradle TMDB_API_KEY=your_own_tmdb_api_key -``` - -## Download + ``` +The app is still usable without an API key. In this case functionality will be limited. -[](https://play.google.com/store/apps/details?id=org.michaelbel.moviemade) -[](https://appgallery.cloud.huawei.com/ag/n/app/C109677247) -[](https://github.com/michaelbel/movies/releases/download/1.5.2/Movies-v1.5.2.1366.-release.apk) +## Build +- Clone this repository using [latest version](https://d.android.com/studio) of Android Studio +- Run the app on your device or emulator + - 🤖 **Android** with Google Mobile Services: + ```gradle + ./gradlew :androidApp:installGmsDebug + ``` + - 🤖 **Android** with Huawei Mobile Services: + ```gradle + ./gradlew :androidApp:installHmsDebug + ``` + - 🤖 **Android** Free and Open Source Software: + ```gradle + ./gradlew :androidApp:installFossDebug + ``` -## Technologies +## Download +[](https://play.google.com/store/apps/details?id=org.michaelbel.moviemade) +[](https://appgallery.cloud.huawei.com/ag/n/app/C109677247) +[](https://github.com/michaelbel/movies/releases/download/1.5.3/Movies-v1.5.3.1478.-gms-release.apk) +[](https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://github.com/michaelbel/movies) +## 📦 Technologies - [x] [Modularization](https://d.android.com/topic/modularization) - [x] [MVVM](https://d.android.com/topic/architecture) - [x] [Clean Architecture](https://d.android.com/topic/architecture) @@ -46,6 +90,7 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [Gradle Version Catalog](https://d.android.com/build/migrate-to-catalogs) - [x] [Build Variants](https://d.android.com/build/build-variants) - [x] [Product Flavors](https://d.android.com/build/build-variants#product-flavors) +- [x] [Using buildSrc Directory](https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources) - [x] MinSDK 23 - [x] TargetSDK 34 - [x] CompileSDK 34 @@ -63,7 +108,6 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [KotlinX Coroutines](https://github.com/Kotlin/kotlinx.coroutines) - [x] [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) - [x] [Appcompat](https://d.android.com/jetpack/androidx/releases/appcompat) -- [x] [Dagger Hilt](https://github.com/google/dagger) - [x] [ViewModel](https://d.android.com/topic/libraries/architecture/viewmodel) - [x] [Lifecycle](https://d.android.com/topic/libraries/architecture/lifecycle) - [x] [Room](https://d.android.com/training/data-storage/room) @@ -72,11 +116,9 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [Startup](https://d.android.com/jetpack/androidx/releases/startup) - [x] [Navigation](https://d.android.com/guide/navigation) - [x] [Paging3](https://d.android.com/topic/libraries/architecture/paging/v3-overview) -- [x] [ConstraintLayout](https://d.android.com/develop/ui/views/layout/constraint-layout) - [x] [Browser](https://d.android.com/jetpack/androidx/releases/browser) - [x] [OkHttp](https://github.com/square/okhttp) -- [x] [Retrofit](https://github.com/square/retrofit) -- [x] [Retrofit Kotlinx Converter Serialization](https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter) +- [x] [Ktor](https://ktor.io) - [x] [Chucker](https://github.com/ChuckerTeam/chucker) - [x] [Flaker](https://github.com/rotbolt/flaker) - [x] [Coil](https://github.com/coil-kt/coil) @@ -111,12 +153,30 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [Support Landscape Orientation](https://d.android.com/guide/topics/large-screens/support-different-screen-sizes) - [x] [Support Display Cutouts](https://d.android.com/jetpack/compose/system/cutouts) - [x] [Voice Input](https://d.android.com/training/wearables/user-input/voice) +- [x] [User Interactions](https://d.android.com/jetpack/compose/text/user-interactions) +- [x] [Glance AppWidget](https://d.android.com/jetpack/compose/glance) +- [x] [Tile Quick Settings](https://d.android.com/reference/android/service/quicksettings/TileService) +- [x] [Grammatical Gender](https://d.android.com/about/versions/14/features/grammatical-inflection) +- [x] [Biometric Authentication Dialog](https://d.android.com/training/sign-in/biometric-auth) +- [x] [LeakCanary](https://github.com/square/leakcanary) +- [x] [ConstraintLayout Multiplatform](https://github.com/Lavmee/constraintlayout-compose-multiplatform) +- [x] [PreCompose](https://github.com/Tlaster/PreCompose) +- [x] [Koin](https://github.com/InsertKoinIO/koin) +- [x] [ConstraintLayout](https://d.android.com/develop/ui/views/layout/constraint-layout) removed in [44723cb](https://github.com/michaelbel/movies/commit/44723cbbafdad89bef6043f99cbd0fbab1ecf19a) +- [x] [Dagger Hilt](https://github.com/google/dagger) removed in [#274](https://github.com/michaelbel/movies/pull/274) +- [x] [Retrofit](https://github.com/square/retrofit) removed in [#275](https://github.com/michaelbel/movies/pull/275) ## Roadmap [Movies App Roadmap](https://github.com/users/michaelbel/projects/1/views/1) +## Contributing +All contributions are welcome! + +⭐ Join [stargazers](https://github.com/michaelbel/movies/stargazers) +↗️ Submit your PR + ## Issues -If you find any problems or would like to suggest a feature, please feel free to file an [issue](https://github.com/michaelbel/moviemade/issues). +If you find any problems or would like to suggest a feature, please feel free to file an [issue](https://github.com/michaelbel/movies/issues). ## Star History @@ -127,7 +187,7 @@ If you find any problems or would like to suggest a feature, please feel free to -## License +## 📄 License Apache License 2.0 diff --git a/readme.ru.md b/readme.ru.md new file mode 100644 index 000000000..d42bf207a --- /dev/null +++ b/readme.ru.md @@ -0,0 +1,212 @@ +[]() +
+ en + ru +
+ +
+
+
+
+ +Movies += + +[![](https://img.shields.io/badge/Android-000000.svg?style=for-the-badge&logo=Android)](https://github.com/michaelbel/movies) + + +[![](https://github.com/michaelbel/movies/actions/workflows/check_pr.yml/badge.svg?branch=develop)](https://github.com/michaelbel/movies/actions/workflows/check_pr.yml) +[![](https://PlayBadges.pavi2410.me/badge/downloads?id=org.michaelbel.moviemade)](https://play.google.com/store/apps/details?id=org.michaelbel.moviemade) +[![](https://img.shields.io/badge/Donate-Paypal-FF5252.svg)](https://paypal.me/michaelbel) +[![](https://img.shields.io/github/last-commit/michaelbel/moviemade?color=FF5252)](https://github.com/michaelbel/movies/commits) + +Movies - простой способ найти популярные фильмы. Это легковесный TMDb-клиент. + +Цель этого проекта - реализовать приложение с единообразным пользовательским интерфейсом для Android-смартфонов, Android-планшетов, Android Auto, Android Wear, Android TV, iOS, Desktop (MacOS, Windows, Linux) и Web. Используя Material3, Compose, Kotlin и Multiplatform. + +Проект находится в активной разработке. Приложение для Android уже доступно. + +## Скриншоты +

+ + + + + + + + + + + + + + +

+ +## Требования +Перейди в `local.properties` и укажи [свой собственный](https://developers.themoviedb.org/3/getting-started/introduction) TMDB API key как здесь: +```gradle +TMDB_API_KEY=your_own_tmdb_api_key + ``` +Приложение можно использовать без ключа API, но его функциональность будет сильно ограничена. + +## Сборка +- Клонируй репозиторий используя [последнюю версию](https://d.android.com/studio) Android Studio +- Запусти приложение на девайсе или эмуляторе + - 🤖 **Android** с Google Mobile Services: + ```gradle + ./gradlew :androidApp:installGmsDebug + ``` + - 🤖 **Android** с Huawei Mobile Services: + ```gradle + ./gradlew :androidApp:installHmsDebug + ``` + - 🤖 **Android** Free and Open Source Software: + ```gradle + ./gradlew :androidApp:installFossDebug + ``` + +## Загрузить +[](https://play.google.com/store/apps/details?id=org.michaelbel.moviemade) +[](https://appgallery.cloud.huawei.com/ag/n/app/C109677247) +[](https://github.com/michaelbel/movies/releases/download/1.5.3/Movies-v1.5.3.1478.-gms-release.apk) +[](https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://github.com/michaelbel/movies) + +## 📦 Технологии +- [x] [Modularization](https://d.android.com/topic/modularization) +- [x] [MVVM](https://d.android.com/topic/architecture) +- [x] [Clean Architecture](https://d.android.com/topic/architecture) +- [x] [TMDB API](https://developers.themoviedb.org/3/getting-started) +- [x] [KTS Gradle Files](https://d.android.com/studio/build/migrate-to-kts) +- [x] [Kotlin Symbol Processing API](https://d.android.com/studio/build/migrate-to-ksp) +- [x] [Gradle Plugin](https://d.android.com/studio/releases/gradle-plugin) +- [x] [Gradle Version Catalog](https://d.android.com/build/migrate-to-catalogs) +- [x] [Build Variants](https://d.android.com/build/build-variants) +- [x] [Product Flavors](https://d.android.com/build/build-variants#product-flavors) +- [x] [Using buildSrc Directory](https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources) +- [x] MinSDK 23 +- [x] TargetSDK 34 +- [x] CompileSDK 34 +- [x] [Material3](https://m3.material.io) +- [x] [Dark Theme](https://d.android.com/develop/ui/views/theming/darktheme) +- [x] Amoled Theme +- [x] [Material You Dynamic Colors](https://d.android.com/develop/ui/views/theming/dynamic-colors) +- [x] [Themed App Icon](https://d.android.com/develop/ui/views/launch/icon_design_adaptive) +- [x] [Palette Colors API](https://d.android.com/develop/ui/views/graphics/palette-colors) +- [x] [Kotlin](https://d.android.com/kotlin) +- [x] [Jetpack Compose](https://d.android.com/jetpack/compose) +- [x] [Accompanist](https://github.com/google/accompanist) +- [x] [Compose PreviewParameterProvider](https://d.android.com/jetpack/compose/tooling#previewparameter) +- [x] [Downloadable Fonts](https://d.android.com/develop/ui/views/text-and-emoji/downloadable-fonts) +- [x] [KotlinX Coroutines](https://github.com/Kotlin/kotlinx.coroutines) +- [x] [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) +- [x] [Appcompat](https://d.android.com/jetpack/androidx/releases/appcompat) +- [x] [ViewModel](https://d.android.com/topic/libraries/architecture/viewmodel) +- [x] [Lifecycle](https://d.android.com/topic/libraries/architecture/lifecycle) +- [x] [Room](https://d.android.com/training/data-storage/room) +- [x] [WorkManager](https://d.android.com/topic/libraries/architecture/workmanager) +- [x] [DataStore](https://d.android.com/datastore) +- [x] [Startup](https://d.android.com/jetpack/androidx/releases/startup) +- [x] [Navigation](https://d.android.com/guide/navigation) +- [x] [Paging3](https://d.android.com/topic/libraries/architecture/paging/v3-overview) +- [x] [Browser](https://d.android.com/jetpack/androidx/releases/browser) +- [x] [OkHttp](https://github.com/square/okhttp) +- [x] [Ktor](https://ktor.io) +- [x] [Chucker](https://github.com/ChuckerTeam/chucker) +- [x] [Flaker](https://github.com/rotbolt/flaker) +- [x] [Coil](https://github.com/coil-kt/coil) +- [x] [Timber](https://github.com/JakeWharton/timber) +- [x] [Firebase Analytics](https://firebase.google.com/products/analytics) +- [x] [Firebase Crashlytics](https://firebase.google.com/products/crashlytics) +- [x] [Firebase App Distribution](https://firebase.google.com/products/app-distribution) +- [x] [Firebase Remote Config](https://firebase.google.com/products/remote-config) +- [x] [Firebase Messaging](https://firebase.google.com/products/cloud-messaging) +- [x] [In-App Reviews](https://d.android.com/guide/playcore/in-app-review) +- [x] [In-App Updates](https://d.android.com/guide/playcore/in-app-updates) +- [x] [App Shortcuts](https://d.android.com/develop/ui/views/launch/shortcuts) +- [x] [Dependabot](https://github.com/dependabot) +- [x] [Github Actions](https://github.com/michaelbel/movies/tree/develop/.github/workflows) +- [x] [Github Releases](https://github.com/michaelbel/movies/releases) +- [x] [Lint](https://d.android.com/studio/write/lint) +- [x] [Detekt](https://github.com/detekt/detekt) +- [x] [Spotless](https://github.com/diffplug/spotless) +- [x] [Distribute App via Telegram Bot](https://github.com/appleboy/telegram-action) +- [x] [Non-Transitive R classes](https://d.android.com/studio/build/optimize-your-build#use-non-transitive-r-classes) +- [x] [SplashScreen API](https://d.android.com/develop/ui/views/launch/splash-screen) +- [x] [Per-App Language Preferences](https://d.android.com/guide/topics/resources/app-languages) +- [x] [Settings Panel](https://d.android.com/reference/android/provider/Settings.Panel) +- [x] [Benchmark](https://d.android.com/topic/performance/benchmarking/benchmarking-overview) +- [x] [Support Localization](https://d.android.com/guide/topics/resources/localization) +- [x] [Notification Runtime Permission](https://d.android.com/develop/ui/views/notifications/notification-permission) +- [x] [Changing Launcher App Icon](https://d.android.com/guide/topics/manifest/activity-alias-element) +- [x] [Predictive Back Gesture](https://d.android.com/guide/navigation/custom-back/predictive-back-gesture) +- [x] [Codebeat Automated Code Review](https://codebeat.co/projects/github-com-michaelbel-movies-develop) +- [x] [Codacy Static Code Analysis](https://app.codacy.com/gh/michaelbel/movies/dashboard) +- [x] [Display Content Edge-to-Edge](https://d.android.com/develop/ui/views/layout/edge-to-edge) +- [x] [Support Landscape Orientation](https://d.android.com/guide/topics/large-screens/support-different-screen-sizes) +- [x] [Support Display Cutouts](https://d.android.com/jetpack/compose/system/cutouts) +- [x] [Voice Input](https://d.android.com/training/wearables/user-input/voice) +- [x] [User Interactions](https://d.android.com/jetpack/compose/text/user-interactions) +- [x] [Glance AppWidget](https://d.android.com/jetpack/compose/glance) +- [x] [Tile Quick Settings](https://d.android.com/reference/android/service/quicksettings/TileService) +- [x] [Grammatical Gender](https://d.android.com/about/versions/14/features/grammatical-inflection) +- [x] [Biometric Authentication Dialog](https://d.android.com/training/sign-in/biometric-auth) +- [x] [LeakCanary](https://github.com/square/leakcanary) +- [x] [ConstraintLayout Multiplatform](https://github.com/Lavmee/constraintlayout-compose-multiplatform) +- [x] [PreCompose](https://github.com/Tlaster/PreCompose) +- [x] [Koin](https://github.com/InsertKoinIO/koin) +- [x] [ConstraintLayout](https://d.android.com/develop/ui/views/layout/constraint-layout) removed in [44723cb](https://github.com/michaelbel/movies/commit/44723cbbafdad89bef6043f99cbd0fbab1ecf19a) +- [x] [Dagger Hilt](https://github.com/google/dagger) removed in [#274](https://github.com/michaelbel/movies/pull/274) +- [x] [Retrofit](https://github.com/square/retrofit) removed in [#275](https://github.com/michaelbel/movies/pull/275) + +## Роадмап +[Movies App Roadmap](https://github.com/users/michaelbel/projects/1/views/1) + +## Вклад +Твоя помощь приветствуется! + +⭐ Присоединяйся к [звездочетам](https://github.com/michaelbel/movies/stargazers) +↗️ Отправляй пулл-реквесты + +## Траблы +Если попался баг или хочешь предложить фичу, не стесняйся, заводи [issue](https://github.com/michaelbel/movies/issues). + +## Контакты +Подписывайся на [telegram-канал](https://t.me/foundout) +Добавляйся в друзья на [Кинопоиске](https://www.kinopoisk.ru/user/4104533) +Добавляйся в друзья на [MyShows](https://myshows.me/michaelbel) + +## История звездочек + + + + + Star History Chart + + + +## 📄 Лицензия + + Apache License 2.0 + + + Copyright 2017 Michael Bely + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/settings.gradle.kts b/settings.gradle.kts index 959c44550..30015a08b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,5 @@ +@file:Suppress("UnstableApiUsage") + pluginManagement { repositories { google() @@ -19,39 +21,42 @@ dependencyResolutionManagement { rootProject.name = "movies" include( - ":android-app", + ":androidApp", + ":desktopApp", + ":iosApp", ":instant", ":benchmark", - ":shared", - ":core:analytics", - ":core:common", - ":core:interactor", - ":core:navigation", - ":core:network", - ":core:notifications", - ":core:persistence", - ":core:platform-services:gms", - ":core:platform-services:hms", - ":core:platform-services:foss", - ":core:platform-services:inject", - ":core:platform-services:interactor", - ":core:repository", - ":core:ui", - ":core:work", + ":core:analytics-kmp", + ":core:common-kmp", + ":core:debug-kmp", + ":core:interactor-kmp", + ":core:navigation-kmp", + ":core:network-kmp", + ":core:notifications-kmp", + ":core:persistence-kmp", + ":core:platform-services:gms-kmp", + ":core:platform-services:hms-kmp", + ":core:platform-services:foss-kmp", + ":core:platform-services:inject-kmp", + ":core:platform-services:interactor-kmp", + ":core:repository-kmp", + ":core:ui-kmp", + ":core:widget-kmp", + ":core:work-kmp", - ":feature:account", - ":feature:account-impl", - ":feature:auth", - ":feature:auth-impl", - ":feature:details", - ":feature:details-impl", - ":feature:feed", - ":feature:feed-impl", - ":feature:gallery", - ":feature:gallery-impl", - ":feature:search", - ":feature:search-impl", - ":feature:settings", - ":feature:settings-impl" + ":feature:account-kmp", + ":feature:account-impl-kmp", + ":feature:auth-kmp", + ":feature:auth-impl-kmp", + ":feature:details-kmp", + ":feature:details-impl-kmp", + ":feature:feed-kmp", + ":feature:feed-impl-kmp", + ":feature:gallery-kmp", + ":feature:gallery-impl-kmp", + ":feature:search-kmp", + ":feature:search-impl-kmp", + ":feature:settings-kmp", + ":feature:settings-impl-kmp" ) \ No newline at end of file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts deleted file mode 100644 index 2e679889f..000000000 --- a/shared/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -plugins { - kotlin("multiplatform") - alias(libs.plugins.library) -} - -@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) -kotlin { - targetHierarchy.default() - - androidTarget { - compilations.all { - kotlinOptions { - jvmTarget = "1.8" - } - } - } - - listOf( - iosX64(), - iosArm64(), - iosSimulatorArm64() - ).forEach { - it.binaries.framework { - baseName = "shared" - } - } - - sourceSets { - val commonMain by getting { - dependencies { - //put your multiplatform dependencies here - } - } - val commonTest by getting { - dependencies { - //implementation(libs.kotlin.test) - } - } - } -} - -android { - namespace = "org.michaelbel.movies.shared" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - } -} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/org/michaelbel/movies/shared/Platform.android.kt b/shared/src/androidMain/kotlin/org/michaelbel/movies/shared/Platform.android.kt deleted file mode 100644 index c48b9e150..000000000 --- a/shared/src/androidMain/kotlin/org/michaelbel/movies/shared/Platform.android.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.shared - -import android.os.Build - -class AndroidPlatform: Platform { - override val name: String = "Android ${Build.VERSION.SDK_INT}" -} - -actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/michaelbel/movies/shared/Greeting.kt b/shared/src/commonMain/kotlin/org/michaelbel/movies/shared/Greeting.kt deleted file mode 100644 index c0b5468e0..000000000 --- a/shared/src/commonMain/kotlin/org/michaelbel/movies/shared/Greeting.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.shared - -class Greeting { - private val platform: Platform = getPlatform() - - fun greet(): String { - return "Hello, ${platform.name}!" - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/michaelbel/movies/shared/Platform.kt b/shared/src/commonMain/kotlin/org/michaelbel/movies/shared/Platform.kt deleted file mode 100644 index 0b40f10c5..000000000 --- a/shared/src/commonMain/kotlin/org/michaelbel/movies/shared/Platform.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.michaelbel.movies.shared - -interface Platform { - val name: String -} - -expect fun getPlatform(): Platform \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/org/michaelbel/movies/shared/Platform.ios.kt b/shared/src/iosMain/kotlin/org/michaelbel/movies/shared/Platform.ios.kt deleted file mode 100644 index 4955719c1..000000000 --- a/shared/src/iosMain/kotlin/org/michaelbel/movies/shared/Platform.ios.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.shared - -import platform.UIKit.UIDevice - -class IOSPlatform: Platform { - override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion -} - -actual fun getPlatform(): Platform = IOSPlatform() \ No newline at end of file