├── .idea ├── .name ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── kotlinc.xml ├── render.experimental.xml ├── vcs.xml ├── migrations.xml ├── detekt.xml ├── deploymentTargetDropDown.xml ├── geminio_plugin_settings.xml ├── runConfigurations.xml ├── jarRepositories.xml ├── misc.xml ├── inspectionProfiles │ └── Project_Default.xml └── gradle.xml ├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ └── ids.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── ktcast │ │ │ │ ├── mvi │ │ │ │ ├── MainUIState.kt │ │ │ │ └── MainSideEffect.kt │ │ │ │ ├── glue │ │ │ │ └── GlueModules.kt │ │ │ │ ├── MainViewModelFactory.kt │ │ │ │ ├── di │ │ │ │ ├── ApplicationComponent.kt │ │ │ │ └── ApplicationModule.kt │ │ │ │ ├── KtCastApplication.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── ExampleUnitTest.kt │ ├── androidTest │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── ExampleInstrumentedTest.kt │ └── debug │ │ └── AndroidManifest.xml ├── dhconfig.json ├── proguard-rules.pro └── build.gradle.kts ├── core ├── core-di │ ├── .gitignore │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── core │ │ │ └── di │ │ │ └── MyClass.kt │ └── build.gradle.kts ├── di │ └── library │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── core │ │ │ └── di │ │ │ └── base │ │ │ ├── holder │ │ │ ├── BaseDependencyHolder1.kt │ │ │ └── BaseDependencyHolder.kt │ │ │ ├── BaseFeatureAPI.kt │ │ │ ├── Provider.kt │ │ │ ├── scope │ │ │ ├── FeatureScope.kt │ │ │ └── FragmentScope.kt │ │ │ ├── BaseFeatureDependencies.kt │ │ │ └── BaseComponentHolder.kt │ │ └── build.gradle.kts ├── mvi │ ├── res │ │ ├── .gitignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── res │ │ │ │ └── layout │ │ │ │ └── fragment_base.xml │ │ └── build.gradle.kts │ ├── android-library │ │ ├── .gitignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── java │ │ │ │ └── me │ │ │ │ │ └── s097t0r1 │ │ │ │ │ └── core │ │ │ │ │ └── mvi │ │ │ │ │ └── base │ │ │ │ │ ├── state │ │ │ │ │ ├── BaseState.kt │ │ │ │ │ └── BaseSideEffect.kt │ │ │ │ │ ├── host │ │ │ │ │ ├── HostViewModelOwner.kt │ │ │ │ │ ├── HostViewModel.kt │ │ │ │ │ └── HostSideEffect.kt │ │ │ │ │ ├── BaseDialogFragment.kt │ │ │ │ │ ├── BaseViewModel.kt │ │ │ │ │ ├── BaseContainerActivity.kt │ │ │ │ │ ├── BaseContainerFragment.kt │ │ │ │ │ └── BaseFragment.kt │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle.kts │ └── android-utils │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ └── AndroidManifest.xml │ │ └── build.gradle.kts ├── exceptions │ └── library │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src │ │ └── main │ │ └── java │ │ └── me │ │ └── s097t0r1 │ │ └── core │ │ └── exceptions │ │ └── library │ │ └── AppException.kt ├── ui-components │ ├── res │ │ ├── .gitignore │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── res │ │ │ │ ├── font │ │ │ │ ├── urbanist_bold.ttf │ │ │ │ ├── urbanist_medium.ttf │ │ │ │ ├── urbanist_regular.ttf │ │ │ │ └── urbanist_semibold.ttf │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── themes.xml │ │ │ │ └── styles.xml │ │ │ │ ├── values-night │ │ │ │ └── colors.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ │ ├── drawable │ │ │ │ ├── ic_info_circle.xml │ │ │ │ ├── ic_toolbar_back.xml │ │ │ │ └── ic_ktcast_logo.xml │ │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ └── build.gradle.kts │ └── android-library │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── core │ │ │ └── ui_components │ │ │ ├── components │ │ │ ├── ProgressBar.kt │ │ │ ├── Checkbox.kt │ │ │ ├── AlertSnackBar.kt │ │ │ └── Button.kt │ │ │ └── theme │ │ │ ├── KtCastColorPallete.kt │ │ │ ├── Theme.kt │ │ │ └── Typography.kt │ │ └── build.gradle.kts ├── dialog │ └── android-library │ │ ├── .gitignore │ │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── ktcast │ │ │ │ └── core │ │ │ │ └── dialog │ │ │ │ └── ProgressDialogFragment.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── s097t0r1 │ │ │ │ └── ktcast │ │ │ │ └── dialog │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── dialog │ │ │ └── ExampleInstrumentedTest.kt │ │ └── build.gradle ├── navigation │ └── android-library │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── core │ │ │ │ └── navigation │ │ │ │ ├── base │ │ │ │ ├── NavigationGraph.kt │ │ │ │ └── NavigationProvider.kt │ │ │ │ ├── router │ │ │ │ ├── RouterProvider.kt │ │ │ │ ├── Router.kt │ │ │ │ └── AppRouter.kt │ │ │ │ ├── navigator │ │ │ │ ├── Navigator.kt │ │ │ │ └── AppNavigator.kt │ │ │ │ ├── dispatcher │ │ │ │ ├── NavigationDispatcherHost.kt │ │ │ │ ├── NavigationDispatcher.kt │ │ │ │ └── FragmentNavigationDispatcher.kt │ │ │ │ ├── message │ │ │ │ ├── NavigationMessage.kt │ │ │ │ └── BaseMessages.kt │ │ │ │ ├── command │ │ │ │ └── NavigationCommand.kt │ │ │ │ └── screen │ │ │ │ └── NavigationScreen.kt │ │ │ └── res │ │ │ └── animator │ │ │ ├── slide_out.xml │ │ │ └── slide_in.xml │ │ └── build.gradle.kts └── debug-helper │ └── android-library │ ├── .gitignore │ ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── core │ │ │ └── debug_helper │ │ │ ├── modules │ │ │ └── stand_module │ │ │ │ ├── Stand.kt │ │ │ │ └── NetworkStandModule.kt │ │ │ ├── KtCastDebugStorage.kt │ │ │ └── KtCastDebugHelper.kt │ ├── test │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── core │ │ │ └── debug_helper │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── me │ │ └── s097t0r1 │ │ └── ktcast │ │ └── core │ │ └── debug_helper │ │ └── ExampleInstrumentedTest.kt │ └── build.gradle ├── common ├── network │ ├── utils │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── common │ │ │ └── network │ │ │ └── utils │ │ │ ├── model │ │ │ └── ErrorResponse.kt │ │ │ └── JsonUtils.kt │ └── android-library │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── common │ │ │ │ └── network │ │ │ │ ├── Endpoint.kt │ │ │ │ ├── Constants.kt │ │ │ │ ├── di │ │ │ │ ├── NetworkDependencies.kt │ │ │ │ ├── NetworkAPI.kt │ │ │ │ ├── NetworkComponentHolder.kt │ │ │ │ ├── NetworkComponent.kt │ │ │ │ └── NetworkModule.kt │ │ │ │ ├── factory │ │ │ │ └── NetworkServiceFactory.kt │ │ │ │ ├── call_adapter │ │ │ │ ├── ReactionCallAdapter.kt │ │ │ │ ├── ReactionCallAdapterFactory.kt │ │ │ │ └── ReactionCall.kt │ │ │ │ ├── interceptors │ │ │ │ └── ApplicationInterceptor.kt │ │ │ │ └── authenticator │ │ │ │ └── KtCastAuthenticator.kt │ │ │ └── AndroidManifest.xml │ │ └── build.gradle ├── logout │ └── android-library │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── common │ │ │ └── logout │ │ │ └── LogoutHandler.kt │ │ ├── test │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── common │ │ │ └── logout │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ └── java │ │ └── me │ │ └── s097t0r1 │ │ └── ktcast │ │ └── common │ │ └── logout │ │ └── ExampleInstrumentedTest.kt └── persistence │ ├── database │ └── android-library │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── persistence │ │ │ └── database │ │ │ ├── di │ │ │ ├── DatabaseAPI.kt │ │ │ ├── DatabaseDependencies.kt │ │ │ ├── DatabaseComponent.kt │ │ │ ├── DatabaseModule.kt │ │ │ └── DatabaseComponentHolder.kt │ │ │ ├── StubEntity.kt │ │ │ └── db │ │ │ └── AppDatabase.kt │ │ └── build.gradle │ └── secure-storage │ └── android-library │ ├── .gitignore │ ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── me │ │ └── s097t0r1 │ │ └── ktcast │ │ └── common │ │ └── secure_storage │ │ ├── di │ │ ├── SecureStorageDependencies.kt │ │ ├── SecureStorageAPI.kt │ │ ├── SecureStorageComponentHolder.kt │ │ ├── SecureStorageModule.kt │ │ └── SecureStorageComponent.kt │ │ └── storage │ │ └── SecureStorage.kt │ └── build.gradle ├── libraries ├── either │ └── library │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── java │ │ └── me │ │ └── s097t0r1 │ │ └── ktcast │ │ └── libraries │ │ └── reaction │ │ └── Either.kt ├── mapper │ └── library │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── libraries │ │ │ └── mapper │ │ │ ├── Mappable.kt │ │ │ └── Mapper.kt │ │ └── build.gradle.kts ├── utils │ └── core │ │ └── android_library │ │ ├── .gitignore │ │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── ktcast │ │ │ │ └── libraries │ │ │ │ └── utils │ │ │ │ └── core │ │ │ │ └── bundle │ │ │ │ └── FragmentArgumentsExtractorDelegate.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── ktcast │ │ │ │ └── libraries │ │ │ │ └── utils │ │ │ │ └── core │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── libraries │ │ │ └── utils │ │ │ └── core │ │ │ └── ExampleInstrumentedTest.kt │ │ └── build.gradle.kts ├── deps-holder-proccessor │ └── library │ │ ├── .gitignore │ │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── deps_holder_processor │ │ │ ├── ComponentHolder.kt │ │ │ ├── MetaInfoProccessor.kt │ │ │ └── DHProcessorProvider.kt │ │ └── build.gradle.kts ├── resource-provider │ └── android-library │ │ ├── .gitignore │ │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── ktcast │ │ │ │ └── libraries │ │ │ │ └── resource_provider │ │ │ │ ├── ResourceProvider.kt │ │ │ │ └── AndroidResourceProvider.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── me │ │ │ │ └── s097t0r1 │ │ │ │ └── ktcast │ │ │ │ └── core │ │ │ │ └── utils │ │ │ │ └── resource_provier │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── me │ │ │ └── s097t0r1 │ │ │ └── ktcast │ │ │ └── core │ │ │ └── utils │ │ │ └── resource_provier │ │ │ └── ExampleInstrumentedTest.kt │ │ └── build.gradle.kts └── viewmodel-factory │ └── android-library │ ├── .gitignore │ ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── me │ │ └── s097t0r1 │ │ └── ktcast │ │ └── libraries │ │ └── factory │ │ ├── ViewModelKey.kt │ │ ├── ViewModelFactoryModule.kt │ │ └── ViewModelFactory.kt │ └── build.gradle.kts ├── config ├── geminio │ └── modules_templates │ │ ├── feature-api │ │ ├── root │ │ │ ├── .gitignore.ftl │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ ├── AndroidManifest.xml.ftl │ │ │ │ │ └── java │ │ │ │ │ └── package │ │ │ │ │ ├── FeatureDependencies.kt.ftl │ │ │ │ │ ├── FeatureStarter.kt.ftl │ │ │ │ │ └── FeatureApi.kt.ftl │ │ │ └── build.gradle.ftl │ │ └── recipe.yaml │ │ └── feature-impl │ │ ├── root │ │ ├── .gitignore.ftl │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml.ftl │ │ │ │ └── java │ │ │ │ └── package │ │ │ │ └── di │ │ │ │ ├── FeatureModule.kt.ftl │ │ │ │ ├── FeatureComponentHolder.kt.ftl │ │ │ │ └── FeatureComponent.kt.ftl │ │ └── build.gradle.ftl │ │ └── recipe.yaml ├── detekt-compose.yml └── detekt.yml ├── metadata ├── logo.webp └── intro.webp ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── geminio.yaml ├── .gitignore ├── .github └── workflows │ └── pull_request.yaml ├── LICENSE ├── settings.gradle.kts ├── README.md ├── gradle.properties └── gradlew.bat /.idea/.name: -------------------------------------------------------------------------------- 1 | ktcast -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/core-di/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/di/library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/mvi/res/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/network/utils/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/exceptions/library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/mvi/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/mvi/android-utils/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui-components/res/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/either/library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/mapper/library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/logout/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/network/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/dialog/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/navigation/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/debug-helper/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui-components/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/utils/core/android_library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/deps-holder-proccessor/library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/persistence/database/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/resource-provider/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/viewmodel-factory/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-api/root/.gitignore.ftl: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-impl/root/.gitignore.ftl: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/holder/BaseDependencyHolder1.kt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/exceptions/library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-java-library") 3 | } -------------------------------------------------------------------------------- /metadata/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/metadata/logo.webp -------------------------------------------------------------------------------- /metadata/intro.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/metadata/intro.webp -------------------------------------------------------------------------------- /common/logout/android-library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-java-library") 3 | } 4 | 5 | -------------------------------------------------------------------------------- /core/core-di/src/main/java/me/s097t0r1/core/di/MyClass.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di 2 | 3 | class MyClass { 4 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /common/logout/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/BaseFeatureAPI.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di.base 2 | 3 | interface BaseFeatureAPI 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/mvi/android-utils/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/dialog/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/state/BaseState.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base.state 2 | 3 | interface BaseState -------------------------------------------------------------------------------- /core/mvi/res/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/debug-helper/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/state/BaseSideEffect.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base.state 2 | 3 | interface BaseSideEffect -------------------------------------------------------------------------------- /common/network/utils/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-java-library") 3 | } 4 | 5 | dependencies { 6 | 7 | implementation(libs.squareup.moshi) 8 | 9 | } -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/font/urbanist_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/font/urbanist_bold.ttf -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-api/root/src/main/AndroidManifest.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-impl/root/src/main/AndroidManifest.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /core/mvi/android-utils/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-android-library") 3 | } 4 | 5 | android { 6 | namespace = "me.s097t0r1.ktcast.core.mvi.res" 7 | } -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/font/urbanist_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/font/urbanist_medium.ttf -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/font/urbanist_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/font/urbanist_regular.ttf -------------------------------------------------------------------------------- /libraries/utils/core/android_library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /libraries/viewmodel-factory/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/base/NavigationGraph.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.base 2 | 3 | interface NavigationGraph 4 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/font/urbanist_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/font/urbanist_semibold.ttf -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /libraries/resource-provider/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/mvi/MainUIState.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.mvi 2 | 3 | import me.s097t0r1.core.mvi.base.state.BaseState 4 | 5 | class MainUIState() : BaseState 6 | -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /libraries/deps-holder-proccessor/library/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | me.s097t0r1.deps_holder_processor.DHProcessorProvider -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/Provider.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di.base 2 | 3 | fun interface Provider { 4 | fun provide(): D 5 | } 6 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s097t0r1/ktcast/HEAD/core/ui-components/res/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ktcast 3 | Hello blank fragment 4 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/render.experimental.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/router/RouterProvider.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.router 2 | 3 | interface RouterProvider { 4 | val router: Router 5 | } 6 | -------------------------------------------------------------------------------- /core/di/library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-java-library") 3 | id("kotlin-kapt") 4 | } 5 | 6 | dependencies { 7 | implementation(libs.google.dagger) 8 | kapt(libs.google.dagger.compiler) 9 | } -------------------------------------------------------------------------------- /geminio.yaml: -------------------------------------------------------------------------------- 1 | templatesRootDirPath: /config/geminio/templates 2 | modulesTemplatesRootDirPath: /config/geminio/modules_templates 3 | 4 | groupsNames: 5 | forNewGroup: KtCast Templates 6 | forNewModulesGroup: KtCast Modules -------------------------------------------------------------------------------- /libraries/deps-holder-proccessor/library/src/main/java/me/s097t0r1/deps_holder_processor/ComponentHolder.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.deps_holder_processor 2 | 3 | annotation class ComponentHolder( 4 | val numberOfComponents: Int 5 | ) 6 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #212121 5 | -------------------------------------------------------------------------------- /libraries/mapper/library/src/main/java/me/s097t0r1/ktcast/libraries/mapper/Mappable.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.mapper 2 | 3 | sealed interface Mappable 4 | 5 | interface DTO : Mappable 6 | 7 | interface DomainModel : Mappable -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/scope/FeatureScope.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di.base.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class FeatureScope 8 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF181A20 4 | #FFFFFF 5 | -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/glue/GlueModules.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.glue 2 | 3 | import android.content.Context 4 | 5 | internal fun glueModules(applicationContext: Context) { 6 | 7 | // Feature 8 | 9 | // Data 10 | } -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-impl/root/build.gradle.ftl: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-feature-implementation") 3 | id("kotlin-kapt") 4 | } 5 | 6 | dependencies { 7 | implementation(projects.feature.${buildModuleName}.api) 8 | } 9 | -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/scope/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di.base.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class FragmentScope 8 | -------------------------------------------------------------------------------- /libraries/either/library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("org.jetbrains.kotlin.jvm") 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_17 8 | targetCompatibility = JavaVersion.VERSION_17 9 | } -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/java/me/s097t0r1/persistence/database/di/DatabaseAPI.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.persistence.database.di 2 | 3 | import me.s097t0r1.core.di.base.BaseFeatureAPI 4 | 5 | interface DatabaseAPI : BaseFeatureAPI { 6 | 7 | } -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/holder/BaseDependencyHolder.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di.base.holder 2 | 3 | import me.s097t0r1.core.di.base.BaseFeatureDependencies 4 | 5 | abstract class BaseDependencyHolder 6 | -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-api/root/build.gradle.ftl: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-feature-api") 3 | } 4 | 5 | dependencies { 6 | <#if (includeStarter)> 7 | implementation(projects.core.navigation.androidLibrary) 8 | 9 | } 10 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/Endpoint.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network 2 | 3 | const val BASE_URL = "http://185.104.115.136/api/v1/" 4 | 5 | object Endpoint { 6 | 7 | const val USER_LOGIN = "user/login/{username}" 8 | 9 | } -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/host/HostViewModelOwner.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base.host 2 | 3 | import androidx.lifecycle.ViewModelProvider 4 | 5 | interface HostViewModelOwner { 6 | val viewModelFactory: ViewModelProvider.Factory 7 | } -------------------------------------------------------------------------------- /libraries/resource-provider/android-library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("module-configurator") 4 | } 5 | 6 | android { 7 | namespace = "me.s097t0r1.ktcast.libraries.resource_provider" 8 | } 9 | 10 | dependencies { 11 | 12 | } -------------------------------------------------------------------------------- /libraries/utils/core/android_library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-android-library") 3 | } 4 | 5 | android { 6 | namespace = "me.s097t0r1.ktcast.libraries.utils.core" 7 | } 8 | 9 | dependencies { 10 | 11 | implementation(libs.androidx.appcompat) 12 | } -------------------------------------------------------------------------------- /common/network/utils/src/main/java/me/s097t0r1/ktcast/common/network/utils/model/ErrorResponse.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.network.utils.model 2 | 3 | import com.squareup.moshi.Json 4 | 5 | class ErrorResponse( 6 | @Json(name ="errors") 7 | val errors: List 8 | ) -------------------------------------------------------------------------------- /libraries/mapper/library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("org.jetbrains.kotlin.jvm") 4 | } 5 | 6 | dependencies { 7 | } 8 | 9 | java { 10 | sourceCompatibility = JavaVersion.VERSION_17 11 | targetCompatibility = JavaVersion.VERSION_17 12 | } -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-api/root/src/main/java/package/FeatureDependencies.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.api 2 | 3 | import me.s097t0r1.core.di.base.BaseFeatureDependencies 4 | 5 | interface ${__formattedModuleName}FeatureDependencies : BaseFeatureDependencies { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Aug 11 21:07:59 MSK 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /core/ui-components/res/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("module-configurator") 4 | } 5 | 6 | android { 7 | namespace = "me.s097t0r1.core.ui_components.res" 8 | } 9 | 10 | moduleConfiguration { 11 | isVectorDrawableSupportEnabled = true 12 | } -------------------------------------------------------------------------------- /common/logout/android-library/src/main/java/me/s097t0r1/ktcast/common/logout/LogoutHandler.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.logout 2 | 3 | interface LogoutHandler { 4 | 5 | fun logout(logoutType: LogoutType) 6 | 7 | enum class LogoutType { 8 | SERVER_LOGOUT 9 | } 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/java/me/s097t0r1/persistence/database/StubEntity.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.persistence.database 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity 7 | class StubEntity( 8 | @PrimaryKey 9 | val id: Int 10 | ) -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/navigator/Navigator.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.navigator 2 | 3 | import me.s097t0r1.core.navigation.command.NavigationCommand 4 | 5 | interface Navigator { 6 | fun execute(commands: Array) 7 | } 8 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/base/NavigationProvider.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.base 2 | 3 | import me.s097t0r1.core.navigation.router.Router 4 | 5 | interface NavigationProvider { 6 | fun navigate(router: Router, screen: N) 7 | } 8 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/BaseFeatureDependencies.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di.base 2 | 3 | import me.s097t0r1.core.di.base.holder.BaseDependencyHolder 4 | 5 | interface BaseFeatureDependencies { 6 | val dependencyProvider: BaseDependencyHolder 7 | } 8 | -------------------------------------------------------------------------------- /app/dhconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "package_location": "me.s097t0r1.core.di.base.holders", 3 | "feature_api_package": "me.s097t0r1.core.di.base.BaseFeatureAPI", 4 | "feature_dependency_package": "me.s097t0r1.core.di.base.BaseFeatureDependencies", 5 | "dependency_holder_package": "me.s097t0r1.core.di.base.holder.BaseDependencyHolder" 6 | } -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-api/root/src/main/java/package/FeatureStarter.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.api 2 | 3 | import androidx.fragment.app.Fragment 4 | import me.s097t0r1.core.navigation.screen.FragmentScreen 5 | 6 | interface ${__formattedModuleName}FeatureStarter { 7 | fun start(): FragmentScreen 8 | } 9 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/resource-provider/android-library/src/main/java/me/s097t0r1/ktcast/libraries/resource_provider/ResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.resource_provider 2 | 3 | import android.graphics.drawable.Drawable 4 | 5 | interface ResourceProvider { 6 | fun getString(id: Int): String 7 | fun getDrawable(id: Int): Drawable 8 | } -------------------------------------------------------------------------------- /core/navigation/android-library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("module-configurator") 4 | } 5 | 6 | android { 7 | namespace = "me.s097t0r1.core.navigation" 8 | } 9 | 10 | dependencies { 11 | 12 | implementation(libs.androidx.appcompat) 13 | implementation(libs.androidx.fragment.ktx) 14 | } 15 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/Constants.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network 2 | 3 | internal const val BEARER_PREFIX = "Bearer " 4 | 5 | internal const val CONTENT_TYPE_HEADER_NAME = "Content-type" 6 | internal const val AUTHORIZATION_HEADER_NAME = "Authorization" 7 | 8 | internal const val TIMEOUT_IN_MILLIS = 10000L -------------------------------------------------------------------------------- /core/debug-helper/android-library/src/main/java/me/s097t0r1/ktcast/core/debug_helper/modules/stand_module/Stand.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.core.debug_helper.modules.stand_module 2 | 3 | enum class Stand(val baseUrl: String) { 4 | MOCKER("https://virtserver.swaggerhub.com/S097T0R1_1/ktcast/1.0.0/"), 5 | TEST("http://185.104.115.136/api/v1/") 6 | } 7 | -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-api/root/src/main/java/package/FeatureApi.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.api 2 | 3 | import me.s097t0r1.core.di.base.BaseFeatureAPI 4 | 5 | interface ${__formattedModuleName}FeatureAPI : BaseFeatureAPI { 6 | <#if (includeStarter)> 7 | val starter: ${__formattedModuleName}FeatureStarter 8 | 9 | } 10 | -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/java/me/s097t0r1/persistence/database/di/DatabaseDependencies.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.persistence.database.di 2 | 3 | import android.content.Context 4 | import me.s097t0r1.core.di.base.BaseFeatureDependencies 5 | 6 | interface DatabaseDependencies : BaseFeatureDependencies { 7 | val applicationContext: Context 8 | } -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /core/ui-components/android-library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-android-library") 3 | id("compose-configurator") 4 | } 5 | 6 | android { 7 | namespace = "me.s097t0r1.core.ui_components" 8 | } 9 | 10 | dependencies { 11 | 12 | implementation(projects.core.uiComponents.res) 13 | implementation(libs.bundles.androidx.compose) 14 | 15 | } -------------------------------------------------------------------------------- /.idea/detekt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 11 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/di/NetworkDependencies.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.di 2 | 3 | import me.s097t0r1.core.di.base.BaseFeatureDependencies 4 | import me.s097t0r1.ktcast.common.secure_storage.storage.SecureStorage 5 | 6 | interface NetworkDependencies : BaseFeatureDependencies { 7 | val secureStorage: SecureStorage 8 | } -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/dispatcher/NavigationDispatcherHost.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.dispatcher 2 | 3 | import me.s097t0r1.core.navigation.message.NavigationMessage 4 | 5 | interface NavigationDispatcherHost { 6 | val dispatcher: NavigationDispatcher 7 | fun accept(navigationMessage: NavigationMessage) 8 | } 9 | -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/src/main/java/me/s097t0r1/ktcast/common/secure_storage/di/SecureStorageDependencies.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.secure_storage.di 2 | 3 | import android.content.Context 4 | import me.s097t0r1.core.di.base.BaseFeatureDependencies 5 | 6 | interface SecureStorageDependencies : BaseFeatureDependencies { 7 | val context: Context 8 | } -------------------------------------------------------------------------------- /core/core-di/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | alias(libs.plugins.jetbrains.kotlin.jvm) 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_11 8 | targetCompatibility = JavaVersion.VERSION_11 9 | } 10 | 11 | kotlin { 12 | compilerOptions { 13 | jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 14 | } 15 | } -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/message/NavigationMessage.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.message 2 | 3 | import me.s097t0r1.core.navigation.command.NavigationCommand 4 | 5 | abstract class NavigationMessage { 6 | val receiver = NavigationCommand.DEFAULT_RECEIVER 7 | abstract fun convertToCommands(): Array 8 | } 9 | -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/src/main/java/me/s097t0r1/ktcast/common/secure_storage/di/SecureStorageAPI.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.secure_storage.di 2 | 3 | import me.s097t0r1.core.di.base.BaseFeatureAPI 4 | import me.s097t0r1.ktcast.common.secure_storage.storage.SecureStorage 5 | 6 | interface SecureStorageAPI : BaseFeatureAPI { 7 | val storage: SecureStorage 8 | } -------------------------------------------------------------------------------- /libraries/deps-holder-proccessor/library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-java-library") 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | buildscript { 10 | dependencies { 11 | classpath(libs.kotlin.gradle.plugin) 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation(libs.google.ksp) 17 | implementation(libs.squareup.moshi) 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/mvi/MainSideEffect.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.mvi 2 | 3 | import me.s097t0r1.core.ui_components.components.AlertSnackBarHost 4 | 5 | 6 | sealed class MainSideEffect { 7 | object Initial : MainSideEffect() 8 | class Alert(val alertType: AlertSnackBarHost.AlertType, val message: String) : MainSideEffect() 9 | object Logout : MainSideEffect() 10 | } -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/res/animator/slide_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/res/animator/slide_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/di/NetworkAPI.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.di 2 | 3 | import me.s097t0r1.common.network.factory.NetworkServiceFactory 4 | import me.s097t0r1.core.di.base.BaseFeatureAPI 5 | 6 | interface NetworkAPI : BaseFeatureAPI { 7 | val authorizedServiceFactory: NetworkServiceFactory 8 | val unauthorizedServiceFactory: NetworkServiceFactory 9 | } -------------------------------------------------------------------------------- /libraries/viewmodel-factory/android-library/src/main/java/me/s097t0r1/ktcast/libraries/factory/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.factory 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @MapKey 8 | @Retention(AnnotationRetention.RUNTIME) 9 | annotation class ViewModelKey( 10 | val viewModelClazz: KClass 11 | ) 12 | -------------------------------------------------------------------------------- /libraries/viewmodel-factory/android-library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("module-configurator") 4 | id("kotlin-kapt") 5 | } 6 | 7 | android { 8 | namespace = "me.s097t0r1.ktcast.libraries.factory" 9 | } 10 | 11 | dependencies { 12 | 13 | implementation(libs.androidx.lifecycle.viewmodel.ktx) 14 | implementation(libs.google.dagger) 15 | kapt(libs.google.dagger.compiler) 16 | 17 | } -------------------------------------------------------------------------------- /core/mvi/res/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("compose-configurator") 4 | id("module-configurator") 5 | } 6 | 7 | android { 8 | namespace = "me.s097t0r1.core.mvi.res" 9 | } 10 | 11 | dependencies { 12 | implementation(libs.androidx.core.ktx) 13 | implementation(libs.androidx.appcompat) 14 | implementation(libs.google.material) 15 | implementation(libs.bundles.androidx.compose) 16 | } -------------------------------------------------------------------------------- /libraries/viewmodel-factory/android-library/src/main/java/me/s097t0r1/ktcast/libraries/factory/ViewModelFactoryModule.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.factory 2 | 3 | import androidx.lifecycle.ViewModelProvider 4 | import dagger.Binds 5 | import dagger.Module 6 | 7 | @Module 8 | abstract class ViewModelFactoryModule { 9 | 10 | @Binds 11 | abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory 12 | 13 | } -------------------------------------------------------------------------------- /app/src/test/java/me/s097t0r1/ktcast/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /core/mvi/res/src/main/res/layout/fragment_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/router/Router.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.router 2 | 3 | import me.s097t0r1.core.navigation.dispatcher.NavigationDispatcher 4 | import me.s097t0r1.core.navigation.message.NavigationMessage 5 | 6 | interface Router { 7 | 8 | fun navigate(message: NavigationMessage) 9 | 10 | fun attachDispatcher(dispatcher: NavigationDispatcher) 11 | 12 | fun dettachDispatcher() 13 | } 14 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/dispatcher/NavigationDispatcher.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.dispatcher 2 | 3 | import me.s097t0r1.core.navigation.command.NavigationCommand 4 | import me.s097t0r1.core.navigation.message.NavigationMessage 5 | 6 | interface NavigationDispatcher { 7 | val receiverTag: String 8 | get() = NavigationCommand.DEFAULT_RECEIVER 9 | 10 | fun dispatch(navigationMessage: NavigationMessage) 11 | } 12 | -------------------------------------------------------------------------------- /libraries/mapper/library/src/main/java/me/s097t0r1/ktcast/libraries/mapper/Mapper.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.mapper 2 | 3 | interface Mapper { 4 | fun map(input: I): O 5 | } 6 | 7 | inline fun > createMapper(vararg args: Any) = 8 | T::class.java.constructors.first().newInstance(args) as T 9 | 10 | inline fun > createMapper() = 11 | T::class.java.constructors.first().newInstance() as T 12 | -------------------------------------------------------------------------------- /core/dialog/android-library/src/test/java/com/s097t0r1/ktcast/dialog/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.s097t0r1.ktcast.dialog 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /common/logout/android-library/src/test/java/me/s097t0r1/ktcast/common/logout/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.logout 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-android-library") 3 | id("kotlin-kapt") 4 | } 5 | 6 | android { 7 | namespace = "me.s097t0r1.ktcast.common.secure_storage" 8 | } 9 | 10 | dependencies { 11 | 12 | implementation(libs.androidx.core.ktx) 13 | implementation(projects.core.di.library) 14 | 15 | implementation(libs.androidx.security) 16 | 17 | implementation(libs.google.dagger) 18 | kapt(libs.google.dagger.compiler) 19 | 20 | } -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/java/me/s097t0r1/persistence/database/di/DatabaseComponent.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.persistence.database.di 2 | 3 | import dagger.Component 4 | 5 | @Component( 6 | dependencies = [DatabaseDependencies::class], 7 | modules = [DatabaseModule::class] 8 | ) 9 | interface DatabaseComponent : DatabaseAPI { 10 | 11 | @Component.Factory 12 | interface Factory { 13 | fun create(dependencies: DatabaseDependencies): DatabaseComponent 14 | } 15 | } -------------------------------------------------------------------------------- /core/debug-helper/android-library/src/test/java/me/s097t0r1/ktcast/core/debug_helper/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.core.debug_helper 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/host/HostViewModel.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base.host 2 | 3 | import androidx.lifecycle.ViewModel 4 | import me.s097t0r1.core.ui_components.components.AlertSnackBarHost 5 | import me.s097t0r1.ktcast.common.logout.LogoutHandler 6 | 7 | abstract class HostViewModel : ViewModel() { 8 | 9 | abstract fun alert(alertType: AlertSnackBarHost.AlertType, message: String) 10 | 11 | abstract fun logout(logoutType: LogoutHandler.LogoutType) 12 | 13 | } -------------------------------------------------------------------------------- /core/dialog/android-library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-android-library") 3 | id("compose-configurator") 4 | } 5 | 6 | android { 7 | namespace = "me.s097t0r1.ktcast.core.dialog" 8 | } 9 | 10 | dependencies { 11 | 12 | implementation(projects.core.uiComponents.androidLibrary) 13 | implementation(projects.core.mvi.androidLibrary) 14 | 15 | implementation(libs.androidx.core.ktx) 16 | implementation(libs.androidx.appcompat) 17 | implementation(libs.bundles.androidx.compose) 18 | 19 | } -------------------------------------------------------------------------------- /common/network/utils/src/main/java/me/s097t0r1/ktcast/common/network/utils/JsonUtils.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.network.utils 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 5 | 6 | private val moshi = Moshi.Builder() 7 | .add(KotlinJsonAdapterFactory()) 8 | .build() 9 | 10 | fun String.deserialize(clazz: Class): T? = moshi.adapter(clazz).fromJson(this) 11 | 12 | inline fun String.deserialize(): T? = this.deserialize(T::class.java) -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/di/NetworkComponentHolder.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.di 2 | 3 | import me.s097t0r1.core.di.base.BaseComponentHolder 4 | 5 | object NetworkComponentHolder : BaseComponentHolder() { 6 | 7 | override fun initComponent(dependencies: NetworkDependencies): NetworkAPI = 8 | DaggerNetworkComponent.factory().create(dependencies) 9 | 10 | internal fun getDaggerComponent() = getComponent() as NetworkComponent 11 | } -------------------------------------------------------------------------------- /common/persistence/database/android-library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-android-library") 3 | id("kotlin-kapt") 4 | } 5 | 6 | android { 7 | namespace = "me.s097t0r1.persistence.database" 8 | } 9 | 10 | dependencies { 11 | 12 | implementation(libs.androidx.room.runtime) 13 | implementation(libs.androidx.room.ktx) 14 | kapt(libs.androidx.room.compiler) 15 | 16 | implementation(libs.google.dagger) 17 | kapt(libs.google.dagger.compiler) 18 | 19 | implementation(projects.core.di.library) 20 | } -------------------------------------------------------------------------------- /libraries/utils/core/android_library/src/test/java/me/s097t0r1/ktcast/libraries/utils/core/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.utils.core 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/java/me/s097t0r1/persistence/database/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.persistence.database.di 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import me.s097t0r1.persistence.database.db.AppDatabase 7 | 8 | @Module 9 | internal abstract class DatabaseModule { 10 | 11 | companion object { 12 | 13 | @Provides 14 | fun provideAppDatabase(context: Context): AppDatabase = AppDatabase.getInstance(context) 15 | } 16 | } -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/src/main/java/me/s097t0r1/ktcast/common/secure_storage/di/SecureStorageComponentHolder.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.secure_storage.di 2 | 3 | import me.s097t0r1.core.di.base.BaseComponentHolder 4 | 5 | object SecureStorageComponentHolder : BaseComponentHolder() { 6 | 7 | override fun initComponent(dependencies: SecureStorageDependencies): SecureStorageAPI = 8 | DaggerSecureStorageComponent.factory().create(dependencies) 9 | 10 | } -------------------------------------------------------------------------------- /libraries/resource-provider/android-library/src/test/java/me/s097t0r1/ktcast/core/utils/resource_provier/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.resource_provier 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/java/me/s097t0r1/persistence/database/di/DatabaseComponentHolder.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.persistence.database.di 2 | 3 | import me.s097t0r1.core.di.base.BaseComponentHolder 4 | 5 | object DatabaseComponentHolder : BaseComponentHolder() { 6 | 7 | override fun initComponent(dependencies: DatabaseDependencies) = 8 | DaggerDatabaseComponent.factory().create(dependencies) 9 | 10 | internal fun getDaggerComponent() = getComponent() as DatabaseComponent 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/MainViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | 6 | class MainViewModelFactory : ViewModelProvider.Factory { 7 | 8 | @Suppress("UNCHECKED_CAST") 9 | override fun create(modelClass: Class): T { 10 | if (modelClass.isAssignableFrom(MainViewModel::class.java)) { 11 | return MainViewModel() as T 12 | } 13 | 14 | error("Cannot inistiate MainViewModel") 15 | } 16 | } -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /libraries/resource-provider/android-library/src/main/java/me/s097t0r1/ktcast/libraries/resource_provider/AndroidResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.resource_provider 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | 6 | class AndroidResourceProvider( 7 | private val context: Context 8 | ) : ResourceProvider { 9 | 10 | override fun getString(id: Int): String = context.getString(id) 11 | 12 | override fun getDrawable(id: Int): Drawable = context.getDrawable(id) 13 | ?: error("This drawable doesn't exist") 14 | } -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/host/HostSideEffect.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base.host 2 | 3 | import me.s097t0r1.core.mvi.base.state.BaseSideEffect 4 | import me.s097t0r1.core.ui_components.components.AlertSnackBarHost 5 | import me.s097t0r1.ktcast.common.logout.LogoutHandler 6 | 7 | sealed class HostSideEffect : BaseSideEffect { 8 | class Alert( 9 | val alertType: AlertSnackBarHost.AlertType, 10 | val message: String 11 | ) : HostSideEffect() 12 | class Logout(val logoutType: LogoutHandler.LogoutType) : HostSideEffect() 13 | } 14 | -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/src/main/java/me/s097t0r1/ktcast/common/secure_storage/di/SecureStorageModule.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.secure_storage.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import me.s097t0r1.ktcast.common.secure_storage.storage.AndroidSecureStorage 6 | import me.s097t0r1.ktcast.common.secure_storage.storage.SecureStorage 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | internal interface SecureStorageModule { 11 | 12 | @Binds 13 | @Singleton 14 | fun bindSecureStorage(secureStorage: AndroidSecureStorage): SecureStorage 15 | } -------------------------------------------------------------------------------- /libraries/deps-holder-proccessor/library/src/main/java/me/s097t0r1/deps_holder_processor/MetaInfoProccessor.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.deps_holder_processor 2 | 3 | import com.squareup.moshi.Json 4 | 5 | class MetaInfoProccessor( 6 | @Json(name = "package_location") 7 | val packageLocation: String, 8 | 9 | @Json(name = "feature_api_package") 10 | val baseFeatureApiPackage: String, 11 | 12 | @Json(name = "feature_dependency_package") 13 | val baseFeatureDependencyPackage: String, 14 | 15 | @Json(name = "dependency_holder_package") 16 | val baseDependencyHolderPackage: String 17 | ) 18 | -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/src/main/java/me/s097t0r1/ktcast/common/secure_storage/di/SecureStorageComponent.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.secure_storage.di 2 | 3 | import dagger.Component 4 | import javax.inject.Singleton 5 | 6 | @Singleton 7 | @Component( 8 | dependencies = [SecureStorageDependencies::class], 9 | modules = [SecureStorageModule::class] 10 | ) 11 | internal interface SecureStorageComponent : SecureStorageAPI { 12 | 13 | @Component.Factory 14 | interface Factory { 15 | fun create(dependencies: SecureStorageDependencies): SecureStorageComponent 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/di/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.di 2 | 3 | import android.content.Context 4 | import dagger.BindsInstance 5 | import dagger.Component 6 | import me.s097t0r1.ktcast.libraries.resource_provider.ResourceProvider 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | @Component( 11 | modules = [ApplicationModule::class] 12 | ) 13 | interface ApplicationComponent { 14 | 15 | fun getResourceProvider(): ResourceProvider 16 | 17 | @Component.Factory 18 | interface Factory { 19 | fun create(@BindsInstance context: Context): ApplicationComponent 20 | } 21 | } -------------------------------------------------------------------------------- /config/detekt-compose.yml: -------------------------------------------------------------------------------- 1 | compose: 2 | ReusedModifierInstance: 3 | active: true 4 | UnnecessaryEventHandlerParameter: 5 | active: true 6 | ComposableEventParameterNaming: 7 | active: true 8 | ComposableParametersOrdering: 9 | active: true 10 | ModifierHeightWithText: 11 | active: true 12 | ModifierParameterPosition: 13 | active: true 14 | ModifierDefaultValue: 15 | active: true 16 | MissingModifierDefaultValue: 17 | active: true 18 | PublicComposablePreview: 19 | active: true 20 | TopLevelComposableFunctions: 21 | active: false 22 | ComposeFunctionName: 23 | active: true 24 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/factory/NetworkServiceFactory.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.factory 2 | 3 | import retrofit2.Retrofit 4 | 5 | interface NetworkService 6 | 7 | interface NetworkServiceFactory { 8 | 9 | fun create(serviceClass: Class): T 10 | 11 | class Base(private val retrofit: Retrofit) : NetworkServiceFactory { 12 | 13 | override fun create(serviceClass: Class): T = 14 | retrofit.create(serviceClass) 15 | } 16 | } 17 | 18 | inline fun NetworkServiceFactory.create() = this.create(T::class.java) -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-impl/root/src/main/java/package/di/FeatureModule.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.impl.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.Provides 6 | <#if (includeStarter)> 7 | import ${packageName}.api.AuthorizationFeatureStarter 8 | import me.s097t0r1.core.navigation.screen.FragmentScreen 9 | 10 | 11 | @Module 12 | internal abstract class ${__formattedModuleName}Module { 13 | 14 | companion object { 15 | 16 | <#if (includeStarter)> 17 | @Provides 18 | fun provideStarter(): ${__formattedModuleName}FeatureStarter = TODO() 19 | 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/command/NavigationCommand.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.command 2 | 3 | import me.s097t0r1.core.navigation.screen.NavigationScreen 4 | 5 | sealed class NavigationCommand { 6 | 7 | class Forward(val screen: NavigationScreen) : NavigationCommand() 8 | 9 | class Replace(val screen: NavigationScreen) : NavigationCommand() 10 | 11 | object Back : NavigationCommand() 12 | 13 | class BackTo(val screen: NavigationScreen) : NavigationCommand() 14 | 15 | companion object { 16 | const val DEFAULT_RECEIVER = "NavigationMessage.DEFAULT_RECIVER" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/di/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.di 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import me.s097t0r1.ktcast.libraries.resource_provider.AndroidResourceProvider 7 | import me.s097t0r1.ktcast.libraries.resource_provider.ResourceProvider 8 | import javax.inject.Singleton 9 | 10 | @Module 11 | abstract class ApplicationModule { 12 | 13 | companion object { 14 | 15 | @Provides 16 | @Singleton 17 | fun provideResourceProvider( 18 | context: Context 19 | ): ResourceProvider = AndroidResourceProvider(context) 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/call_adapter/ReactionCallAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.call_adapter 2 | 3 | import java.lang.reflect.Type 4 | import me.s097t0r1.core.exceptions.library.AppException 5 | import me.s097t0r1.ktcast.libraries.either.Either 6 | import retrofit2.Call 7 | import retrofit2.CallAdapter 8 | 9 | internal class EitherCallAdapter( 10 | private val responseType: Type 11 | ) : CallAdapter>> { 12 | 13 | override fun responseType(): Type = responseType 14 | 15 | override fun adapt(call: Call): Call> { 16 | return EitherCall(call) 17 | } 18 | } -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/router/AppRouter.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.router 2 | 3 | import me.s097t0r1.core.navigation.dispatcher.NavigationDispatcher 4 | import me.s097t0r1.core.navigation.message.NavigationMessage 5 | 6 | class AppRouter : Router { 7 | 8 | private var dispatcher: NavigationDispatcher? = null 9 | 10 | override fun navigate(message: NavigationMessage) { 11 | dispatcher?.dispatch(message) 12 | } 13 | 14 | override fun attachDispatcher(dispatcher: NavigationDispatcher) { 15 | this.dispatcher = dispatcher 16 | } 17 | 18 | override fun dettachDispatcher() { 19 | this.dispatcher = null 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/drawable/ic_info_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/s097t0r1/ktcast/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("me.s097t0r1.ktcast", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-impl/root/src/main/java/package/di/FeatureComponentHolder.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.impl.di 2 | 3 | import me.s097t0r1.core.di.base.BaseComponentHolder 4 | import ${packageName}.api.${__formattedModuleName}FeatureAPI 5 | import ${packageName}.api.${__formattedModuleName}FeatureDependencies 6 | 7 | object ${__formattedModuleName}ComponentHolder : BaseComponentHolder<${__formattedModuleName}FeatureAPI, ${__formattedModuleName}FeatureDependencies>() { 8 | 9 | override fun initComponent(dependencies: ${__formattedModuleName}FeatureDependencies): ${__formattedModuleName}FeatureAPI = 10 | Dagger${__formattedModuleName}Component.factory().create(dependencies) 11 | 12 | internal fun getDaggerComponent() = getComponent() as ${__formattedModuleName}Component 13 | } 14 | -------------------------------------------------------------------------------- /core/debug-helper/android-library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ktcast-android-library") 3 | } 4 | 5 | android { 6 | buildFeatures { 7 | buildConfig = true 8 | } 9 | 10 | namespace = "me.s097t0r1.ktcast.core.debug_helper" 11 | } 12 | 13 | dependencies { 14 | 15 | implementation(libs.androidx.core.ktx) 16 | implementation(libs.androidx.appcompat) 17 | 18 | debugImplementation(libs.pandulapetor.beagle) 19 | releaseImplementation(libs.pandulapetor.beagle.noop) 20 | 21 | debugImplementation(libs.pandulapetor.beagle.okhttp) 22 | releaseImplementation(libs.pandulapetor.beagle.okhttp.noop) 23 | 24 | debugImplementation(libs.pandulapetor.beagle.logger) 25 | releaseImplementation(libs.pandulapetor.beagle.logger.noop) 26 | 27 | implementation(libs.jakewharton.timber) 28 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/dialog/android-library/src/androidTest/java/com/s097t0r1/ktcast/dialog/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.s097t0r1.ktcast.dialog 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.s097t0r1.ktcast.dialog.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/interceptors/ApplicationInterceptor.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.interceptors 2 | 3 | import me.s097t0r1.common.network.CONTENT_TYPE_HEADER_NAME 4 | import okhttp3.Interceptor 5 | import okhttp3.Request 6 | import okhttp3.Response 7 | 8 | internal class ApplicationInterceptor : Interceptor { 9 | 10 | override fun intercept(chain: Interceptor.Chain): Response { 11 | return chain.proceed( 12 | chain.request().newBuilder() 13 | .addContentTypeHeader() 14 | .build() 15 | ) 16 | } 17 | 18 | private fun Request.Builder.addContentTypeHeader() = 19 | addHeader(CONTENT_TYPE_HEADER_NAME, APPLICATION_JSON) 20 | 21 | companion object { 22 | private const val APPLICATION_JSON = "application/json" 23 | } 24 | } -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/di/NetworkComponent.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.di 2 | 3 | import dagger.Component 4 | import me.s097t0r1.common.network.factory.NetworkServiceFactory 5 | import javax.inject.Named 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | @Component( 10 | dependencies = [NetworkDependencies::class], 11 | modules = [NetworkModule::class] 12 | ) 13 | internal interface NetworkComponent : NetworkAPI { 14 | 15 | @get:Named(NetworkModule.AUTHORIZED) 16 | override val authorizedServiceFactory: NetworkServiceFactory 17 | 18 | @get:Named(NetworkModule.UNAUTHORIZED) 19 | override val unauthorizedServiceFactory: NetworkServiceFactory 20 | 21 | @Component.Factory 22 | interface Factory { 23 | fun create(dependencies: NetworkDependencies): NetworkComponent 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/java/me/s097t0r1/core/ui_components/components/ProgressBar.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.ui_components.components 2 | 3 | import androidx.compose.material.CircularProgressIndicator 4 | import androidx.compose.material.ProgressIndicatorDefaults 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.unit.Dp 9 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 10 | 11 | @Composable 12 | fun KtCastCircularProgressBar( 13 | modifier: Modifier = Modifier, 14 | color: Color = KtCastTheme.colors.primaryColor, 15 | strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth 16 | ) { 17 | CircularProgressIndicator( 18 | modifier = modifier, 19 | color = color, 20 | strokeWidth = strokeWidth 21 | ) 22 | } -------------------------------------------------------------------------------- /common/logout/android-library/src/androidTest/java/me/s097t0r1/ktcast/common/logout/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.logout 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("me.s097t0r1.ktcast.common.logout.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.google.devtools.ksp") version "2.0.20-1.0.25" 3 | id("ktcast-android-application") 4 | id("compose-configurator") 5 | } 6 | 7 | kotlin { 8 | sourceSets.debug { 9 | kotlin.srcDir("build/generated/ksp/debug/kotlin") 10 | } 11 | sourceSets.main { 12 | kotlin.srcDir("build/generated/ksp/main/kotlin") 13 | } 14 | } 15 | 16 | android { 17 | namespace = "me.s097t0r1.ktcast" 18 | } 19 | 20 | dependencies { 21 | 22 | implementation(projects.libraries.depsHolderProccessor.library) 23 | ksp(projects.libraries.depsHolderProccessor.library) 24 | 25 | implementation(libs.google.material) 26 | implementation(libs.androidx.compose.viewbinding) 27 | 28 | implementation(libs.bundles.orbit.mvi) 29 | 30 | } 31 | 32 | ksp { 33 | arg("jsonConfigurationFilePath", "$rootDir/app/dhconfig.json") 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/KtCastApplication.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast 2 | 3 | import android.app.Application 4 | import me.s097t0r1.ktcast.core.debug_helper.KtCastDebugHelper 5 | import me.s097t0r1.ktcast.di.ApplicationComponent 6 | import me.s097t0r1.ktcast.di.DaggerApplicationComponent 7 | import me.s097t0r1.ktcast.glue.glueModules 8 | 9 | class KtCastApplication : Application() { 10 | 11 | val component: ApplicationComponent by lazy { 12 | DaggerApplicationComponent.factory().create(this) 13 | } 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | INSTANCE = this 18 | glueModules(this) 19 | KtCastDebugHelper.initialize(this).also { 20 | KtCastDebugHelper.Logger.init() 21 | } 22 | } 23 | 24 | companion object { 25 | lateinit var INSTANCE: KtCastApplication 26 | } 27 | 28 | 29 | } -------------------------------------------------------------------------------- /core/debug-helper/android-library/src/androidTest/java/me/s097t0r1/ktcast/core/debug_helper/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.core.debug_helper 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("me.s097t0r1.ktcast.core.debug_helper.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /core/exceptions/library/src/main/java/me/s097t0r1/core/exceptions/library/AppException.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.exceptions.library 2 | 3 | sealed class AppException : Throwable() { 4 | sealed class NetworkException(override val message: String? = null) : AppException() { 5 | object NoInternetConnectionException : NetworkException() 6 | object TimeoutException : NetworkException() 7 | class HttpException( 8 | val code: Int, 9 | val messages: List 10 | ) : NetworkException() { 11 | companion object { 12 | const val UNATHORIZED_CODE = 401 13 | } 14 | } 15 | object InternalServerException : NetworkException() 16 | object UnknownException : NetworkException() 17 | } 18 | 19 | open class LocalException(override val message: String? = null) : AppException() 20 | } 21 | -------------------------------------------------------------------------------- /libraries/utils/core/android_library/src/androidTest/java/me/s097t0r1/ktcast/libraries/utils/core/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.utils.core 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("me.s097t0r1.ktcast.libraries.utils.core.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /.idea/geminio_plugin_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-impl/root/src/main/java/package/di/FeatureComponent.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.impl.di 2 | 3 | import dagger.Component 4 | import me.s097t0r1.core.di.base.scope.FeatureScope 5 | import ${packageName}.api.${__formattedModuleName}FeatureAPI 6 | import ${packageName}.api.${__formattedModuleName}FeatureDependencies 7 | import me.s097t0r1.ktcast.libraries.factory.ViewModelFactoryModule 8 | 9 | @FeatureScope 10 | @Component( 11 | dependencies = [${__formattedModuleName}FeatureDependencies::class], 12 | modules = [ViewModelFactoryModule::class, ${__formattedModuleName}Module::class], 13 | ) 14 | internal interface ${__formattedModuleName}Component : ${__formattedModuleName}FeatureAPI { 15 | 16 | @Component.Factory 17 | interface Factory { 18 | fun create(dependencies: ${__formattedModuleName}FeatureDependencies): ${__formattedModuleName}Component 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/drawable/ic_toolbar_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /libraries/resource-provider/android-library/src/androidTest/java/me/s097t0r1/ktcast/core/utils/resource_provier/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.resource_provier 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("me.s097t0r1.ktcast.libraries.resource_provier.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /config/detekt.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 32 3 | 4 | naming: 5 | FunctionNaming: 6 | active: true 7 | functionPattern: '[a-zA-Z][a-zA-Z0-9]*' 8 | TopLevelPropertyNaming: 9 | active: true 10 | constantPattern: '[A-Z][A-Za-z0-9_]*' 11 | PackageNaming: 12 | active: true 13 | packagePattern: '[a-z]+(\.[a-z][a-z0-9_]*)*' 14 | 15 | formatting: 16 | NoWildcardImports: 17 | active: false 18 | 19 | complexity: 20 | LongParameterList: 21 | ignoreAnnotated: 22 | - 'Composable' 23 | LongMethod: 24 | ignoreAnnotated: 25 | - 'Composable' 26 | TooManyFunctions: 27 | active: false 28 | 29 | style: 30 | UnusedPrivateMember: 31 | ignoreAnnotated: 32 | - 'Preview' 33 | MagicNumber: 34 | ignorePropertyDeclaration: true 35 | ignoreCompanionObjectPropertyDeclaration: true 36 | WildcardImport: 37 | active: false 38 | MaxLineLength: 39 | active: true 40 | maxLineLength: 120 41 | 42 | -------------------------------------------------------------------------------- /core/debug-helper/android-library/src/main/java/me/s097t0r1/ktcast/core/debug_helper/KtCastDebugStorage.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.core.debug_helper 2 | 3 | import android.content.Context 4 | import androidx.core.content.edit 5 | import me.s097t0r1.ktcast.core.debug_helper.modules.stand_module.Stand 6 | 7 | class KtCastDebugStorage(context: Context) { 8 | 9 | private val storage = context.getSharedPreferences( 10 | STORAGE_NAME, 11 | Context.MODE_PRIVATE, 12 | ) 13 | 14 | var stand: Stand 15 | get() = Stand.valueOf( 16 | storage.getString(STAND_KEY, null) 17 | ?: Stand.MOCKER.name 18 | ) 19 | set(value) { 20 | storage.edit { 21 | putString(STAND_KEY, value.name) 22 | } 23 | } 24 | 25 | 26 | companion object { 27 | private const val STORAGE_NAME = "me.s097t0r1.ktcast.core.debug_helper" 28 | 29 | private const val STAND_KEY = "STAND_KEY" 30 | } 31 | } -------------------------------------------------------------------------------- /.github/workflows/pull_request.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | # Triggers the workflow on push or pull request events but only for default and protected branches 3 | push: 4 | branches: [ "main", "dev" ] 5 | pull_request: 6 | branches: [ "main", "dev" ] 7 | schedule: 8 | - cron: '41 17 * * 1' 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | jobs: 12 | static_analysis: 13 | name: Run static analyses on code 14 | runs-on: ubuntu-latest 15 | env: 16 | GRADLE_OPTS: -Dorg.gradle.daemon=false 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-java@v3 20 | with: 21 | distribution: 'adopt-hotspot' 22 | java-version: '11' 23 | - name: Run detekt 24 | run: ./gradlew detekt 25 | - name: Sarif 26 | uses: github/codeql-action/upload-sarif@v2 27 | if: success() || failure() 28 | with: 29 | sarif_file: build/reports/detekt/merge.sarif 30 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /core/di/library/src/main/java/me/s097t0r1/core/di/base/BaseComponentHolder.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.di.base 2 | 3 | import java.lang.ref.WeakReference 4 | 5 | abstract class BaseComponentHolder { 6 | 7 | private var weakRef: WeakReference? = null 8 | 9 | lateinit var provider: Provider 10 | 11 | protected abstract fun initComponent(dependencies: D): A 12 | 13 | fun get(): A = getComponent() 14 | 15 | protected fun getComponent(): A { 16 | var component: A? = null 17 | 18 | synchronized(this) { 19 | if (this::provider.isInitialized) { 20 | component = weakRef?.get() 21 | 22 | if (component == null) { 23 | component = initComponent(provider.provide()) 24 | weakRef = WeakReference(component) 25 | } 26 | } else { 27 | error("Dependency provider is not initialized") 28 | } 29 | } 30 | 31 | return requireNotNull(component) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/persistence/database/android-library/src/main/java/me/s097t0r1/persistence/database/db/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.persistence.database.db 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import me.s097t0r1.persistence.database.StubEntity 8 | 9 | @Database( 10 | entities = [StubEntity::class], 11 | version = 1, 12 | exportSchema = false 13 | ) 14 | abstract class AppDatabase : RoomDatabase() { 15 | 16 | companion object { 17 | const val DATABASE_NAME = "WE_TALK_DATABASE" 18 | 19 | private lateinit var INSTANCE: AppDatabase 20 | 21 | fun getInstance(context: Context): AppDatabase { 22 | 23 | if (!this::INSTANCE.isInitialized) { 24 | synchronized(this) { 25 | INSTANCE = Room 26 | .databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) 27 | .build() 28 | } 29 | } 30 | 31 | return INSTANCE 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /core/mvi/android-library/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("compose-configurator") 4 | id("module-configurator") 5 | } 6 | 7 | android { 8 | buildFeatures { 9 | viewBinding = true 10 | } 11 | namespace = "me.s097t0r1.core.mvi" 12 | } 13 | 14 | dependencies { 15 | 16 | implementation(libs.androidx.core.ktx) 17 | implementation(libs.androidx.appcompat) 18 | implementation(libs.androidx.fragment.ktx) 19 | implementation(libs.androidx.lifecycle.viewmodel.ktx) 20 | implementation(libs.androidx.lifecycle.runtime.ktx) 21 | implementation(libs.androidx.compose.viewbinding) 22 | implementation(libs.bundles.androidx.compose) 23 | implementation(libs.bundles.orbit.mvi) 24 | 25 | implementation(projects.core.mvi.res) 26 | implementation(projects.core.uiComponents.androidLibrary) 27 | implementation(projects.core.exceptions.library) 28 | implementation(projects.core.uiComponents.res) 29 | implementation(projects.core.navigation.androidLibrary) 30 | implementation(projects.common.logout.androidLibrary) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nikita Zmitrovich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast 2 | 3 | import me.s097t0r1.core.mvi.base.host.HostViewModel 4 | import me.s097t0r1.core.ui_components.components.AlertSnackBarHost 5 | import me.s097t0r1.ktcast.common.logout.LogoutHandler 6 | import me.s097t0r1.ktcast.common.secure_storage.di.SecureStorageComponentHolder 7 | import me.s097t0r1.ktcast.mvi.MainSideEffect 8 | import me.s097t0r1.ktcast.mvi.MainUIState 9 | import org.orbitmvi.orbit.ContainerHost 10 | import org.orbitmvi.orbit.syntax.simple.intent 11 | import org.orbitmvi.orbit.syntax.simple.postSideEffect 12 | import org.orbitmvi.orbit.viewmodel.container 13 | 14 | class MainViewModel : HostViewModel(), ContainerHost { 15 | 16 | override val container = container(MainUIState()) 17 | 18 | override fun alert(alertType: AlertSnackBarHost.AlertType, message: String) = intent { 19 | postSideEffect(MainSideEffect.Alert(alertType, message)) 20 | } 21 | 22 | override fun logout(logoutType: LogoutHandler.LogoutType) { 23 | SecureStorageComponentHolder.get().storage.clear() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /common/network/android-library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("ktcast-android-library") 4 | id("kotlin-kapt") 5 | } 6 | 7 | android { 8 | namespace = "me.s097t0r1.ktcast.common.network" 9 | } 10 | 11 | dependencies { 12 | 13 | implementation(projects.common.network.utils) 14 | 15 | implementation(libs.google.dagger) 16 | kapt(libs.google.dagger.compiler) 17 | 18 | implementation(libs.squareup.retrofit) 19 | implementation(libs.squareup.okhttp) 20 | implementation(libs.squareup.okhttp.logger) 21 | implementation(libs.squareup.moshi) 22 | implementation(libs.squareup.moshi.converter) 23 | implementation(libs.jetbrains.coroutines.android) 24 | 25 | implementation(projects.core.debugHelper.androidLibrary) 26 | 27 | debugImplementation(libs.pandulapetor.beagle.okhttp) 28 | releaseImplementation(libs.pandulapetor.beagle.okhttp.noop) 29 | 30 | implementation(projects.core.di.library) 31 | implementation(projects.core.exceptions.library) 32 | implementation(projects.libraries.either.library) 33 | 34 | implementation(projects.common.persistence.secureStorage.androidLibrary) 35 | } 36 | -------------------------------------------------------------------------------- /libraries/viewmodel-factory/android-library/src/main/java/me/s097t0r1/ktcast/libraries/factory/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.factory 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | 8 | class ViewModelFactory @Inject constructor( 9 | private val map: Map, @JvmSuppressWildcards Provider> 10 | ) : ViewModelProvider.Factory { 11 | 12 | @Suppress("UNCHECKED_CAST") 13 | override fun create(modelClass: Class): T { 14 | var viewModelProvider = map[modelClass] 15 | return if (viewModelProvider == null) { 16 | viewModelProvider = map.toList() 17 | .find { modelClass.isAssignableFrom(it.first) }?.second 18 | requireNotNull(viewModelProvider) { 19 | "Provider for $modelClass didn't find" 20 | } 21 | viewModelProvider.get() as? T ?: error("Cannot create viewmodel with $modelClass") 22 | } else { 23 | viewModelProvider.get() as? T ?: error("Cannot create viewmodel with $modelClass") 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /libraries/deps-holder-proccessor/library/src/main/java/me/s097t0r1/deps_holder_processor/DHProcessorProvider.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.deps_holder_processor 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessor 4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider 6 | import com.squareup.moshi.Moshi 7 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 8 | import java.io.File 9 | 10 | class DHProcessorProvider : SymbolProcessorProvider { 11 | 12 | private val moshi = Moshi.Builder() 13 | .addLast(KotlinJsonAdapterFactory()) 14 | .build() 15 | 16 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { 17 | return DHProcessor( 18 | environment.codeGenerator, 19 | environment.logger, 20 | moshi.adapter(MetaInfoProccessor::class.java).fromJson( 21 | File( 22 | environment.options["jsonConfigurationFilePath"] 23 | ?: error("Configuration file path not specified") 24 | ).bufferedReader().readText() 25 | ) ?: error("Cannot read json configurartion file") 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /core/debug-helper/android-library/src/main/java/me/s097t0r1/ktcast/core/debug_helper/modules/stand_module/NetworkStandModule.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.core.debug_helper.modules.stand_module 2 | 3 | import android.widget.Toast 4 | import com.pandulapeter.beagle.Beagle 5 | import com.pandulapeter.beagle.common.configuration.Text 6 | import com.pandulapeter.beagle.common.contracts.BeagleListItemContract 7 | import com.pandulapeter.beagle.modules.SingleSelectionListModule 8 | import me.s097t0r1.ktcast.core.debug_helper.KtCastDebugHelper 9 | 10 | internal fun NetworkStandModule() = SingleSelectionListModule( 11 | title = "Select STAND", 12 | items = Stand.values().map(::StandButtonModuleItem), 13 | initiallySelectedItemId = KtCastDebugHelper.storage.stand.name, 14 | onSelectionChanged = { 15 | if (it == null) return@SingleSelectionListModule 16 | KtCastDebugHelper.storage.stand = it.stand 17 | Beagle.performOnHide { 18 | Toast.makeText( 19 | Beagle.currentActivity, 20 | "Please completely restart app", 21 | Toast.LENGTH_LONG 22 | ).show() 23 | }.also { Beagle.hide() } 24 | } 25 | ) 26 | 27 | data class StandButtonModuleItem(val stand: Stand) : BeagleListItemContract { 28 | 29 | override val id: String = stand.name 30 | 31 | override val title: Text = Text.CharSequence(stand.name) 32 | } 33 | -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/java/me/s097t0r1/core/ui_components/theme/KtCastColorPallete.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.ui_components.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | object KtCastColorPallete { 6 | 7 | val primaryColor = Color(0xFF9610FF) 8 | val primary100Color = Color(0xFFF5E7FF) 9 | 10 | val secondaryColor = Color(0xFFFFD300) 11 | 12 | val statusSuccessColor = Color(0xFF0ABE75) 13 | val statusInfoColor = Color(0xFF246BFD) 14 | val statusWarningColor = Color(0xFFFACC15) 15 | val statusErrorColor = Color(0xFFF75555) 16 | val statusDisabledColor = Color(0xFFD8D8D8) 17 | val statusDisabledButton = Color(0xFF7817C3) 18 | 19 | val grayScale900Color = Color(0xFF212121) 20 | val grayScale800Color = Color(0xFF424242) 21 | val grayScale700Color = Color(0xFF616161) 22 | val grayScale600Color = Color(0xFF757575) 23 | val grayScale500Color = Color(0xFF9E9E9E) 24 | val grayScale400Color = Color(0xFFBDBDBD) 25 | val grayScale300Color = Color(0xFF757575) 26 | val grayScale200Color = Color(0xFFE0E0E0) 27 | val grayScale100Color = Color(0xFFEEEEEE) 28 | val grayScale50Color = Color(0xFFFAFAFA) 29 | 30 | val dark1Color = Color(0xFF181A20) 31 | val dark2Color = Color(0xFF1F222A) 32 | val dark3Color = Color(0xFF35383F) 33 | 34 | val otherWhiteColor = Color(0xFFFFFFFF) 35 | val otherBlackColor = Color(0xFF000000) 36 | 37 | val transperentPurpleColor = Color(0x149610FF) 38 | } -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/dispatcher/FragmentNavigationDispatcher.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.dispatcher 2 | 3 | import androidx.fragment.app.Fragment 4 | import me.s097t0r1.core.navigation.command.NavigationCommand 5 | import me.s097t0r1.core.navigation.message.NavigationMessage 6 | 7 | abstract class FragmentNavigationDispatcher( 8 | private val host: Fragment 9 | ) : NavigationDispatcher { 10 | 11 | abstract fun isSupportMessage(navigationMessage: NavigationMessage): Boolean 12 | 13 | override fun dispatch(navigationMessage: NavigationMessage) { 14 | if (!isSupportMessage(navigationMessage)) { 15 | dispatchToParentNode(navigationMessage) 16 | } else { 17 | if (navigationMessage.receiver != NavigationCommand.DEFAULT_RECEIVER && 18 | receiverTag != navigationMessage.receiver 19 | ) { 20 | dispatchToParentNode(navigationMessage) 21 | } else { 22 | (host as? NavigationDispatcherHost)?.accept(navigationMessage) 23 | } 24 | } 25 | } 26 | 27 | private fun dispatchToParentNode(navigationMessage: NavigationMessage) { 28 | val parentNode = host.parentFragment ?: host.activity 29 | if (parentNode == null || parentNode !is NavigationDispatcherHost) { 30 | throw RuntimeException("Navigation host supported $navigationMessage not found") 31 | } 32 | parentNode.dispatcher.dispatch(navigationMessage) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/call_adapter/ReactionCallAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.call_adapter 2 | 3 | import com.squareup.moshi.rawType 4 | import java.lang.reflect.ParameterizedType 5 | import java.lang.reflect.Type 6 | import me.s097t0r1.core.exceptions.library.AppException 7 | import me.s097t0r1.ktcast.libraries.either.Either 8 | import retrofit2.Call 9 | import retrofit2.CallAdapter 10 | import retrofit2.Retrofit 11 | 12 | internal class EitherCallAdapterFactory : CallAdapter.Factory() { 13 | override fun get( 14 | returnType: Type, 15 | annotations: Array, 16 | retrofit: Retrofit 17 | ): CallAdapter<*, *>? { 18 | if (getRawType(returnType) != Call::class.java) return null 19 | check( 20 | returnType is ParameterizedType 21 | && getParameterUpperBound(0, returnType).rawType == Either::class.java 22 | ) { 23 | "Call must be parametrized like: Call>" 24 | } 25 | 26 | val either = getParameterUpperBound(0, returnType) 27 | check(either is ParameterizedType 28 | && getParameterUpperBound(1, either) 29 | .rawType 30 | .isAssignableFrom(AppException.NetworkException::class.java) 31 | ) { 32 | "Either must be parameterized like: Either" 33 | } 34 | 35 | return EitherCallAdapter( 36 | getParameterUpperBound(0, either) 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | includeBuild("build-logic") 5 | repositories { 6 | gradlePluginPortal() 7 | google() 8 | mavenCentral() 9 | } 10 | } 11 | 12 | dependencyResolutionManagement { 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | } 18 | 19 | rootProject.name = "ktcast" 20 | 21 | include(":app") 22 | 23 | // Core 24 | 25 | include(":core:di:library") 26 | 27 | include(":core:navigation:android-library") 28 | 29 | include(":core:exceptions:library") 30 | 31 | include(":core:ui-components:android-library") 32 | include(":core:ui-components:res") 33 | 34 | include(":core:mvi:android-library") 35 | include(":core:mvi:android-utils") 36 | include(":core:mvi:res") 37 | 38 | include(":core:debug-helper:android-library") 39 | 40 | include(":core:dialog:android-library") 41 | 42 | // Common 43 | 44 | include(":common:network:android-library") 45 | include(":common:network:utils") 46 | 47 | include(":common:persistence:database:android-library") 48 | 49 | include(":common:persistence:secure-storage:android-library") 50 | 51 | include(":common:logout:android-library") 52 | 53 | // Libraries 54 | 55 | include(":libraries:mapper:library") 56 | include(":libraries:either:library") 57 | include(":libraries:viewmodel-factory:android-library") 58 | include(":libraries:resource-provider:android-library") 59 | 60 | include(":libraries:utils:core:android_library") 61 | include(":libraries:deps-holder-proccessor:library") 62 | include(":core:core-di") 63 | -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/java/me/s097t0r1/core/ui_components/components/Checkbox.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.ui_components.components 2 | 3 | import androidx.compose.foundation.interaction.MutableInteractionSource 4 | import androidx.compose.material.Checkbox 5 | import androidx.compose.material.CheckboxColors 6 | import androidx.compose.material.CheckboxDefaults 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.ui.Modifier 10 | import me.s097t0r1.core.ui_components.theme.KtCastColorPallete 11 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 12 | 13 | @Composable 14 | fun KtCastCheckbox( 15 | checked: Boolean, 16 | onCheckedChange: ((Boolean) -> Unit)?, 17 | modifier: Modifier = Modifier, 18 | enabled: Boolean = true, 19 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 20 | colors: CheckboxColors = CheckboxDefaults.colors( 21 | checkedColor = KtCastTheme.colors.primaryColor, 22 | uncheckedColor = KtCastTheme.colors.primaryColor, 23 | checkmarkColor = KtCastColorPallete.otherWhiteColor, 24 | disabledColor = KtCastTheme.colors.buttonDisabledBackgroundColor, 25 | disabledIndeterminateColor = KtCastTheme.colors.buttonDisabledBackgroundColor 26 | ) 27 | ) { 28 | Checkbox( 29 | checked = checked, 30 | onCheckedChange = onCheckedChange, 31 | modifier = modifier, 32 | enabled = enabled, 33 | interactionSource = interactionSource, 34 | colors = colors 35 | ) 36 | } -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/java/me/s097t0r1/core/ui_components/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.ui_components.theme 2 | 3 | import androidx.compose.foundation.LocalIndication 4 | import androidx.compose.foundation.isSystemInDarkTheme 5 | import androidx.compose.material.ContentAlpha 6 | import androidx.compose.material.LocalContentAlpha 7 | import androidx.compose.material.LocalContentColor 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.CompositionLocalProvider 10 | import androidx.compose.runtime.ReadOnlyComposable 11 | import androidx.compose.runtime.remember 12 | 13 | @Composable 14 | fun KtCastTheme( 15 | isDarkTheme: Boolean = isSystemInDarkTheme(), 16 | colors: KtCastColors = if (isDarkTheme) darkColors() else lightColors(), 17 | typography: KtCastTypography = KtCastTheme.typography, 18 | Content: @Composable () -> Unit 19 | ) { 20 | val remeberedColors = remember { colors.copy() }.apply { updateFrom(colors) } 21 | CompositionLocalProvider( 22 | LocalColors provides remeberedColors, 23 | LocalContentAlpha provides ContentAlpha.high, 24 | LocalContentColor provides remeberedColors.textPrimaryColor, 25 | LocalTypography provides typography 26 | ) { Content() } 27 | } 28 | 29 | object KtCastTheme { 30 | 31 | val colors: KtCastColors 32 | @Composable 33 | @ReadOnlyComposable 34 | get() = LocalColors.current 35 | 36 | val typography: KtCastTypography 37 | @Composable 38 | @ReadOnlyComposable 39 | get() = LocalTypography.current 40 | 41 | } -------------------------------------------------------------------------------- /core/dialog/android-library/src/main/java/me/s097t0r1/ktcast/core/dialog/ProgressDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.core.dialog 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.material.Surface 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.compose.ui.unit.dp 14 | import me.s097t0r1.core.mvi.base.BaseDialogFragment 15 | import me.s097t0r1.core.ui_components.components.KtCastCircularProgressBar 16 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 17 | 18 | class ProgressDialogFragment : BaseDialogFragment() { 19 | 20 | 21 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 22 | return super.onCreateDialog(savedInstanceState).apply { 23 | setCanceledOnTouchOutside(false) 24 | setCancelable(false) 25 | } 26 | } 27 | 28 | @Preview 29 | @Composable 30 | override fun Content() { 31 | Surface( 32 | modifier = Modifier.size(200.dp), 33 | shape = RoundedCornerShape(48.dp), 34 | color = KtCastTheme.colors.backgroundPrimaryColor, 35 | contentColor = KtCastTheme.colors.textPrimaryColor 36 | ) { 37 | Box { KtCastCircularProgressBar(modifier = Modifier.align(Alignment.Center)) } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-api/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: KtCast API Module 3 | description: Create API Module 4 | 5 | # optional 6 | optionalParams: 7 | revision: 1 8 | category: fragment 9 | formFactor: mobile 10 | constraints: 11 | - kotlin 12 | screens: 13 | - new_module 14 | minApi: 24 15 | minBuildApi: 32 16 | 17 | # required only for modules templates 18 | predefinedFeatures: 19 | - enableModuleCreationParams: 20 | defaultPackageNamePrefix: me.s097t0r1.ktcast.feature 21 | 22 | widgets: 23 | - booleanParameter: 24 | id: includeStarter 25 | name: Include starter for feature 26 | help: Generate starter interface 27 | default: false 28 | 29 | recipe: 30 | - mkDirs: 31 | - ${srcOut}: 32 | - api 33 | - instantiateAndOpen: 34 | from: root/src/main/java/package/FeatureApi.kt.ftl 35 | to: ${srcOut}/api/${__formattedModuleName}FeatureAPI.kt 36 | - instantiate: 37 | from: root/src/main/java/package/FeatureDependencies.kt.ftl 38 | to: ${srcOut}/api/${__formattedModuleName}FeatureDependencies.kt 39 | - instantiate: 40 | from: root/build.gradle.ftl 41 | to: ${rootOut}/build.gradle 42 | - instantiate: 43 | from: root/.gitignore.ftl 44 | to: ${rootOut}/.gitignore 45 | - instantiate: 46 | from: root/src/main/AndroidManifest.xml.ftl 47 | to: ${rootOut}/src/main/AndroidManifest.xml 48 | - predicate: 49 | validIf: ${includeStarter} 50 | commands: 51 | - instantiate: 52 | from: root/src/main/java/package/FeatureStarter.kt.ftl 53 | to: ${srcOut}/api/${__formattedModuleName}FeatureStarter.kt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 | # ktcast 6 | 7 | ### Open-Source Podcast App 8 | 9 | [![Android](https://img.shields.io/badge/Android-grey?logo=android&style=flat)](https://www.android.com/) 10 | [![AndroidAPI](https://img.shields.io/badge/API-24%2B-brightgreen.svg?style=flat)](https://www.android.com/) 11 | [![Kotlin](https://img.shields.io/badge/kotlin-2.0.20-blue.svg?logo=kotlin)](https://kotlinlang.org) 12 | [![JetpackCompose](https://img.shields.io/badge/Jetpack%20Compose-1.7.5-yellow)](https://developer.android.com/jetpack/compose) 13 | [![License: MIT](https://img.shields.io/badge/License-MIT-orange.svg)](./LICENSE) 14 | 15 | ---- 16 | 17 | Open-source podcast app written in Kotlin. In progress... 18 | 19 |
20 | 21 | ## Features 22 | 23 | **In progress** 24 | 25 |
26 | 27 |

28 | 29 |

30 | 31 | 32 | ## Technical Details 33 | 34 | - **Jetpack Compose** 🚀 35 | 36 | - **Multimodule Clean Architecture** 🏛 37 | 38 | ## Powered By 39 | 40 | **In progress** 41 | 42 | ## Credits 43 | 44 | **In progress** 45 | 46 | ## License 47 | 48 | ``` 49 | MIT License 50 | 51 | Copyright (c) 2022 Nikita Zmitrovich 52 | 53 | Permission is hereby granted, free of charge, to any person obtaining a copy 54 | of this software and associated documentation files (the "Software"), to deal 55 | in the Software without restriction, including without limitation the rights 56 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 57 | copies of the Software, and to permit persons to whom the Software is 58 | furnished to do so, subject to the following conditions: 59 | ``` 60 | -------------------------------------------------------------------------------- /config/geminio/modules_templates/feature-impl/recipe.yaml: -------------------------------------------------------------------------------- 1 | requiredParams: 2 | name: KtCast Implementation 3 | description: Create Implementation Module 4 | 5 | # optional 6 | optionalParams: 7 | revision: 1 8 | category: fragment 9 | formFactor: mobile 10 | constraints: 11 | - kotlin 12 | screens: 13 | - new_module 14 | minApi: 24 15 | minBuildApi: 32 16 | 17 | # required only for modules templates 18 | predefinedFeatures: 19 | - enableModuleCreationParams: 20 | defaultPackageNamePrefix: me.s097t0r1.ktcast.feature 21 | 22 | widgets: 23 | - booleanParameter: 24 | id: includeStarter 25 | name: Include starter for feature 26 | help: Generate starter interface 27 | default: false 28 | 29 | globals: 30 | - stringParameter: 31 | id: buildModuleName 32 | value: ${__formattedModuleName.classToResource()} 33 | 34 | recipe: 35 | - mkDirs: 36 | - ${srcOut}: 37 | - impl 38 | - instantiateAndOpen: 39 | from: root/src/main/java/package/di/FeatureComponent.kt.ftl 40 | to: ${srcOut}/impl/di/${__formattedModuleName}Component.kt 41 | - instantiate: 42 | from: root/src/main/java/package/di/FeatureComponentHolder.kt.ftl 43 | to: ${srcOut}/impl/di/${__formattedModuleName}ComponentHolder.kt 44 | - instantiate: 45 | from: root/src/main/java/package/di/FeatureModule.kt.ftl 46 | to: ${srcOut}/impl/di/${__formattedModuleName}Module.kt 47 | - instantiate: 48 | from: root/build.gradle.ftl 49 | to: ${rootOut}/build.gradle 50 | - instantiate: 51 | from: root/.gitignore.ftl 52 | to: ${rootOut}/.gitignore 53 | - instantiate: 54 | from: root/src/main/AndroidManifest.xml.ftl 55 | to: ${rootOut}/src/main/AndroidManifest.xml -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /common/persistence/secure-storage/android-library/src/main/java/me/s097t0r1/ktcast/common/secure_storage/storage/SecureStorage.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.common.secure_storage.storage 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import androidx.core.content.edit 6 | import androidx.security.crypto.EncryptedSharedPreferences 7 | import androidx.security.crypto.MasterKey 8 | import javax.inject.Inject 9 | 10 | 11 | interface SecureStorage { 12 | 13 | var webToken: String? 14 | 15 | fun clear() 16 | 17 | } 18 | 19 | enum class Role { 20 | ADMIN, 21 | USER, 22 | UNKNOWN 23 | } 24 | 25 | internal class AndroidSecureStorage @Inject constructor(context: Context) : SecureStorage { 26 | 27 | private val masterKey = MasterKey.Builder(context) 28 | .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) 29 | .build() 30 | 31 | private val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create( 32 | context, 33 | SHARED_PREFS_FILE_NAME, 34 | masterKey, 35 | EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, 36 | EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM 37 | ) 38 | 39 | override var webToken: String? 40 | get() = sharedPreferences.getString(WEB_TOKEN_KEY, null) 41 | set(value) = sharedPreferences.edit { 42 | putString(WEB_TOKEN_KEY, value) 43 | } 44 | 45 | override fun clear() { 46 | sharedPreferences.edit { clear() } 47 | } 48 | 49 | companion object { 50 | private const val SHARED_PREFS_FILE_NAME = "me.s097t0r1.ktcast.common.secure_storage.storage" 51 | private const val ROLE_KEY = "me.s097t0r1.ktcast.common.secure_storage.storage.ROLE" 52 | private const val WEB_TOKEN_KEY = "me.s097t0r1.ktcast.common.secure_storage.storage.WEB_TOKEN_KEY" 53 | } 54 | } -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/BaseDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base 2 | 3 | import android.app.Dialog 4 | import android.graphics.Color 5 | import android.graphics.drawable.ColorDrawable 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.annotation.LayoutRes 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.platform.ComposeView 13 | import androidx.compose.ui.platform.ViewCompositionStrategy 14 | import androidx.fragment.app.DialogFragment 15 | import me.s097t0r1.core.mvi.res.R 16 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 17 | 18 | abstract class BaseDialogFragment : DialogFragment { 19 | 20 | constructor() : super() 21 | constructor(@LayoutRes layoutRes: Int) : super(layoutRes) 22 | 23 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 24 | val dialog = super.onCreateDialog(savedInstanceState) 25 | dialog.apply { 26 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 27 | } 28 | return dialog 29 | } 30 | 31 | override fun onCreateView( 32 | inflater: LayoutInflater, 33 | container: ViewGroup?, 34 | savedInstanceState: Bundle? 35 | ): View? { 36 | return inflater.inflate(R.layout.fragment_base, container, false).apply { 37 | findViewById(R.id.cvContent).apply { 38 | setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) 39 | setContent { 40 | KtCastTheme { 41 | this@BaseDialogFragment.Content() 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | @Composable 49 | protected abstract fun Content() 50 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/message/BaseMessages.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.message 2 | 3 | import me.s097t0r1.core.navigation.command.NavigationCommand 4 | import me.s097t0r1.core.navigation.screen.FragmentScreen 5 | import me.s097t0r1.core.navigation.screen.NavigationScreen 6 | 7 | class ForwardMessage( 8 | private val screen: NavigationScreen 9 | ) : NavigationMessage() { 10 | 11 | override fun convertToCommands(): Array { 12 | return arrayOf(NavigationCommand.Forward(screen)) 13 | } 14 | } 15 | 16 | object BackMessage : NavigationMessage() { 17 | 18 | override fun convertToCommands(): Array { 19 | return arrayOf(NavigationCommand.Back) 20 | } 21 | } 22 | 23 | class ReplaceMessage( 24 | private val screen: NavigationScreen, 25 | private val clearContainer: Boolean = false 26 | ) : NavigationMessage() { 27 | 28 | override fun convertToCommands(): Array { 29 | return if (clearContainer) { 30 | arrayOf( 31 | NavigationCommand.BackTo(FragmentScreen.Root), 32 | NavigationCommand.Replace(screen) 33 | ) 34 | } else { 35 | arrayOf(NavigationCommand.Replace(screen)) 36 | } 37 | } 38 | 39 | } 40 | 41 | class StartFlowMessage( 42 | private val screen: NavigationScreen 43 | ) : NavigationMessage() { 44 | 45 | override fun convertToCommands(): Array { 46 | return arrayOf( 47 | NavigationCommand.BackTo(FragmentScreen.Root), 48 | NavigationCommand.Replace(screen) 49 | ) 50 | } 51 | } 52 | 53 | class FinishFlowMessage( 54 | private val screen: NavigationScreen 55 | ) : NavigationMessage() { 56 | 57 | override fun convertToCommands(): Array { 58 | return arrayOf( 59 | NavigationCommand.BackTo(FragmentScreen.Root) 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 33 | -------------------------------------------------------------------------------- /libraries/either/library/src/main/java/me/s097t0r1/ktcast/libraries/reaction/Either.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.either 2 | 3 | import kotlin.contracts.ExperimentalContracts 4 | import kotlin.contracts.InvocationKind 5 | import kotlin.contracts.contract 6 | 7 | sealed class Either { 8 | 9 | abstract operator fun component1(): V? 10 | abstract operator fun component2(): E? 11 | } 12 | 13 | class Ok private constructor(val value: V) : Either() { 14 | 15 | override fun component1(): V = value 16 | override fun component2(): Nothing? = null 17 | 18 | companion object { 19 | fun of(value: V) = Ok(value) 20 | } 21 | } 22 | 23 | class Err private constructor(val err: E) : Either() { 24 | override fun component1(): Nothing? = null 25 | override fun component2(): E? = err 26 | 27 | companion object { 28 | fun of(err: E) = Err(err) 29 | } 30 | } 31 | 32 | @OptIn(ExperimentalContracts::class) 33 | inline fun catchEither(block: () -> T): Either { 34 | contract { 35 | callsInPlace(block, InvocationKind.EXACTLY_ONCE) 36 | } 37 | 38 | return try { 39 | val value = block() 40 | Ok.of(value) 41 | } catch (e: Throwable) { 42 | Err.of(e) 43 | } 44 | } 45 | 46 | inline fun Either.subscribe( 47 | onSuccess: (value: T) -> Unit, 48 | onFailure: (exception: E) -> Unit 49 | ) { 50 | return when (this) { 51 | is Ok -> onSuccess(value) 52 | is Err -> onFailure(err) 53 | } 54 | } 55 | 56 | inline fun Either.fold( 57 | onSuccess: (value: T) -> R, 58 | onFailure: (exception: E) -> R 59 | ): R { 60 | return when (this) { 61 | is Ok -> onSuccess(value) 62 | is Err -> onFailure(err) 63 | } 64 | } 65 | 66 | inline fun Either.andThen( 67 | block: (T1) -> Either, 68 | ): Either { 69 | return when (this) { 70 | is Ok -> block(this.value) 71 | is Err -> Err.of(err) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2G -Xms256m -XX:+UseParallelGC 10 | org.gradle.caching=true 11 | org.gradle.daemon=true 12 | org.gradle.parallel=true 13 | org.gradle.vfs.watch=true 14 | org.gradle.unsafe.configuration-cache=true 15 | ## ================================================================================================= 16 | ## java 17 | ## ================================================================================================= 18 | compileJava.options.incremental=true 19 | compileJava.options.fork=true 20 | ## ================================================================================================= 21 | ## android 22 | ## ================================================================================================= 23 | android.useAndroidX=true 24 | android.enableJetifier=false 25 | android.defaults.buildfeatures.buildconfig=false 26 | android.defaults.buildfeatures.aidl=false 27 | android.defaults.buildfeatures.renderscript=false 28 | android.defaults.buildfeatures.resvalues=false 29 | android.defaults.buildfeatures.shaders=false 30 | android.nonTransitiveRClass=true 31 | ## ================================================================================================= 32 | ## kotlin 33 | ## ================================================================================================= 34 | # Kotlin code style for this project: "official" or "obsolete": 35 | kotlin.code.style=official 36 | kotlin.parallel.tasks.in.project=true 37 | kotlin.incremental=true 38 | kotlin.caching.enabled=true 39 | ## ================================================================================================= 40 | ## kapt 41 | ## ================================================================================================= 42 | kapt.incremental.apt=true 43 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/screen/NavigationScreen.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.screen 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.fragment.app.DialogFragment 6 | import androidx.fragment.app.Fragment 7 | import androidx.fragment.app.FragmentFactory 8 | 9 | fun interface Creator { 10 | fun create(input: I): O 11 | } 12 | 13 | sealed interface NavigationScreen 14 | 15 | open class ActivityScreen private constructor( 16 | val screenKey: String? = null, 17 | val creator: Creator 18 | ) : NavigationScreen { 19 | 20 | companion object { 21 | 22 | fun create(screenKey: String? = null, creator: Creator) = 23 | ActivityScreen(screenKey, creator) 24 | 25 | inline fun create(creator: Creator): ActivityScreen = 26 | create(T::class.simpleName, creator) 27 | } 28 | } 29 | 30 | open class FragmentScreen private constructor( 31 | val screenKey: String? = null, 32 | val creator: Creator 33 | ) : NavigationScreen { 34 | 35 | companion object { 36 | 37 | fun create(screenKey: String? = null, creator: Creator) = 38 | FragmentScreen(screenKey, creator) 39 | 40 | inline fun create(creator: Creator) = 41 | create(T::class.simpleName, creator) 42 | } 43 | 44 | object Root : FragmentScreen( 45 | screenKey = "ROOT", 46 | creator = { Fragment() } 47 | ) 48 | } 49 | 50 | open class DialogFragmentScreen private constructor( 51 | val screenKey: String? = null, 52 | val creator: Creator 53 | ) : NavigationScreen { 54 | 55 | companion object { 56 | 57 | fun create( 58 | screenKey: String? = null, 59 | creator: Creator 60 | ) = DialogFragmentScreen(screenKey, creator) 61 | 62 | inline fun create( 63 | creator: Creator 64 | ) = create(T::class.simpleName, creator) 65 | } 66 | } -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/java/me/s097t0r1/core/ui_components/theme/Typography.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.ui_components.theme 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.compose.runtime.staticCompositionLocalOf 5 | import androidx.compose.ui.text.TextStyle 6 | import androidx.compose.ui.text.font.Font 7 | import androidx.compose.ui.text.font.FontFamily 8 | import androidx.compose.ui.text.font.FontStyle 9 | import androidx.compose.ui.text.font.FontWeight 10 | import androidx.compose.ui.unit.sp 11 | 12 | @Immutable 13 | class KtCastTypography( 14 | 15 | val Heading1: TextStyle = TextStyle( 16 | fontFamily = Urbanist, 17 | fontWeight = FontWeight.Bold, 18 | fontSize = 48.sp 19 | ), 20 | val Heading2: TextStyle = Heading1.copy( 21 | fontSize = 40.sp 22 | ), 23 | val Heading3: TextStyle = Heading1.copy( 24 | fontSize = 32.sp 25 | ), 26 | val Heading4: TextStyle = Heading1.copy( 27 | fontSize = 24.sp 28 | ), 29 | val Heading5: TextStyle = Heading1.copy( 30 | fontSize = 24.sp 31 | ), 32 | val Heading6: TextStyle = Heading1.copy( 33 | fontSize = 18.sp 34 | ), 35 | 36 | val BodyXLarge: TextStyle = TextStyle( 37 | fontFamily = Urbanist, 38 | fontSize = 18.sp 39 | ), 40 | val BodyLarge: TextStyle = BodyXLarge.copy( 41 | fontSize = 16.sp 42 | ), 43 | val BodyMedium: TextStyle = BodyXLarge.copy( 44 | fontSize = 14.sp 45 | ), 46 | val BodySmall: TextStyle = BodyXLarge.copy( 47 | fontSize = 12.sp 48 | ), 49 | val BodyXSmall: TextStyle = BodyXLarge.copy( 50 | fontSize = 10.sp 51 | ), 52 | 53 | ) 54 | 55 | internal val Urbanist = FontFamily( 56 | Font(me.s097t0r1.core.ui_components.res.R.font.urbanist_regular, FontWeight.Normal, FontStyle.Normal), 57 | Font(me.s097t0r1.core.ui_components.res.R.font.urbanist_medium, FontWeight.Medium, FontStyle.Normal), 58 | Font(me.s097t0r1.core.ui_components.res.R.font.urbanist_semibold, FontWeight.SemiBold, FontStyle.Normal), 59 | Font(me.s097t0r1.core.ui_components.res.R.font.urbanist_bold, FontWeight.Bold, FontStyle.Normal) 60 | ) 61 | 62 | internal val LocalTypography = staticCompositionLocalOf { KtCastTypography() } 63 | -------------------------------------------------------------------------------- /core/ui-components/res/src/main/res/drawable/ic_ktcast_logo.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import kotlinx.coroutines.channels.BufferOverflow 6 | import kotlinx.coroutines.channels.Channel 7 | import kotlinx.coroutines.flow.MutableSharedFlow 8 | import kotlinx.coroutines.flow.asSharedFlow 9 | import kotlinx.coroutines.flow.receiveAsFlow 10 | import kotlinx.coroutines.launch 11 | import me.s097t0r1.core.exceptions.library.AppException 12 | import me.s097t0r1.core.mvi.base.host.HostSideEffect 13 | import me.s097t0r1.core.mvi.base.state.BaseSideEffect 14 | import me.s097t0r1.core.mvi.base.state.BaseState 15 | import me.s097t0r1.core.navigation.base.NavigationGraph 16 | import me.s097t0r1.core.ui_components.components.AlertSnackBarHost 17 | import me.s097t0r1.ktcast.common.logout.LogoutHandler 18 | import org.orbitmvi.orbit.ContainerHost 19 | 20 | abstract class BaseViewModel( 21 | 22 | ) : ContainerHost, ViewModel() { 23 | 24 | private val _navigation: Channel = Channel( 25 | capacity = 1, 26 | onBufferOverflow = BufferOverflow.DROP_LATEST 27 | ) 28 | val navigation = _navigation.receiveAsFlow() 29 | 30 | private val _hostSideEffect = MutableSharedFlow( 31 | replay = 0, 32 | extraBufferCapacity = 1, 33 | onBufferOverflow = BufferOverflow.DROP_LATEST 34 | ) 35 | val hostSideEffect = _hostSideEffect.asSharedFlow() 36 | 37 | fun onError(exception: AppException) { 38 | when (exception) { 39 | is AppException.NetworkException -> { 40 | when (exception) { 41 | is AppException.NetworkException.HttpException -> handleHttpException(exception) 42 | else -> {} 43 | } 44 | } 45 | is AppException.LocalException -> {} 46 | } 47 | } 48 | 49 | private fun handleHttpException(exception: AppException.NetworkException.HttpException) { 50 | when (exception.code) { 51 | AppException.NetworkException.HttpException.UNATHORIZED_CODE -> _hostSideEffect.tryEmit( 52 | HostSideEffect.Logout(LogoutHandler.LogoutType.SERVER_LOGOUT) 53 | ) 54 | else -> alert(AlertSnackBarHost.AlertType.ERROR, exception.messages.first()) 55 | } 56 | } 57 | 58 | protected fun navigateTo(screen: N) { 59 | viewModelScope.launch { _navigation.send(screen) } 60 | } 61 | 62 | protected fun alert(alertType: AlertSnackBarHost.AlertType, message: String) { 63 | _hostSideEffect.tryEmit(HostSideEffect.Alert(alertType, message)) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/authenticator/KtCastAuthenticator.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.authenticator 2 | 3 | import com.squareup.moshi.Json 4 | import javax.inject.Inject 5 | import javax.inject.Named 6 | import kotlinx.coroutines.runBlocking 7 | import me.s097t0r1.common.network.AUTHORIZATION_HEADER_NAME 8 | import me.s097t0r1.common.network.BEARER_PREFIX 9 | import me.s097t0r1.common.network.Endpoint 10 | import me.s097t0r1.common.network.di.NetworkModule 11 | import me.s097t0r1.common.network.factory.NetworkService 12 | import me.s097t0r1.common.network.factory.NetworkServiceFactory 13 | import me.s097t0r1.core.exceptions.library.AppException 14 | import me.s097t0r1.ktcast.common.secure_storage.storage.SecureStorage 15 | import me.s097t0r1.ktcast.libraries.either.Either 16 | import me.s097t0r1.ktcast.libraries.either.Ok 17 | import okhttp3.Authenticator 18 | import okhttp3.Request 19 | import okhttp3.Response 20 | import okhttp3.Route 21 | import retrofit2.http.Header 22 | import retrofit2.http.POST 23 | 24 | internal class KtCastAuthenticator @Inject constructor( 25 | @Named(NetworkModule.UNAUTHORIZED) 26 | private val apiFactory: NetworkServiceFactory, 27 | private val secureStorage: SecureStorage 28 | ) : Authenticator { 29 | 30 | private val authService = apiFactory.create(AuthService::class.java) 31 | 32 | @Synchronized 33 | override fun authenticate(route: Route?, response: Response): Request? { 34 | val request = response.request.also { response.close() } 35 | return request.refreshToken() 36 | } 37 | 38 | private fun Request.refreshToken(): Request? = runBlocking { 39 | if (secureStorage.webToken == null) return@runBlocking null 40 | val authReact = authService.tokenUpdate(secureStorage.webToken!!) 41 | when (authReact) { 42 | is Ok -> { 43 | secureStorage.webToken = authReact.value.authToken 44 | buildAuthorizedRequest(authReact.value.authToken) 45 | } 46 | else -> null 47 | } 48 | } 49 | 50 | private fun Request.buildAuthorizedRequest(newToken: String): Request { 51 | return this.newBuilder() 52 | .addHeader(AUTHORIZATION_HEADER_NAME, "$BEARER_PREFIX $newToken") 53 | .build() 54 | } 55 | 56 | private interface AuthService : NetworkService { 57 | 58 | @POST(Endpoint.USER_LOGIN) 59 | suspend fun tokenUpdate( 60 | @Header(AUTHORIZATION_HEADER_NAME) token: String 61 | ): Either 62 | 63 | } 64 | 65 | private class TokenUpdateDTO( 66 | @Json(name = "role") 67 | val role: String, 68 | @Json(name = "auth_token") 69 | val authToken: String 70 | ) 71 | 72 | } -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/BaseContainerActivity.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.activity.compose.setContent 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.appcompat.widget.Toolbar 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.viewinterop.AndroidViewBinding 11 | import androidx.viewbinding.ViewBinding 12 | import me.s097t0r1.core.navigation.dispatcher.NavigationDispatcher 13 | import me.s097t0r1.core.navigation.dispatcher.NavigationDispatcherHost 14 | import me.s097t0r1.core.navigation.message.NavigationMessage 15 | import me.s097t0r1.core.navigation.navigator.AppNavigator 16 | import me.s097t0r1.core.navigation.router.AppRouter 17 | import me.s097t0r1.core.navigation.router.Router 18 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 19 | 20 | abstract class BaseContainerActivity : AppCompatActivity(), 21 | NavigationDispatcherHost { 22 | 23 | protected lateinit var binding: V 24 | 25 | abstract val containerId: Int 26 | 27 | protected val router: Router by lazy { AppRouter() } 28 | 29 | override val dispatcher: NavigationDispatcher by lazy { 30 | object : NavigationDispatcher { 31 | override fun dispatch(navigationMessage: NavigationMessage) { 32 | this@BaseContainerActivity.accept(navigationMessage) 33 | } 34 | } 35 | } 36 | 37 | private val navigator: AppNavigator by lazy { 38 | AppNavigator(containerId, this) 39 | } 40 | 41 | protected abstract fun setupToolbar(): Toolbar 42 | 43 | abstract fun openLaunchScreen() 44 | 45 | abstract fun onCreateViewBinding( 46 | inflater: LayoutInflater, 47 | parent: ViewGroup, 48 | attachToParent: Boolean 49 | ): V 50 | 51 | open fun onViewCreated(binding: V) { 52 | setSupportActionBar(setupToolbar()) 53 | router.attachDispatcher(dispatcher) 54 | openLaunchScreen() 55 | } 56 | 57 | @Composable 58 | abstract fun Content() 59 | 60 | override fun onCreate(savedInstanceState: Bundle?) { 61 | super.onCreate(savedInstanceState) 62 | setContent { 63 | KtCastTheme { 64 | AndroidViewBinding(factory = ::onCreateViewBinding) { 65 | binding = this 66 | onViewCreated(binding) 67 | } 68 | Content() 69 | } 70 | } 71 | } 72 | 73 | override fun onDestroy() { 74 | router.dettachDispatcher() 75 | super.onDestroy() 76 | } 77 | 78 | override fun accept(navigationMessage: NavigationMessage) { 79 | navigator.execute(navigationMessage.convertToCommands()) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/BaseContainerFragment.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.activity.addCallback 6 | import androidx.annotation.LayoutRes 7 | import androidx.fragment.app.Fragment 8 | import me.s097t0r1.core.navigation.dispatcher.FragmentNavigationDispatcher 9 | import me.s097t0r1.core.navigation.dispatcher.NavigationDispatcher 10 | import me.s097t0r1.core.navigation.dispatcher.NavigationDispatcherHost 11 | import me.s097t0r1.core.navigation.message.BackMessage 12 | import me.s097t0r1.core.navigation.message.ForwardMessage 13 | import me.s097t0r1.core.navigation.message.NavigationMessage 14 | import me.s097t0r1.core.navigation.message.ReplaceMessage 15 | import me.s097t0r1.core.navigation.navigator.AppNavigator 16 | import me.s097t0r1.core.navigation.navigator.Navigator 17 | import me.s097t0r1.core.navigation.router.AppRouter 18 | import me.s097t0r1.core.navigation.router.Router 19 | import me.s097t0r1.core.navigation.router.RouterProvider 20 | 21 | abstract class BaseContainerFragment : Fragment, NavigationDispatcherHost, RouterProvider { 22 | 23 | protected abstract val containerId: Int 24 | 25 | override val dispatcher: NavigationDispatcher by lazy { 26 | 27 | object : FragmentNavigationDispatcher(this) { 28 | override fun isSupportMessage(navigationMessage: NavigationMessage): Boolean { 29 | return navigationMessage is ForwardMessage || navigationMessage is BackMessage || 30 | navigationMessage is ReplaceMessage 31 | } 32 | } 33 | } 34 | 35 | override val router: Router by lazy { AppRouter() } 36 | 37 | private val navigator: Navigator by lazy { 38 | AppNavigator(containerId, requireActivity(), childFragmentManager) 39 | } 40 | 41 | constructor() : super() 42 | constructor(@LayoutRes layoutRes: Int) : super(layoutRes) 43 | 44 | protected abstract fun openLaunchScreen() 45 | 46 | protected open fun inject() { /* no-op */ } 47 | 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | inject() 50 | super.onCreate(savedInstanceState) 51 | router.attachDispatcher(dispatcher) 52 | } 53 | 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 55 | super.onViewCreated(view, savedInstanceState) 56 | addBackHandler() 57 | openLaunchScreen() 58 | } 59 | 60 | private fun addBackHandler() { 61 | requireActivity().onBackPressedDispatcher.addCallback(this) { 62 | if (childFragmentManager.backStackEntryCount > 1) { 63 | accept(BackMessage) 64 | } else { 65 | router.navigate(BackMessage) 66 | } 67 | } 68 | } 69 | 70 | override fun onDestroy() { 71 | router.dettachDispatcher() 72 | super.onDestroy() 73 | } 74 | 75 | override fun accept(navigationMessage: NavigationMessage) { 76 | navigator.execute(navigationMessage.convertToCommands()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/me/s097t0r1/ktcast/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.appcompat.widget.Toolbar 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.remember 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import androidx.lifecycle.ViewModelProvider 13 | import me.s097t0r1.core.mvi.base.BaseContainerActivity 14 | import me.s097t0r1.core.mvi.base.host.HostViewModel 15 | import me.s097t0r1.core.mvi.base.host.HostViewModelOwner 16 | import me.s097t0r1.core.navigation.message.StartFlowMessage 17 | import me.s097t0r1.core.ui_components.components.AlertSnackBar 18 | import me.s097t0r1.core.ui_components.components.AlertSnackBarHost 19 | import me.s097t0r1.ktcast.databinding.ActivityMainBinding 20 | import me.s097t0r1.ktcast.mvi.MainSideEffect 21 | import org.orbitmvi.orbit.compose.collectSideEffect 22 | 23 | class MainActivity : BaseContainerActivity(), HostViewModelOwner { 24 | 25 | override val viewModelFactory: ViewModelProvider.Factory = MainViewModelFactory() 26 | 27 | private lateinit var viewModel: MainViewModel 28 | 29 | override val containerId: Int by lazy { binding.container.id } 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | viewModel = ViewModelProvider(this, viewModelFactory) 34 | .get(HostViewModel::class.java) as MainViewModel 35 | } 36 | 37 | override fun onViewCreated(binding: ActivityMainBinding) { 38 | super.onViewCreated(binding) 39 | setListeners() 40 | } 41 | 42 | @Composable 43 | override fun Content() { 44 | 45 | val alertSnackBarHost = remember { AlertSnackBarHost() } 46 | 47 | Column( 48 | modifier = Modifier.fillMaxSize(), 49 | verticalArrangement = Arrangement.Top 50 | ) { 51 | 52 | AlertSnackBar( 53 | alertSnackBarHost, 54 | modifier = Modifier 55 | .padding(vertical = 16.dp, horizontal = 12.dp) 56 | .fillMaxWidth() 57 | .height(64.dp) 58 | ) 59 | } 60 | 61 | CollectSideEffects(alertSnackBarHost) 62 | } 63 | 64 | @Composable 65 | private fun CollectSideEffects(alertSnackBarHost: AlertSnackBarHost) { 66 | viewModel.collectSideEffect { sideEffect -> 67 | when (sideEffect) { 68 | is MainSideEffect.Alert -> alertSnackBarHost.show( 69 | message = sideEffect.message, 70 | alertType = sideEffect.alertType, 71 | durationInMillis = 2000L 72 | ) 73 | else -> {} 74 | } 75 | } 76 | } 77 | 78 | private fun setListeners() { 79 | binding.mtToolbar.setNavigationOnClickListener { 80 | onBackPressedDispatcher.onBackPressed() 81 | } 82 | } 83 | 84 | override fun openLaunchScreen() {} 85 | 86 | override fun setupToolbar(): Toolbar = binding.mtToolbar 87 | 88 | override fun onCreateViewBinding( 89 | inflater: LayoutInflater, 90 | parent: ViewGroup, 91 | attachToParent: Boolean 92 | ): ActivityMainBinding { 93 | return ActivityMainBinding.inflate(inflater, parent, attachToParent) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.di 2 | 3 | import com.pandulapeter.beagle.logOkHttp.BeagleOkHttpLogger 4 | import com.squareup.moshi.Moshi 5 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 6 | import dagger.Module 7 | import dagger.Provides 8 | import javax.inject.Named 9 | import javax.inject.Singleton 10 | import me.s097t0r1.common.network.BASE_URL 11 | import me.s097t0r1.common.network.authenticator.KtCastAuthenticator 12 | import me.s097t0r1.common.network.call_adapter.EitherCallAdapterFactory 13 | import me.s097t0r1.common.network.factory.NetworkServiceFactory 14 | import me.s097t0r1.common.network.interceptors.ApplicationInterceptor 15 | import okhttp3.Interceptor 16 | import okhttp3.OkHttpClient 17 | import okhttp3.logging.HttpLoggingInterceptor 18 | import retrofit2.Retrofit 19 | import retrofit2.converter.moshi.MoshiConverterFactory 20 | 21 | @Module 22 | internal object NetworkModule { 23 | 24 | @Provides 25 | @Singleton 26 | @Named(UNAUTHORIZED) 27 | fun provideUnauthorizedApiFactory( 28 | @Named(UNAUTHORIZED) 29 | retrofit: Retrofit 30 | ): NetworkServiceFactory = NetworkServiceFactory.Base(retrofit) 31 | 32 | @Provides 33 | @Singleton 34 | @Named(AUTHORIZED) 35 | fun provideAuthorizedApiFactory( 36 | @Named(AUTHORIZED) 37 | retrofit: Retrofit 38 | ): NetworkServiceFactory = NetworkServiceFactory.Base(retrofit) 39 | 40 | @Provides 41 | @Singleton 42 | @Named(AUTHORIZED) 43 | fun provideAuthorizedRetrofit( 44 | moshi: Moshi, 45 | @Named(AUTHORIZED) 46 | okHttpClient: OkHttpClient 47 | ): Retrofit = Retrofit.Builder().apply { 48 | baseUrl(BASE_URL) 49 | addConverterFactory(MoshiConverterFactory.create(moshi)) 50 | addCallAdapterFactory(EitherCallAdapterFactory()) 51 | client(okHttpClient) 52 | }.build() 53 | 54 | @Provides 55 | @Singleton 56 | @Named(UNAUTHORIZED) 57 | fun provideUnathorizedRetrofit( 58 | moshi: Moshi, 59 | @Named(UNAUTHORIZED) 60 | okHttpClient: OkHttpClient 61 | ): Retrofit = Retrofit.Builder().apply { 62 | baseUrl(BASE_URL) 63 | addConverterFactory(MoshiConverterFactory.create(moshi)) 64 | addCallAdapterFactory(EitherCallAdapterFactory()) 65 | client(okHttpClient) 66 | }.build() 67 | 68 | @Provides 69 | @Singleton 70 | @Named(AUTHORIZED) 71 | fun provideAuthorizedOkHttpClient( 72 | authenticator: KtCastAuthenticator 73 | ): OkHttpClient = OkHttpClient.Builder().apply { 74 | addInterceptor(HttpLoggingInterceptor()) 75 | addInterceptor(ApplicationInterceptor()) 76 | (BeagleOkHttpLogger.logger as? Interceptor?)?.let(::addInterceptor) 77 | authenticator(authenticator) 78 | }.build() 79 | 80 | @Provides 81 | @Singleton 82 | @Named(UNAUTHORIZED) 83 | fun provideUnauthorizedOkHttpClient(): OkHttpClient = OkHttpClient.Builder().apply { 84 | addInterceptor(HttpLoggingInterceptor()) 85 | addInterceptor(ApplicationInterceptor()) 86 | (BeagleOkHttpLogger.logger as? Interceptor?)?.let(::addInterceptor) 87 | }.build() 88 | 89 | @Provides 90 | @Singleton 91 | fun provideMoshi(): Moshi = Moshi.Builder() 92 | .addLast(KotlinJsonAdapterFactory()) 93 | .build() 94 | 95 | const val UNAUTHORIZED = "UNAUTHORIZED_API_FACTORY" 96 | const val AUTHORIZED = "AUHTORIZED_API_FACTORY" 97 | 98 | } -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/java/me/s097t0r1/core/ui_components/components/AlertSnackBar.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.ui_components.components 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.expandIn 5 | import androidx.compose.animation.shrinkOut 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.Spacer 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.width 10 | import androidx.compose.foundation.shape.RoundedCornerShape 11 | import androidx.compose.material.Icon 12 | import androidx.compose.material.Surface 13 | import androidx.compose.material.Text 14 | import androidx.compose.runtime.* 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.res.painterResource 19 | import androidx.compose.ui.text.font.FontWeight 20 | import androidx.compose.ui.unit.dp 21 | import kotlinx.coroutines.delay 22 | import me.s097t0r1.core.ui_components.theme.KtCastColorPallete 23 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 24 | 25 | @Composable 26 | fun AlertSnackBar( 27 | alertSnackBarHost: AlertSnackBarHost, 28 | modifier: Modifier = Modifier 29 | ) { 30 | 31 | val currentAlertState = alertSnackBarHost.alertSnackBarState 32 | 33 | LaunchedEffect(currentAlertState) { 34 | if (currentAlertState == null) return@LaunchedEffect 35 | delay(currentAlertState.durationInMillis) 36 | alertSnackBarHost.dismiss() 37 | } 38 | 39 | AnimatedVisibility( 40 | visible = currentAlertState != null, 41 | enter = expandIn(), 42 | exit = shrinkOut() 43 | ) { 44 | 45 | if (currentAlertState == null) return@AnimatedVisibility 46 | 47 | Surface( 48 | modifier = modifier, 49 | shape = RoundedCornerShape(16.dp), 50 | color = currentAlertState.alertType.color.copy(alpha = 0.2f), 51 | contentColor = currentAlertState.alertType.color 52 | ) { 53 | Row( 54 | verticalAlignment = Alignment.CenterVertically, 55 | modifier = Modifier.padding(vertical = 4.dp, horizontal = 12.dp) 56 | ) { 57 | Icon( 58 | painter = painterResource(id = me.s097t0r1.core.ui_components.res.R.drawable.ic_info_circle), 59 | tint = currentAlertState.alertType.color, 60 | contentDescription = null 61 | ) 62 | Spacer(modifier = Modifier.width(6.dp)) 63 | Text( 64 | text = currentAlertState.message, 65 | style = KtCastTheme.typography.BodyMedium.copy(fontWeight = FontWeight.Normal) 66 | ) 67 | } 68 | } 69 | } 70 | } 71 | 72 | @Stable 73 | class AlertSnackBarHost { 74 | 75 | var alertSnackBarState: Inner? by mutableStateOf(null) 76 | 77 | fun show( 78 | message: String, 79 | alertType: AlertType, 80 | durationInMillis: Long 81 | ) { 82 | alertSnackBarState = Inner(message, alertType, durationInMillis) 83 | } 84 | 85 | fun dismiss() { 86 | alertSnackBarState = null 87 | } 88 | 89 | class Inner( 90 | val message: String, 91 | val alertType: AlertType, 92 | val durationInMillis: Long 93 | ) 94 | 95 | enum class AlertType(val color: Color) { 96 | SUCCESS(KtCastColorPallete.statusSuccessColor), 97 | INFO(KtCastColorPallete.statusInfoColor), 98 | WARNING(KtCastColorPallete.statusWarningColor), 99 | ERROR(KtCastColorPallete.statusErrorColor) 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 116 | 117 | -------------------------------------------------------------------------------- /core/ui-components/android-library/src/main/java/me/s097t0r1/core/ui_components/components/Button.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.ui_components.components 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.foundation.layout.RowScope 7 | import androidx.compose.material.* 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.remember 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Shape 12 | import androidx.compose.ui.text.font.FontWeight 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 16 | 17 | @Composable 18 | fun KtCastPrimaryButton( 19 | onClick: () -> Unit, 20 | modifier: Modifier = Modifier, 21 | enabled: Boolean = true, 22 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 23 | elevation: ButtonElevation? = ButtonDefaults.elevation(), 24 | shape: Shape = MaterialTheme.shapes.small, 25 | border: BorderStroke? = null, 26 | colors: ButtonColors = ButtonDefaults.buttonColors( 27 | backgroundColor = KtCastTheme.colors.buttonPrimaryBackgroundColor, 28 | contentColor = KtCastTheme.colors.buttonPrimaryContentColor, 29 | disabledBackgroundColor = KtCastTheme.colors.buttonDisabledBackgroundColor, 30 | disabledContentColor = KtCastTheme.colors.buttonDisabledContentColor 31 | ), 32 | contentPadding: PaddingValues = PaddingValues(vertical = 18.dp), 33 | content: @Composable RowScope.() -> Unit 34 | ) { 35 | Button( 36 | onClick = onClick, 37 | modifier = modifier, 38 | enabled = enabled, 39 | interactionSource = interactionSource, 40 | elevation = elevation, 41 | shape = shape, 42 | border = border, 43 | colors = colors, 44 | contentPadding = contentPadding, 45 | ) { 46 | ProvideTextStyle(KtCastTheme.typography.BodyLarge.copy(fontWeight = FontWeight.Bold)) { 47 | content() 48 | } 49 | } 50 | } 51 | 52 | @Composable 53 | fun KtCastSecondaryButton( 54 | onClick: () -> Unit, 55 | modifier: Modifier = Modifier, 56 | enabled: Boolean = true, 57 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 58 | elevation: ButtonElevation? = ButtonDefaults.elevation(), 59 | shape: Shape = MaterialTheme.shapes.small, 60 | border: BorderStroke? = null, 61 | colors: ButtonColors = ButtonDefaults.buttonColors( 62 | backgroundColor = KtCastTheme.colors.buttonSecondaryBackgroundColor, 63 | contentColor = KtCastTheme.colors.buttonSecondaryContentColor, 64 | disabledBackgroundColor = KtCastTheme.colors.buttonDisabledBackgroundColor, 65 | disabledContentColor = KtCastTheme.colors.buttonDisabledContentColor 66 | ), 67 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 68 | content: @Composable RowScope.() -> Unit 69 | ) { 70 | Button( 71 | onClick = onClick, 72 | modifier = modifier, 73 | enabled = enabled, 74 | interactionSource = interactionSource, 75 | elevation = elevation, 76 | shape = shape, 77 | border = border, 78 | colors = colors, 79 | contentPadding = contentPadding, 80 | ) { 81 | ProvideTextStyle( 82 | KtCastTheme.typography.BodyLarge 83 | .copy(fontWeight = FontWeight.Bold) 84 | ) { content() } 85 | } 86 | } 87 | 88 | @Preview 89 | @Composable 90 | private fun KtCastPrimaryButtonPreview() { 91 | KtCastTheme { 92 | KtCastPrimaryButton(onClick = { /*TODO*/ }) { Text("Button") } 93 | } 94 | } 95 | 96 | @Preview 97 | @Composable 98 | private fun KtCastSecndaryButtonPreview() { 99 | KtCastTheme { 100 | KtCastSecondaryButton(onClick = { /*TODO*/ }) { Text("Button") } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /common/network/android-library/src/main/java/me/s097t0r1/common/network/call_adapter/ReactionCall.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.common.network.call_adapter 2 | 3 | import java.net.ConnectException 4 | import java.net.SocketTimeoutException 5 | import java.net.UnknownHostException 6 | import me.s097t0r1.core.exceptions.library.AppException 7 | import me.s097t0r1.ktcast.common.network.utils.deserialize 8 | import me.s097t0r1.ktcast.common.network.utils.model.ErrorResponse 9 | import me.s097t0r1.ktcast.libraries.either.Either 10 | import me.s097t0r1.ktcast.libraries.either.Err 11 | import me.s097t0r1.ktcast.libraries.either.Ok 12 | import okhttp3.Request 13 | import okio.Timeout 14 | import retrofit2.Call 15 | import retrofit2.Callback 16 | import retrofit2.Response 17 | 18 | internal class EitherCall(private val delegate: Call) : Call> { 19 | 20 | override fun isExecuted(): Boolean = delegate.isExecuted 21 | 22 | override fun cancel() = delegate.cancel() 23 | 24 | override fun isCanceled(): Boolean = delegate.isCanceled 25 | 26 | override fun request(): Request = delegate.request() 27 | 28 | override fun timeout(): Timeout = delegate.timeout() 29 | 30 | override fun clone(): Call> = EitherCall(delegate) 31 | 32 | override fun execute(): Response> = 33 | error("Only async call supported") 34 | 35 | override fun enqueue(callback: Callback>) { 36 | delegate.enqueue(object : Callback { 37 | override fun onResponse(call: Call, response: Response) { 38 | if (response.isSuccessful) { 39 | handleSuccessResponse(callback, response) 40 | } else { 41 | val exception = when (response.errorBody()) { 42 | null -> AppException.NetworkException.UnknownException 43 | else -> response.toAppException() 44 | } 45 | callback.onResponse( 46 | this@EitherCall, 47 | Response.success(Err.of(exception)) 48 | ) 49 | } 50 | } 51 | 52 | override fun onFailure(call: Call, t: Throwable) { 53 | callback.onResponse( 54 | this@EitherCall, 55 | Response.success(Err.of(t.toAppException())) 56 | ) 57 | } 58 | }) 59 | } 60 | 61 | private fun handleSuccessResponse( 62 | callback: Callback>, 63 | response: Response 64 | ) { 65 | if (response.body() != null) { 66 | callback.onResponse( 67 | this@EitherCall, 68 | Response.success(Ok.of(response.body()!!)) 69 | ) 70 | } else { 71 | callback.onResponse( 72 | this@EitherCall, 73 | Response.success( 74 | Err.of(AppException.NetworkException.UnknownException) 75 | ) 76 | ) 77 | } 78 | } 79 | 80 | private fun Response<*>.toAppException(): AppException.NetworkException { 81 | return when (this.code()) { 82 | in 400..410 -> AppException.NetworkException.HttpException( 83 | code = this.code(), 84 | messages = this.errorBody() 85 | ?.string() 86 | ?.deserialize() 87 | ?.errors ?: emptyList() 88 | ) 89 | in 500..510 -> AppException.NetworkException.InternalServerException 90 | else -> AppException.NetworkException.UnknownException 91 | } 92 | } 93 | 94 | private fun Throwable.toAppException(): AppException.NetworkException { 95 | return when(this) { 96 | is UnknownHostException, is ConnectException -> 97 | AppException.NetworkException.NoInternetConnectionException 98 | 99 | is SocketTimeoutException -> AppException.NetworkException.TimeoutException 100 | 101 | else -> AppException.NetworkException.UnknownException 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 75 | 76 | -------------------------------------------------------------------------------- /libraries/utils/core/android_library/src/main/java/me/s097t0r1/ktcast/libraries/utils/core/bundle/FragmentArgumentsExtractorDelegate.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.libraries.utils.core.bundle 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.os.Parcelable 6 | import androidx.fragment.app.Fragment 7 | import java.io.Serializable 8 | import kotlin.properties.ReadWriteProperty 9 | import kotlin.reflect.KProperty 10 | 11 | inline fun args(key: String, defaultValue: T) = 12 | FragmentArgumentsExtractorDelegate(key, defaultValue, T::class.java) 13 | 14 | class FragmentArgumentsExtractorDelegate( 15 | private val key: String, 16 | private val defaultValue: T, 17 | private val valueClazz: Class 18 | ) : ReadWriteProperty { 19 | 20 | override fun getValue(thisRef: Fragment, property: KProperty<*>): T { 21 | return thisRef.arguments?.get(key) as? T ?: defaultValue 22 | } 23 | 24 | override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { 25 | if (thisRef.arguments == null) thisRef.arguments = Bundle() 26 | thisRef.requireArguments().put(key, valueClazz) 27 | } 28 | 29 | } 30 | 31 | inline fun extras(key: String, defaultValue: T) = 32 | ActivityExtrasExtractorDelegate(key, defaultValue, T::class.java) 33 | 34 | class ActivityExtrasExtractorDelegate( 35 | private val key: String, 36 | private val defaultValue: T, 37 | private val valueClazz: Class 38 | ) : ReadWriteProperty { 39 | 40 | override fun getValue(thisRef: Activity, property: KProperty<*>): T { 41 | return thisRef.intent.extras?.get(key) as? T ?: defaultValue 42 | } 43 | 44 | override fun setValue(thisRef: Activity, property: KProperty<*>, value: T) { 45 | thisRef.intent.putExtras(Bundle().apply { put(key, valueClazz) }) 46 | } 47 | 48 | } 49 | 50 | private inline fun Bundle.put(key: String, value: T) { 51 | when (value) { 52 | null -> putString(key, null) // Any nullable type will suffice. 53 | 54 | // Scalars 55 | is Boolean -> putBoolean(key, value) 56 | is Byte -> putByte(key, value) 57 | is Char -> putChar(key, value) 58 | is Double -> putDouble(key, value) 59 | is Float -> putFloat(key, value) 60 | is Int -> putInt(key, value) 61 | is Long -> putLong(key, value) 62 | is Short -> putShort(key, value) 63 | 64 | // References 65 | is Bundle -> putBundle(key, value) 66 | is CharSequence -> putCharSequence(key, value) 67 | is Parcelable -> putParcelable(key, value) 68 | 69 | // Scalar arrays 70 | is BooleanArray -> putBooleanArray(key, value) 71 | is ByteArray -> putByteArray(key, value) 72 | is CharArray -> putCharArray(key, value) 73 | is DoubleArray -> putDoubleArray(key, value) 74 | is FloatArray -> putFloatArray(key, value) 75 | is IntArray -> putIntArray(key, value) 76 | is LongArray -> putLongArray(key, value) 77 | is ShortArray -> putShortArray(key, value) 78 | 79 | // Reference arrays 80 | is Array<*> -> { 81 | val componentType = value!!::class.java.componentType 82 | @Suppress("UNCHECKED_CAST") // Checked by reflection. 83 | when { 84 | Parcelable::class.java.isAssignableFrom(componentType) -> { 85 | putParcelableArray(key, value as Array) 86 | } 87 | String::class.java.isAssignableFrom(componentType) -> { 88 | putStringArray(key, value as Array) 89 | } 90 | CharSequence::class.java.isAssignableFrom(componentType) -> { 91 | putCharSequenceArray(key, value as Array) 92 | } 93 | Serializable::class.java.isAssignableFrom(componentType) -> { 94 | putSerializable(key, value) 95 | } 96 | else -> { 97 | val valueType = componentType.canonicalName 98 | throw IllegalArgumentException( 99 | "Illegal value array type $valueType for key \"$key\"" 100 | ) 101 | } 102 | } 103 | } 104 | 105 | // Last resort. Also we must check this after Array<*> as all arrays are serializable. 106 | is Serializable -> putSerializable(key, value) 107 | } 108 | } -------------------------------------------------------------------------------- /core/debug-helper/android-library/src/main/java/me/s097t0r1/ktcast/core/debug_helper/KtCastDebugHelper.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.ktcast.core.debug_helper 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.pandulapeter.beagle.Beagle 6 | import com.pandulapeter.beagle.common.configuration.Behavior 7 | import com.pandulapeter.beagle.log.BeagleLogger 8 | import com.pandulapeter.beagle.logOkHttp.BeagleOkHttpLogger 9 | import com.pandulapeter.beagle.modules.AnimationDurationSwitchModule 10 | import com.pandulapeter.beagle.modules.AppInfoButtonModule 11 | import com.pandulapeter.beagle.modules.BugReportButtonModule 12 | import com.pandulapeter.beagle.modules.DeveloperOptionsButtonModule 13 | import com.pandulapeter.beagle.modules.DeviceInfoModule 14 | import com.pandulapeter.beagle.modules.DividerModule 15 | import com.pandulapeter.beagle.modules.HeaderModule 16 | import com.pandulapeter.beagle.modules.KeylineOverlaySwitchModule 17 | import com.pandulapeter.beagle.modules.LifecycleLogListModule 18 | import com.pandulapeter.beagle.modules.LogListModule 19 | import com.pandulapeter.beagle.modules.NetworkLogListModule 20 | import com.pandulapeter.beagle.modules.PaddingModule 21 | import com.pandulapeter.beagle.modules.ScreenCaptureToolboxModule 22 | import com.pandulapeter.beagle.modules.TextModule 23 | import me.s097t0r1.ktcast.core.debug_helper.modules.stand_module.NetworkStandModule 24 | import timber.log.Timber 25 | 26 | object KtCastDebugHelper { 27 | 28 | lateinit var storage: KtCastDebugStorage 29 | 30 | fun initialize(application: Application) { 31 | Beagle.initialize( 32 | application = application, 33 | behavior = Behavior( 34 | networkLogBehavior = Behavior.NetworkLogBehavior( 35 | networkLoggers = listOf(BeagleOkHttpLogger), 36 | ), 37 | logBehavior = Behavior.LogBehavior( 38 | loggers = listOf(BeagleLogger) 39 | ), 40 | ) 41 | ) 42 | initStorage(application) 43 | initModules() 44 | } 45 | 46 | private fun initStorage(context: Context) { 47 | storage = KtCastDebugStorage(context) 48 | } 49 | 50 | private fun initModules() { 51 | Beagle.set( 52 | HeaderModule( 53 | title = "KtCast", 54 | text = BuildConfig.BUILD_TYPE 55 | ), 56 | AppInfoButtonModule(), 57 | DeveloperOptionsButtonModule(), 58 | PaddingModule(), 59 | TextModule("General", TextModule.Type.SECTION_HEADER), 60 | KeylineOverlaySwitchModule(), 61 | AnimationDurationSwitchModule(), 62 | ScreenCaptureToolboxModule(), 63 | NetworkStandModule(), 64 | DividerModule(), 65 | TextModule("Logs", TextModule.Type.SECTION_HEADER), 66 | NetworkLogListModule(), // Might require additional setup, see below 67 | LogListModule(), // Might require additional setup, see below 68 | LifecycleLogListModule(), 69 | DividerModule(), 70 | TextModule("Other", TextModule.Type.SECTION_HEADER), 71 | DeviceInfoModule(), 72 | BugReportButtonModule() 73 | ) 74 | } 75 | 76 | object Logger { 77 | 78 | fun init() { 79 | Timber.plant(Timber.DebugTree()) 80 | Timber.plant( 81 | object : Timber.Tree() { 82 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 83 | Beagle.log("[$tag] $message", "Timber", t?.stackTraceToString()) 84 | } 85 | } 86 | ) 87 | } 88 | 89 | fun v(message: String) = Timber.v(message) 90 | fun v(t: Throwable, message: String) = Timber.v(t, message) 91 | fun v(t: Throwable) = Timber.v(t) 92 | 93 | fun d(message: String) = Timber.d(message) 94 | fun d(t: Throwable, message: String) = Timber.d(t, message) 95 | fun d(t: Throwable) = Timber.d(t) 96 | 97 | fun i(message: String) = Timber.i(message) 98 | fun i(t: Throwable, message: String) = Timber.i(t, message) 99 | fun i(t: Throwable) = Timber.i(t) 100 | 101 | fun w(message: String) = Timber.w(message) 102 | fun w(t: Throwable, message: String) = Timber.w(t, message) 103 | fun w(t: Throwable) = Timber.w(t) 104 | 105 | fun e(message: String) = Timber.e(message) 106 | fun e(t: Throwable, message: String) = Timber.e(t, message) 107 | fun e(t: Throwable) = Timber.e(t) 108 | 109 | fun wtf(message: String) = Timber.wtf(message) 110 | fun wtf(t: Throwable, message: String) = Timber.wtf(t, message) 111 | fun wtf(t: Throwable) = Timber.wtf(t) 112 | 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /core/mvi/android-library/src/main/java/me/s097t0r1/core/mvi/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.mvi.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.annotation.LayoutRes 8 | import androidx.appcompat.app.ActionBar 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.compose.material.Surface 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.platform.ComposeView 13 | import androidx.compose.ui.platform.ViewCompositionStrategy 14 | import androidx.fragment.app.Fragment 15 | import androidx.fragment.app.activityViewModels 16 | import androidx.lifecycle.lifecycleScope 17 | import kotlinx.coroutines.flow.collectLatest 18 | import me.s097t0r1.core.mvi.base.host.HostSideEffect 19 | import me.s097t0r1.core.mvi.base.host.HostViewModel 20 | import me.s097t0r1.core.mvi.base.host.HostViewModelOwner 21 | import me.s097t0r1.core.mvi.base.state.BaseSideEffect 22 | import me.s097t0r1.core.mvi.base.state.BaseState 23 | import me.s097t0r1.core.mvi.res.R 24 | import me.s097t0r1.core.navigation.base.NavigationGraph 25 | import me.s097t0r1.core.navigation.base.NavigationProvider 26 | import me.s097t0r1.core.navigation.router.RouterProvider 27 | import me.s097t0r1.core.ui_components.theme.KtCastTheme 28 | 29 | abstract class BaseFragment, S : BaseState, E : BaseSideEffect, N : NavigationGraph> : Fragment { 30 | 31 | constructor() : super() 32 | constructor(@LayoutRes layoutRes: Int) : super(layoutRes) 33 | 34 | protected abstract val viewModel: VM 35 | protected abstract val navigationProvider: NavigationProvider 36 | 37 | private val hostViewModel: HostViewModel by activityViewModels( 38 | factoryProducer = { (requireActivity() as HostViewModelOwner).viewModelFactory } 39 | ) 40 | 41 | private val router by lazy { (parentFragment as RouterProvider).router } 42 | 43 | protected abstract fun onInjectDaggerComponent() 44 | 45 | @Composable 46 | protected abstract fun Content() 47 | 48 | open fun onInitViewModel(viewModel: VM) { /* no-op */ } 49 | 50 | override fun onCreate(savedInstanceState: Bundle?) { 51 | onInjectDaggerComponent() 52 | super.onCreate(savedInstanceState) 53 | onInitViewModel(viewModel) 54 | } 55 | 56 | override fun onCreateView( 57 | inflater: LayoutInflater, 58 | container: ViewGroup?, 59 | savedInstanceState: Bundle? 60 | ): View { 61 | onSetupToolbar() 62 | return inflater.inflate(R.layout.fragment_base, container, false).apply { 63 | findViewById(R.id.cvContent).apply { 64 | setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) 65 | setContent { 66 | KtCastTheme { 67 | Surface(color = KtCastTheme.colors.backgroundPrimaryColor) { 68 | this@BaseFragment.Content() 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | private fun onSetupToolbar() { 77 | (requireActivity() as? AppCompatActivity)?.supportActionBar?.let { setupToolbar(it) } 78 | } 79 | 80 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 81 | super.onViewCreated(view, savedInstanceState) 82 | initViewLifecycleObservers() 83 | } 84 | 85 | protected open fun setupToolbar(actionBar: ActionBar) { 86 | if (isBackAvailable()) { 87 | actionBar.show() 88 | actionBar.setDisplayHomeAsUpEnabled(true) 89 | actionBar.setHomeAsUpIndicator( 90 | me.s097t0r1.core.ui_components.res.R.drawable.ic_toolbar_back 91 | ) 92 | actionBar.setDisplayShowTitleEnabled(false) 93 | } else { 94 | actionBar.hide() 95 | actionBar.setDisplayHomeAsUpEnabled(false) 96 | actionBar.setHomeAsUpIndicator(null) 97 | } 98 | } 99 | 100 | protected open fun isBackAvailable(): Boolean { 101 | return parentFragmentManager.backStackEntryCount > 1 102 | } 103 | 104 | private fun initViewLifecycleObservers() { 105 | viewLifecycleOwner.lifecycleScope.launchWhenStarted { initNavigationObserver() } 106 | viewLifecycleOwner.lifecycleScope.launchWhenStarted { initSideEffectObserver() } 107 | } 108 | 109 | private suspend fun initNavigationObserver() { 110 | viewModel.navigation.collectLatest { navigationProvider.navigate(router, it) } 111 | } 112 | 113 | private suspend fun initSideEffectObserver() { 114 | viewModel.hostSideEffect.collect { 115 | when (it) { 116 | is HostSideEffect.Alert -> hostViewModel.alert(it.alertType, it.message) 117 | is HostSideEffect.Logout -> hostViewModel.logout(it.logoutType) 118 | } 119 | } 120 | } 121 | 122 | 123 | } 124 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | compileSdk = "34" 3 | targetSdk = "33" 4 | minSdk = "24" 5 | 6 | androidGradlePlugin = "8.7.2" 7 | kotlinGradlePlugin = "2.0.20" 8 | detektGradlePlugin = "1.23.7" 9 | 10 | kotlinComposeCompiler = "2.0.20" 11 | 12 | # Code generation 13 | ksp = "2.0.20-1.0.25" 14 | 15 | androidDesugaring = "2.1.2" 16 | 17 | androidxCore = "1.13.1" 18 | androidxAppCompat = "1.7.0" 19 | androidxLifecycle = "2.8.7" 20 | androidxFragment = "1.8.5" 21 | androidxSecurity = "1.1.0-alpha06" 22 | androidxComposeActivity = "1.9.3" 23 | androidxCompose = "1.7.5" 24 | androidxRoom = "2.6.1" 25 | 26 | googleMaterial = "1.12.0" 27 | 28 | retrofit = "2.9.0" 29 | okHttp = "4.10.0" 30 | moshi = "1.14.0" 31 | 32 | coroutinesAndroid = "1.7.1" 33 | 34 | dagger2 = "2.52" 35 | 36 | orbit = "4.3.2" 37 | 38 | beagle = "2.8.3" 39 | timber = "5.0.1" 40 | 41 | detektComposeRules = "1.2.2" 42 | jetbrainsKotlinJvm = "1.9.24" 43 | 44 | [libraries] 45 | 46 | android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" } 47 | android-gradle-plugin-api = { module = "com.android.tools.build:gradle-api", version.ref = "androidGradlePlugin" } 48 | android-desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "androidDesugaring" } 49 | 50 | kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" } 51 | 52 | androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCore" } 53 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppCompat" } 54 | androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidxFragment" } 55 | androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" } 56 | androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" } 57 | androidx-security = { module = "androidx.security:security-crypto", version.ref = "androidxSecurity"} 58 | 59 | androidx-compose-activity = { module = "androidx.activity:activity-compose", version.ref = "androidxComposeActivity"} 60 | androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "androidxCompose" } 61 | androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "androidxCompose" } 62 | androidx-compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "androidxCompose" } 63 | androidx-compose-viewbinding = { module = "androidx.compose.ui:ui-viewbinding", version.ref = "androidxCompose"} 64 | 65 | androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidxRoom"} 66 | androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidxRoom" } 67 | androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidxRoom" } 68 | 69 | arturbosch-detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detektGradlePlugin" } 70 | 71 | google-material = { module = "com.google.android.material:material", version.ref = "googleMaterial" } 72 | google-dagger = { module = "com.google.dagger:dagger", version.ref = "dagger2" } 73 | google-dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger2" } 74 | google-ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } 75 | 76 | pandulapetor-beagle = { module = "io.github.pandulapeter.beagle:ui-drawer", version.ref = "beagle" } 77 | pandulapetor-beagle-noop = { module = "io.github.pandulapeter.beagle:noop", version.ref = "beagle" } 78 | 79 | pandulapetor-beagle-okhttp = { module = "io.github.pandulapeter.beagle:log-okhttp", version.ref = "beagle" } 80 | pandulapetor-beagle-okhttp-noop = { module = "io.github.pandulapeter.beagle:log-okhttp-noop", version.ref = "beagle" } 81 | 82 | pandulapetor-beagle-logger = { module = "io.github.pandulapeter.beagle:log", version.ref = "beagle" } 83 | pandulapetor-beagle-logger-noop = { module = "io.github.pandulapeter.beagle:log-noop", version.ref = "beagle" } 84 | 85 | squareup-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } 86 | squareup-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } 87 | squareup-okhttp-logger = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okHttp" } 88 | squareup-moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } 89 | squareup-moshi-converter = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" } 90 | 91 | jetbrains-kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinGradlePlugin"} 92 | jetbrains-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutinesAndroid"} 93 | 94 | jakewharton-timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } 95 | 96 | orbit-mvi-core = { module = "org.orbit-mvi:orbit-core", version.ref = "orbit"} 97 | orbit-mvi-viewmodel = { module = "org.orbit-mvi:orbit-viewmodel", version.ref = "orbit" } 98 | orbit-mvi-compose = { module = "org.orbit-mvi:orbit-compose", version.ref = "orbit" } 99 | 100 | kode-detekt-rules-compose = { module = "ru.kode:detekt-rules-compose", version.ref = "detektComposeRules" } 101 | 102 | [bundles] 103 | 104 | androidx-compose = [ 105 | "androidx-compose-material", "androidx-compose-animation", 106 | "androidx-compose-tooling", "androidx-compose-activity" 107 | ] 108 | 109 | orbit-mvi = ["orbit-mvi-core", "orbit-mvi-viewmodel", "orbit-mvi-compose"] 110 | 111 | [plugins] 112 | 113 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detektGradlePlugin" } 114 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlinGradlePlugin" } 115 | jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" } 116 | -------------------------------------------------------------------------------- /core/navigation/android-library/src/main/java/me/s097t0r1/core/navigation/navigator/AppNavigator.kt: -------------------------------------------------------------------------------- 1 | package me.s097t0r1.core.navigation.navigator 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentActivity 5 | import androidx.fragment.app.FragmentManager 6 | import androidx.fragment.app.FragmentTransaction 7 | import androidx.fragment.app.commit 8 | import me.s097t0r1.core.navigation.R 9 | import me.s097t0r1.core.navigation.command.NavigationCommand 10 | import me.s097t0r1.core.navigation.screen.ActivityScreen 11 | import me.s097t0r1.core.navigation.screen.DialogFragmentScreen 12 | import me.s097t0r1.core.navigation.screen.FragmentScreen 13 | import me.s097t0r1.core.navigation.screen.NavigationScreen 14 | 15 | class AppNavigator( 16 | private val containerId: Int, 17 | private val fragmentActivity: FragmentActivity, 18 | private val fragmentManager: FragmentManager = fragmentActivity.supportFragmentManager 19 | ) : Navigator { 20 | 21 | private var transactions = listOf>() 22 | 23 | override fun execute(commands: Array) { 24 | for (command in commands) { 25 | when (command) { 26 | is NavigationCommand.Back -> back() 27 | is NavigationCommand.BackTo -> backTo(command.screen) 28 | is NavigationCommand.Replace -> replace(command.screen) 29 | is NavigationCommand.Forward -> forward(command.screen) 30 | } 31 | } 32 | } 33 | 34 | private fun forward(screen: NavigationScreen) { 35 | when (screen) { 36 | is ActivityScreen -> screen.creator.create(fragmentActivity) 37 | is FragmentScreen<*> -> fragmentManager.commit { 38 | val nextFragment = screen.creator.create(fragmentManager.fragmentFactory) 39 | setupFragmentTransaction( 40 | screen, 41 | this, 42 | nextFragment, 43 | fragmentManager.findFragmentById(containerId) 44 | ) 45 | val transaction = '+' to screen.screenKey.orEmpty() 46 | transactions += transaction 47 | addToBackStack(transaction.hashCode().toString()) 48 | replace( 49 | containerId, 50 | nextFragment, 51 | screen.screenKey 52 | ) 53 | } 54 | is DialogFragmentScreen<*> -> { 55 | screen.creator.create(fragmentManager.fragmentFactory) 56 | .show(fragmentManager, screen.screenKey) 57 | } 58 | } 59 | } 60 | 61 | private fun replace(screen: NavigationScreen) { 62 | when (screen) { 63 | is ActivityScreen -> { 64 | val context = fragmentActivity.applicationContext 65 | fragmentActivity.finish() 66 | screen.creator.create(context) 67 | } 68 | is FragmentScreen<*> -> { 69 | fragmentManager.commit { 70 | val nextFragment = screen.creator.create(fragmentManager.fragmentFactory) 71 | 72 | if (transactions.isNotEmpty()) { 73 | transactions = transactions.subList(0, transactions.lastIndex) 74 | } 75 | 76 | transactions += '+' to screen.screenKey.toString() 77 | 78 | setupFragmentTransaction( 79 | screen, 80 | this, 81 | nextFragment, 82 | null 83 | ) 84 | replace(containerId, nextFragment, screen.screenKey.toString()) 85 | addToBackStack(transactions.last().hashCode().toString()) 86 | } 87 | } 88 | else -> error("DialogFragment doesn't support 'Replace' command") 89 | } 90 | } 91 | 92 | open fun setupFragmentTransaction( 93 | fragmentScreen: FragmentScreen, 94 | fragmentTransaction: FragmentTransaction, 95 | nextFragment: Fragment, 96 | previousFragment: Fragment? 97 | ) { 98 | fragmentTransaction.setReorderingAllowed(true) 99 | fragmentTransaction.setCustomAnimations( 100 | R.animator.slide_in, 101 | R.animator.slide_out, 102 | R.animator.slide_in, 103 | R.animator.slide_out 104 | ) 105 | } 106 | 107 | private fun backTo(screen: NavigationScreen) = when (screen) { 108 | is ActivityScreen -> throw IllegalArgumentException("Activity doesn't support 'BackTo' command") 109 | is FragmentScreen<*> -> { 110 | if (screen != FragmentScreen.Root) { 111 | val indexOfScreen = transactions.indexOfLast { it.second == screen.screenKey } 112 | fragmentManager.popBackStack(transactions[indexOfScreen].hashCode().toString(), 0) 113 | transactions = transactions.subList(0, indexOfScreen) 114 | } else { 115 | fragmentManager.popBackStack( 116 | transactions.firstOrNull().hashCode().toString(), 117 | FragmentManager.POP_BACK_STACK_INCLUSIVE 118 | ) 119 | transactions = emptyList() 120 | } 121 | } 122 | else -> error("DialogFragment doesn't support 'Back' command") 123 | } 124 | 125 | private fun back() { 126 | if (fragmentManager.backStackEntryCount <= 1) { 127 | transactions = emptyList() 128 | fragmentActivity.finish() 129 | } else { 130 | transactions = if (transactions.size > 1) { 131 | transactions.subList(0, transactions.lastIndex) 132 | } else { 133 | emptyList() 134 | } 135 | fragmentManager.popBackStack( 136 | transactions.lastOrNull().hashCode().toString(), 137 | 0 138 | ) 139 | } 140 | } 141 | 142 | } 143 | --------------------------------------------------------------------------------