diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 1d3937d..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,178 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' -apply plugin: 'com.google.firebase.crashlytics' -apply plugin: 'com.google.gms.google-services' - -def customEndpoint = System.getenv("CUSTOM_ENDPOINT") - -android { - compileSdkVersion versions.compileSdk - - defaultConfig { - applicationId "com.touchin.template" - minSdkVersion 21 - targetSdkVersion versions.compileSdk - versionCode System.getenv("BUILD_NUMBER") as Integer ?: 10000 - versionName "1.0." + versionCode - - rootProject.extensions.pathToApiSchemes = "$rootDir/Template-common/api" - rootProject.extensions.applicationId = "com.touchin.template" - - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - - firebaseCrashlytics { - mappingFileUploadEnabled true - } - - signingConfigs { - release { - storeFile file("keystore/touchin.jks") - storePassword "iphoneandroidwp7" - keyAlias "touchin" - keyPassword "iphoneandroidwp7" - } - } - - buildTypes { - debug { - versionNameSuffix ".debug" - minifyEnabled false - shrinkResources false - ext.enableCrashlytics = false - signingConfig signingConfigs.release - } - release { - minifyEnabled true - shrinkResources true - ext.enableCrashlytics = true - signingConfig signingConfigs.release - } - } - - flavorDimensions "proguardSettings", "apiEndpoint", "sslPinning", "testPanel" - - /* - Use that guide for adding new server env. flavours https://github.com/TouchInstinct/Styleguide/blob/master/general/setupBuildGuide.md - */ - productFlavors { - noObfuscate { - dimension "proguardSettings" - proguardFiles getDefaultProguardFile('proguard-android.txt'), "$buildScriptsDir/proguard/noObfuscate.pro" - } - obfuscate { - dimension "proguardSettings" - proguardFiles getDefaultProguardFile('proguard-android.txt'), "$buildScriptsDir/proguard/obfuscate.pro" - } - touchinTest { - def endpoint = customEndpoint ?: 'https://template-server.test.touchin.ru' - dimension "apiEndpoint" - buildConfigField "String", "API_URL", """\"${endpoint}/\"""" - } - customerProd { - def endpoint = customEndpoint ?: 'https://template-server.prod.customer.ru' - dimension "apiEndpoint" - buildConfigField "String", "API_URL", """\"${endpoint}/\"""" - } - withSSLPinning { - dimension "sslPinning" - buildConfigField "Boolean", "PIN_SSL", 'true' - } - withoutSSLPinning { - dimension "sslPinning" - buildConfigField "Boolean", "PIN_SSL", 'false' - } - withTestPanel { - dimension "testPanel" - } - withoutTestPanel { - dimension "testPanel" - } - } - - extensions.languageMap = ["ru": "Template-common/strings/default_common_strings_ru.json"] - -} - -androidExtensions { - experimental = true -} - -dependencies { - - // RoboSwag - gradle.ext.roboswag.forEach { module -> - implementation project(":$module") - } - - // Kotlin - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - // AndroidX - implementation "androidx.appcompat:appcompat:$versions.appcompat" - implementation "androidx.recyclerview:recyclerview:$versions.androidx" - implementation "androidx.cardview:cardview:$versions.androidx" - implementation "androidx.gridlayout:gridlayout:$versions.androidx" - implementation "androidx.core:core-ktx:$versions.androidxKtx" - implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" - implementation "com.google.android.material:material:$versions.material" - - // Lifecycle - implementation "androidx.lifecycle:lifecycle-extensions:$versions.lifecycle" - implementation "androidx.lifecycle:lifecycle-reactivestreams:$versions.lifecycle" - - // Room - implementation "androidx.room:room-runtime:$versions.room" - implementation "androidx.room:room-rxjava2:$versions.room" - kapt "androidx.room:room-compiler:$versions.room" - - // Retrofit - implementation "com.squareup.retrofit2:retrofit:$versions.retrofit" - implementation "com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit" - implementation "com.squareup.okhttp3:logging-interceptor:$versions.okhttp3" - implementation "com.squareup.okhttp3:okhttp-urlconnection:$versions.okhttp3" - - // Logan square - implementation "ru.touchin:logansquare:$versions.logansquare" - kapt "ru.touchin:logansquare-compiler:$versions.logansquare" - - // Dagger - implementation "com.google.dagger:dagger:$versions.dagger" - kapt "com.google.dagger:dagger-compiler:$versions.dagger" - - // RxJava - implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava" - implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid" - - // Glide - implementation "com.github.bumptech.glide:glide:$versions.glide" - implementation "com.github.bumptech.glide:okhttp3-integration:$versions.glide" - kapt "com.github.bumptech.glide:compiler:$versions.glide" - - // Chucker - withTestPanelImplementation "com.github.ChuckerTeam.Chucker:library:$versions.chucker" - withoutTestPanelImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$versions.chucker" - - // LeakCanary - withTestPanelImplementation "com.squareup.leakcanary:leakcanary-android:$versions.leakcanary" - -} -//TODO: uncomment, when common repo become plugged - -//gradle.projectsEvaluated { -// preBuild.dependsOn('stringGenerator') -//} - -//apply from: "$buildScriptsDir/gradle/stringGenerator.gradle" -apply from: "$buildScriptsDir/gradle/apiGenerator.gradle" -apply from: "$buildScriptsDir/gradle/applicationFileNaming.gradle" \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..9dd2dd8 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,97 @@ +plugins { + id(Plugins.ANDROID_APP_PLUGIN_WITH_DEFAULT_CONFIG) + id(Plugins.FIREBASE_CRASHLYTICS) +} + +android { + signingConfigs { + addConfig(SigningConfig.Test) + addConfig(SigningConfig.Prod) + } + + defaultConfig { + applicationId = AndroidConfig.TEST_APP_ID + signingConfig = signingConfigs.getByName(SigningConfig.Test.name) + } + + firebaseCrashlytics { + mappingFileUploadEnabled = true + } + + buildTypes { + addBuildType(BuildType.Debug) + addBuildType(BuildType.Release) + } + + flavorDimensions( + ProguardFlavour.DIMENSION_NAME, + ApiFlavour.DIMENSION_NAME, + SSLPinningFlavour.DIMENSION_NAME, + TestPanelFlavour.DIMENSION_NAME + ) + + productFlavors { + create(ProguardFlavour.NO_OBFUSCATE) { + dimension = ProguardFlavour.DIMENSION_NAME + setProguardFiles(listOf( + getDefaultProguardFile("proguard-android.txt"), + "$rootProject.projectDir/BuildScripts/proguard/noObfuscate.pro" + )) + } + + create(ProguardFlavour.OBFUSCATE) { + dimension = ProguardFlavour.DIMENSION_NAME + setProguardFiles(listOf( + getDefaultProguardFile("proguard-android.txt"), + "$rootProject.projectDir/BuildScripts/proguard/obfuscate.pro" + )) + } + + addEmptyFlavour(ApiFlavour.MockDev) + addEmptyFlavour(ApiFlavour.TouchinTest) + addEmptyFlavour(ApiFlavour.CustomerProd) + + addEmptyFlavour(SSLPinningFlavour.OFF) + addEmptyFlavour(SSLPinningFlavour.ON) + + addEmptyFlavour(TestPanelFlavour.OFF) + addEmptyFlavour(TestPanelFlavour.ON) + } + + extensions.add("languageMap", mapOf("ru" to "Template-common/strings/default_common_strings_ru.json")) + + variantFilter = Action { + if (name.contentEquals(AndroidConfig.PROD_BUILD_NAME)) { + (defaultConfig as com.android.build.gradle.internal.dsl.BaseFlavor).apply { + applicationId = AndroidConfig.PROD_APP_ID + signingConfig = signingConfigs.getByName(SigningConfig.Prod.name) + } + } + } + +} + +androidExtensions { + features = setOf("parcelize") +} + +dependencies { + androidX() + featureModules() + mvi() + materialDesign() + dagger() + retrofit() + moshi() + navigation() + coreNetwork() + leakCanary() + sharedPrefs() +} + +//gradle.projectsEvaluated { +// preBuild.dependsOn('stringGenerator') +//} + +//apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/stringGenerator.gradle") +apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/applicationFileNaming.gradle") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 37ca934..54278fa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,12 +1,12 @@ + package="ru.touchin.template"> diff --git a/app/src/main/java/ru/touchin/template/App.kt b/app/src/main/java/ru/touchin/template/App.kt new file mode 100644 index 0000000..af73a8b --- /dev/null +++ b/app/src/main/java/ru/touchin/template/App.kt @@ -0,0 +1,27 @@ +package ru.touchin.template + +import me.vponomarenko.injectionmanager.IHasComponent +import me.vponomarenko.injectionmanager.x.XInjectionManager +import ru.touchin.roboswag.navigation_base.TouchinApp +import ru.touchin.template.core_prefs.PreferencesModule +import ru.touchin.template.di.ApplicationComponent +import ru.touchin.template.di.DaggerApplicationComponent + +class App : TouchinApp(), IHasComponent { + + override fun onCreate() { + super.onCreate() + initDagger() + } + + fun initDagger() { + XInjectionManager.init(this) + XInjectionManager.bindComponent(this) + } + + override fun getComponent(): ApplicationComponent = DaggerApplicationComponent + .builder() + .preferencesModule(PreferencesModule(this)) + .build() + +} diff --git a/app/src/main/java/ru/touchin/template/SingleActivity.kt b/app/src/main/java/ru/touchin/template/SingleActivity.kt new file mode 100644 index 0000000..d2da4c5 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/SingleActivity.kt @@ -0,0 +1,46 @@ +package ru.touchin.template + +import android.os.Bundle +import me.vponomarenko.injectionmanager.x.XInjectionManager +import ru.terrakok.cicerone.NavigatorHolder +import ru.touchin.roboswag.navigation_base.activities.BaseActivity +import ru.touchin.roboswag.navigation_cicerone.CiceroneTuner +import ru.touchin.template.di.ApplicationComponent +import ru.touchin.template.navigation.MainNavigation +import ru.touchin.template.navigation.StartUpNavigation +import javax.inject.Inject + +class SingleActivity : BaseActivity() { + + @Inject + @MainNavigation + lateinit var navigatorHolder: NavigatorHolder + + @Inject + lateinit var navigation: StartUpNavigation + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_main) + + injectDependencies() + + lifecycle.addObserver( + CiceroneTuner( + activity = this, + navigatorHolder = navigatorHolder, + fragmentContainerId = R.id.fragment_container + ) + ) + + navigation.start() + } + + private fun injectDependencies() { + XInjectionManager + .findComponent() + .inject(this) + } + +} diff --git a/app/src/main/java/ru/touchin/template/TemplateApplication.kt b/app/src/main/java/ru/touchin/template/TemplateApplication.kt deleted file mode 100644 index 67ee59b..0000000 --- a/app/src/main/java/ru/touchin/template/TemplateApplication.kt +++ /dev/null @@ -1,43 +0,0 @@ -package ru.touchin.template - -import androidx.appcompat.app.AppCompatDelegate -import com.bluelinelabs.logansquare.LoganSquare -import org.joda.time.DateTime -import org.joda.time.format.DateTimeFormat -import ru.touchin.lifecycle.viewmodel.ViewModelFactory -import ru.touchin.lifecycle.viewmodel.ViewModelFactoryProvider -import ru.touchin.roboswag.components.navigation.TouchinApp -import ru.touchin.template.di.app.DaggerApplicationComponent -import ru.touchin.template.di.app.modules.ApplicationModule -import ru.touchin.templates.logansquare.LoganSquareBigDecimalConverter -import ru.touchin.templates.logansquare.LoganSquareJodaTimeConverter -import java.math.BigDecimal -import javax.inject.Inject - -class TemplateApplication : TouchinApp(), ViewModelFactoryProvider { - @Inject - override lateinit var viewModelFactory: ViewModelFactory - - override fun onCreate() { - super.onCreate() - //TODO remove after init - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) - initializeLoganSquare() - initializeDagger() - - } - - private fun initializeLoganSquare() { - val formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZZ") - LoganSquare.registerTypeConverter(BigDecimal::class.java, LoganSquareBigDecimalConverter()) - LoganSquare.registerTypeConverter(DateTime::class.java, LoganSquareJodaTimeConverter(formatter)) - } - - private fun initializeDagger() { - DaggerApplicationComponent.builder() - .applicationModule(ApplicationModule(this)) - .build() - .inject(this) - } - -} diff --git a/app/src/main/java/ru/touchin/template/activities/StartupActivity.kt b/app/src/main/java/ru/touchin/template/activities/StartupActivity.kt deleted file mode 100644 index e0ef03c..0000000 --- a/app/src/main/java/ru/touchin/template/activities/StartupActivity.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ru.touchin.template.activities - -import android.os.Bundle -import com.touchin.template.R -import ru.touchin.roboswag.components.navigation.activities.BaseActivity - -class StartupActivity : BaseActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.common_activity) - } - -} diff --git a/app/src/main/java/ru/touchin/template/api/ExceptionsInterceptor.kt b/app/src/main/java/ru/touchin/template/api/ExceptionsInterceptor.kt deleted file mode 100644 index dc30306..0000000 --- a/app/src/main/java/ru/touchin/template/api/ExceptionsInterceptor.kt +++ /dev/null @@ -1,41 +0,0 @@ -package ru.touchin.template.api - -import okhttp3.Interceptor -import okhttp3.Response -import okhttp3.ResponseBody -import org.json.JSONException -import org.json.JSONObject -import ru.touchin.template.api.exceptions.ServerException -import ru.touchin.template.extensions.cloneBody -import java.io.IOException -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ExceptionsInterceptor @Inject constructor() : Interceptor { - - companion object { - private const val ERROR_MESSAGE_FIELD = "errorMessage" - } - - override fun intercept(chain: Interceptor.Chain): Response = chain - .proceed(chain.request()) - .also { getError(it, it.body())?.let { exception -> throw exception } } - - @Suppress("detekt.NestedBlockDepth") - private fun getError(response: Response, body: ResponseBody?): IOException? = body - ?.cloneBody() - ?.let { responseBody -> - try { - val jsonObject = JSONObject(responseBody) - val message = jsonObject.optString(ERROR_MESSAGE_FIELD) - when { - response.code() != 200 -> ServerException(response.code(), message) - else -> null - } - } catch (error: JSONException) { - null - } - } - -} diff --git a/app/src/main/java/ru/touchin/template/api/UserApi.kt b/app/src/main/java/ru/touchin/template/api/UserApi.kt deleted file mode 100644 index fb8c7ba..0000000 --- a/app/src/main/java/ru/touchin/template/api/UserApi.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ru.touchin.template.api - -import io.reactivex.Single -import retrofit2.http.Body -import retrofit2.http.POST - -interface UserApi { - - @POST("user/session/create") - fun getSession(@Body body: Body): Single - -} diff --git a/app/src/main/java/ru/touchin/template/di/ApplicationComponent.kt b/app/src/main/java/ru/touchin/template/di/ApplicationComponent.kt new file mode 100644 index 0000000..120e5ee --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/ApplicationComponent.kt @@ -0,0 +1,30 @@ +package ru.touchin.template.di + +import android.content.Context +import dagger.Component +import ru.terrakok.cicerone.Router +import ru.touchin.mvi_test.feature_login.LoginDeps +import ru.touchin.mvitest.network.di.NetworkModule +import ru.touchin.template.App +import ru.touchin.template.SingleActivity +import ru.touchin.template.core_prefs.PreferencesModule +import ru.touchin.template.navigation.MainNavigation +import javax.inject.Singleton + +@Singleton +@Component(modules = [ + PreferencesModule::class, + MainNavigationModule::class, + NetworkModule::class, + CoordinatorsImpl::class +]) +interface ApplicationComponent : LoginDeps { + + @MainNavigation + fun router(): Router + + fun inject(application: App) + + fun inject(activity: SingleActivity) + +} diff --git a/app/src/main/java/ru/touchin/template/di/ApplicationModule.kt b/app/src/main/java/ru/touchin/template/di/ApplicationModule.kt new file mode 100644 index 0000000..c979ceb --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/ApplicationModule.kt @@ -0,0 +1,9 @@ +package ru.touchin.template.di + +import dagger.Module +import dagger.Provides + +@Module +class ApplicationModule { + +} diff --git a/app/src/main/java/ru/touchin/template/di/CoordinatorsImpl.kt b/app/src/main/java/ru/touchin/template/di/CoordinatorsImpl.kt new file mode 100644 index 0000000..01c7eea --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/CoordinatorsImpl.kt @@ -0,0 +1,13 @@ +package ru.touchin.template.di + +import dagger.Binds +import dagger.Module +import ru.touchin.mvi_test.feature_login.navigation.LoginCoordinator +import ru.touchin.template.navigation.login.LoginCoordinatorImpl + +@Module +abstract class CoordinatorsImpl { + + @Binds + abstract fun loginCoordinator(impl: LoginCoordinatorImpl): LoginCoordinator +} diff --git a/app/src/main/java/ru/touchin/template/di/MainNavigationModule.kt b/app/src/main/java/ru/touchin/template/di/MainNavigationModule.kt new file mode 100644 index 0000000..d7c055d --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/MainNavigationModule.kt @@ -0,0 +1,27 @@ +package ru.touchin.template.di + +import dagger.Module +import dagger.Provides +import ru.terrakok.cicerone.Cicerone +import ru.terrakok.cicerone.NavigatorHolder +import ru.terrakok.cicerone.Router +import ru.touchin.template.navigation.MainNavigation +import javax.inject.Singleton + +@Module +class MainNavigationModule { + + @Provides + @Singleton + @MainNavigation + fun provideCicerone(): Cicerone = Cicerone.create() + + @Provides + @MainNavigation + fun provideNavigatorHolder(@MainNavigation cicerone: Cicerone): NavigatorHolder = cicerone.navigatorHolder + + @Provides + @MainNavigation + fun provideRouter(@MainNavigation cicerone: Cicerone): Router = cicerone.router + +} diff --git a/app/src/main/java/ru/touchin/template/di/app/ApplicationComponent.kt b/app/src/main/java/ru/touchin/template/di/app/ApplicationComponent.kt deleted file mode 100644 index b0eef4d..0000000 --- a/app/src/main/java/ru/touchin/template/di/app/ApplicationComponent.kt +++ /dev/null @@ -1,17 +0,0 @@ -package ru.touchin.template.di.app - -import dagger.Component -import ru.touchin.template.TemplateApplication -import ru.touchin.template.di.app.modules.ApplicationModule -import ru.touchin.template.di.app.modules.NetworkModule -import ru.touchin.template.di.app.modules.PersistentModule -import ru.touchin.template.di.viewmodel.ViewModelModule -import javax.inject.Singleton - -@Singleton -@Component(modules = [ApplicationModule::class, PersistentModule::class, ViewModelModule::class, NetworkModule::class]) -interface ApplicationComponent { - - fun inject(application: TemplateApplication) - -} diff --git a/app/src/main/java/ru/touchin/template/di/app/modules/ApplicationModule.kt b/app/src/main/java/ru/touchin/template/di/app/modules/ApplicationModule.kt deleted file mode 100644 index fcd01fd..0000000 --- a/app/src/main/java/ru/touchin/template/di/app/modules/ApplicationModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ru.touchin.template.di.app.modules - -import android.content.Context -import android.content.SharedPreferences -import android.preference.PreferenceManager -import dagger.Module -import dagger.Provides - -@Module -class ApplicationModule(private val context: Context) { - - @Provides - fun provideContext(): Context = context - - @Provides - fun provideDefaultSharedPreferences(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - -} diff --git a/app/src/main/java/ru/touchin/template/di/app/modules/NetworkModule.kt b/app/src/main/java/ru/touchin/template/di/app/modules/NetworkModule.kt deleted file mode 100644 index eed5fc8..0000000 --- a/app/src/main/java/ru/touchin/template/di/app/modules/NetworkModule.kt +++ /dev/null @@ -1,63 +0,0 @@ -package ru.touchin.template.di.app.modules - -import android.content.Context -import com.chuckerteam.chucker.api.ChuckerInterceptor -import com.touchin.template.BuildConfig -import dagger.Module -import dagger.Provides -import io.reactivex.schedulers.Schedulers -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import ru.touchin.template.api.ExceptionsInterceptor -import ru.touchin.template.api.UserApi -import ru.touchin.template.di.qualifiers.PublicApi -import ru.touchin.templates.logansquare.LoganSquareJsonFactory -import java.util.concurrent.TimeUnit -import javax.inject.Singleton - -@Module -class NetworkModule { - - companion object { - private val CONVERTER_FACTORY = LoganSquareJsonFactory() - private val CALL_ADAPTER_FACTORY = RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()) - private const val TIMEOUT = 30L - } - - @Singleton - @Provides - fun provideUserApi(@PublicApi retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java) - - @Singleton - @PublicApi - @Provides - fun providePublicRetrofit(@PublicApi client: OkHttpClient) = buildRetrofitInstance(client, BuildConfig.API_URL) - - @Singleton - @PublicApi - @Provides - fun providePublicClient(context: Context, exceptionsInterceptor: ExceptionsInterceptor): OkHttpClient = - buildPublicClient(context, exceptionsInterceptor) - - private fun buildRetrofitInstance(client: OkHttpClient, apiUrl: String): Retrofit = Retrofit.Builder() - .baseUrl(apiUrl) - .client(client) - .addCallAdapterFactory(CALL_ADAPTER_FACTORY) - .addConverterFactory(CONVERTER_FACTORY) - .build() - - private fun buildPublicClient(context: Context, exceptionsInterceptor: ExceptionsInterceptor): OkHttpClient = OkHttpClient.Builder() - .apply { - connectTimeout(TIMEOUT, TimeUnit.SECONDS) - readTimeout(TIMEOUT, TimeUnit.SECONDS) - writeTimeout(TIMEOUT, TimeUnit.SECONDS) - addInterceptor(exceptionsInterceptor) - addInterceptor(ChuckerInterceptor(context)) - if (BuildConfig.DEBUG) { - addNetworkInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) - } - }.build() - -} diff --git a/app/src/main/java/ru/touchin/template/di/app/modules/PersistentModule.kt b/app/src/main/java/ru/touchin/template/di/app/modules/PersistentModule.kt deleted file mode 100644 index 92c3198..0000000 --- a/app/src/main/java/ru/touchin/template/di/app/modules/PersistentModule.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ru.touchin.template.di.app.modules - -import android.content.Context -import android.content.SharedPreferences -import androidx.room.Room -import dagger.Module -import dagger.Provides -import ru.touchin.roboswag.components.utils.storables.PreferenceUtils -import ru.touchin.roboswag.core.observables.storable.NonNullStorable -import ru.touchin.template.di.qualifiers.SessionStorable -import ru.touchin.template.persistence.Database -import javax.inject.Singleton - -@Module -class PersistentModule { - - @Provides - @Singleton - fun provideDatabase(context: Context): Database = Room - .databaseBuilder(context, Database::class.java, "database") - .build() - - @Singleton - @Provides - @SessionStorable - fun provideSessionStorable(sharedPreferences: SharedPreferences): NonNullStorable = - PreferenceUtils.stringStorable("MIDDLE_SESSION", sharedPreferences, "") - -} diff --git a/app/src/main/java/ru/touchin/template/di/qualifiers/PublicApi.kt b/app/src/main/java/ru/touchin/template/di/qualifiers/PublicApi.kt deleted file mode 100644 index af243f0..0000000 --- a/app/src/main/java/ru/touchin/template/di/qualifiers/PublicApi.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ru.touchin.template.di.qualifiers - -import javax.inject.Qualifier - -@Qualifier -annotation class PublicApi diff --git a/app/src/main/java/ru/touchin/template/di/qualifiers/SessionStorable.kt b/app/src/main/java/ru/touchin/template/di/qualifiers/SessionStorable.kt deleted file mode 100644 index 794eba9..0000000 --- a/app/src/main/java/ru/touchin/template/di/qualifiers/SessionStorable.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ru.touchin.template.di.qualifiers - -import javax.inject.Qualifier - -@Qualifier -annotation class SessionStorable diff --git a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt deleted file mode 100644 index c097cbd..0000000 --- a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ru.touchin.template.di.viewmodel - -import androidx.lifecycle.ViewModel -import dagger.MapKey -import kotlin.reflect.KClass - -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) -@MapKey -annotation class ViewModelKey(val value: KClass) diff --git a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelModule.kt b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelModule.kt deleted file mode 100644 index 9a5ba36..0000000 --- a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package ru.touchin.template.di.viewmodel - -import androidx.lifecycle.ViewModel -import dagger.Binds -import dagger.Module -import dagger.multibindings.IntoMap -import ru.touchin.template.viewmodel.StartupViewModel - -@Module -interface ViewModelModule { - - @Binds - @IntoMap - @ViewModelKey(StartupViewModel::class) - fun bindStartupViewModel(viewModel: StartupViewModel): ViewModel - -} diff --git a/app/src/main/java/ru/touchin/template/model/TemplateApiError.kt b/app/src/main/java/ru/touchin/template/model/TemplateApiError.kt deleted file mode 100644 index b6ec761..0000000 --- a/app/src/main/java/ru/touchin/template/model/TemplateApiError.kt +++ /dev/null @@ -1,15 +0,0 @@ -package ru.touchin.template.model - -import com.bluelinelabs.logansquare.annotation.JsonEnum -import com.bluelinelabs.logansquare.annotation.JsonNumberValue - -@JsonEnum -enum class TemplateApiError { - - @JsonNumberValue(-1) - INVALID_PARAMETERS, - - @JsonNumberValue(0) - VALID_RESPONSE - -} diff --git a/app/src/main/java/ru/touchin/template/navigation/MainNavigation.kt b/app/src/main/java/ru/touchin/template/navigation/MainNavigation.kt new file mode 100644 index 0000000..e63ac31 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/navigation/MainNavigation.kt @@ -0,0 +1,6 @@ +package ru.touchin.template.navigation + +import javax.inject.Qualifier + +@Qualifier +annotation class MainNavigation diff --git a/app/src/main/java/ru/touchin/template/navigation/Screens.kt b/app/src/main/java/ru/touchin/template/navigation/Screens.kt new file mode 100644 index 0000000..6803959 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/navigation/Screens.kt @@ -0,0 +1,13 @@ +package ru.touchin.template.navigation + +import androidx.fragment.app.Fragment +import ru.terrakok.cicerone.android.support.SupportAppScreen +import ru.touchin.mvi_test.feature_login.presentation.LoginFragment + +object Screens { + + class Login : SupportAppScreen() { + override fun getFragment(): Fragment = LoginFragment() + + } +} diff --git a/app/src/main/java/ru/touchin/template/navigation/StartUpNavigation.kt b/app/src/main/java/ru/touchin/template/navigation/StartUpNavigation.kt new file mode 100644 index 0000000..6bb82a1 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/navigation/StartUpNavigation.kt @@ -0,0 +1,14 @@ +package ru.touchin.template.navigation + +import ru.terrakok.cicerone.Router +import javax.inject.Inject + +class StartUpNavigation @Inject constructor( + @MainNavigation private val router: Router +) { + + fun start() { + router.newRootScreen(Screens.Login()) + } + +} diff --git a/app/src/main/java/ru/touchin/template/navigation/login/LoginCoordinatorImpl.kt b/app/src/main/java/ru/touchin/template/navigation/login/LoginCoordinatorImpl.kt new file mode 100644 index 0000000..63d354a --- /dev/null +++ b/app/src/main/java/ru/touchin/template/navigation/login/LoginCoordinatorImpl.kt @@ -0,0 +1,17 @@ +package ru.touchin.template.navigation.login + +import ru.terrakok.cicerone.Router +import ru.touchin.mvi_test.feature_login.navigation.LoginCoordinator +import ru.touchin.template.navigation.MainNavigation +import ru.touchin.template.navigation.Screens +import javax.inject.Inject + +class LoginCoordinatorImpl @Inject constructor( + @MainNavigation private val router: Router +) : LoginCoordinator { + + override fun openMainScreen() { + router.exit() + } + +} diff --git a/app/src/main/java/ru/touchin/template/persistence/Database.kt b/app/src/main/java/ru/touchin/template/persistence/Database.kt deleted file mode 100644 index ca189e8..0000000 --- a/app/src/main/java/ru/touchin/template/persistence/Database.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ru.touchin.template.persistence - -import androidx.room.Database -import androidx.room.RoomDatabase -import ru.touchin.template.persistence.entities.UserEntity - -@Database(version = 1, entities = [UserEntity::class], exportSchema = false) -abstract class Database : RoomDatabase() diff --git a/app/src/main/java/ru/touchin/template/persistence/entities/UserEntity.kt b/app/src/main/java/ru/touchin/template/persistence/entities/UserEntity.kt deleted file mode 100644 index 3550e5f..0000000 --- a/app/src/main/java/ru/touchin/template/persistence/entities/UserEntity.kt +++ /dev/null @@ -1,16 +0,0 @@ -package ru.touchin.template.persistence.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "user") -data class UserEntity( - - @PrimaryKey - val uid: String = "", - - @ColumnInfo(name = "session_id") - var sessionId: String? - -) diff --git a/app/src/main/java/ru/touchin/template/viewmodel/StartupViewModel.kt b/app/src/main/java/ru/touchin/template/viewmodel/StartupViewModel.kt deleted file mode 100644 index c82d295..0000000 --- a/app/src/main/java/ru/touchin/template/viewmodel/StartupViewModel.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ru.touchin.template.viewmodel - -import ru.touchin.lifecycle.viewmodel.RxViewModel -import javax.inject.Inject - -class StartupViewModel @Inject constructor() : RxViewModel() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..a066a0d --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/layout/common_activity.xml b/app/src/main/res/layout/common_activity.xml deleted file mode 100644 index f78b1f2..0000000 --- a/app/src/main/res/layout/common_activity.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/build.gradle b/build.gradle deleted file mode 100644 index b265b4a..0000000 --- a/build.gradle +++ /dev/null @@ -1,69 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - ext.kotlin_version = '1.3.72' - ext.gradle_version = '3.6.2' - ext.fabric_version = '1.27.1' - repositories { - google() - jcenter() - maven { url "https://plugins.gradle.org/m2/" } - } - dependencies { - classpath "com.android.tools.build:gradle:${gradle_version}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta04' - classpath 'com.google.gms:google-services:4.3.3' - } - -} - -plugins { - id "io.gitlab.arturbosch.detekt" version "1.7.4" - id "de.aaschmid.cpd" version "3.1" -} - -allprojects { - repositories { - google() - jcenter() - maven { url "http://dl.bintray.com/touchin/touchin-tools" } - maven { url 'https://jitpack.io' } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} - -ext { - buildScriptsDir = "$rootProject.projectDir/BuildScripts" - versions = [ - compileSdk : 29, - androidx : '1.0.0', - fragment : '1.2.1', - appcompat : '1.1.0', - material : '1.1.0', - androidxKtx : '1.2.0', - lifecycle : '2.2.0', - room : '2.2.5', - dagger : '2.17', - retrofit : '2.4.0', - okhttp3 : '3.14.1', - logansquare : '1.4.3', - rxJava : '2.2.3', - rxAndroid : '2.1.1', - crashlytics : '2.10.1', - firebaseCrashlytics: '17.0.0-beta04', - glide : '4.11.0', - location : '16.0.0', - chucker : '3.1.1', - leakcanary : '2.1' - ] -} - -subprojects { - apply plugin: "io.gitlab.arturbosch.detekt" -} - -apply from: "$buildScriptsDir/gradle/staticAnalysis.gradle" diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..5695b72 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,40 @@ +buildscript { + repositories { + mavenCentral() + google() + jcenter() + maven("https://plugins.gradle.org/m2/") + } + dependencies { + classpath("com.android.tools.build:gradle:${Version.ANDROID_PLUGIN}") + classpath(kotlin("gradle-plugin", version = Version.KOTLIN)) + classpath("com.google.firebase:firebase-crashlytics-gradle:${Version.FIREBASE_CRASHLYTICS}") + classpath("com.vanniktech:gradle-dependency-graph-generator-plugin:0.5.0") + } +} + +plugins { + id(Plugins.DETEKT).version("1.10.0") + id(Plugins.CPD).version("3.1") +} + +allprojects { + repositories { + google() + jcenter() + maven("http://dl.bintray.com/touchin/touchin-tools") + maven("https://jitpack.io") + } +} + +subprojects { + apply(plugin = Plugins.DETEKT) +} + +val buildScriptsDir = "${rootProject.projectDir}/BuildScripts" +ext["buildScriptsDir"] = buildScriptsDir + +apply(plugin = Plugins.DEPENDENCY_GRAPH) + +//TODO: make staticAnalysis work on kotlin DSL +//apply(from = "$buildScriptsDir/gradle/staticAnalysis.gradle") diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..bda1b11 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,33 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` + `kotlin-dsl-precompiled-script-plugins` + kotlin("jvm") version embeddedKotlinVersion +} + +// The kotlin-dsl plugin requires a repository to be declared +repositories { + jcenter() + google() + maven { + url = uri("https://plugins.gradle.org/m2/") + } +} + +dependencies { + // android gradle plugin, required by custom plugin + implementation("com.android.tools.build:gradle:4.0.0") + + // kotlin plugin, required by custom plugin + implementation(kotlin("gradle-plugin", embeddedKotlinVersion)) + + gradleKotlinDsl() + implementation(kotlin("stdlib-jdk8")) +} + + +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} diff --git a/buildSrc/src/main/kotlin/AndroidConfig.kt b/buildSrc/src/main/kotlin/AndroidConfig.kt new file mode 100644 index 0000000..d80019f --- /dev/null +++ b/buildSrc/src/main/kotlin/AndroidConfig.kt @@ -0,0 +1,23 @@ +object AndroidConfig { + const val COMPILE_SDK_VERSION = 29 + const val MIN_SDK_VERSION = 23 + const val TARGET_SDK_VERSION = 29 + const val BUILD_TOOLS_VERSION = "29.0.2" + + val VERSION_CODE: Int = System.getenv("BUILD_NUMBER")?.toIntOrNull() ?: 10000 + val VERSION_NAME = "1.0.0" + + val PROD_BUILD_NAME = ProguardFlavour.OBFUSCATE + + ApiFlavour.CustomerProd.flavourName + + SSLPinningFlavour.ON.flavourName + + TestPanelFlavour.OFF.flavourName + + BuildType.Release.name + + const val TEST_APP_ID = "ru.touchin.template" + const val PROD_APP_ID = "ru.ask.client" + + +} + + + diff --git a/buildSrc/src/main/kotlin/DependencyHandler.kt b/buildSrc/src/main/kotlin/DependencyHandler.kt new file mode 100644 index 0000000..af5a47d --- /dev/null +++ b/buildSrc/src/main/kotlin/DependencyHandler.kt @@ -0,0 +1,136 @@ +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.artifacts.dsl.DependencyHandler + +fun DependencyHandler.fragment() { + implementation(Library.ANDROIDX_FRAGMENT) + implementation(Library.ANDROIDX_FRAGMENT_KTX) + implementationModule(Module.RoboSwag.NAVIGATION_BASE) +} + +fun DependencyHandler.materialDesign() { + implementation(Library.ANDROID_MATERIAL) + implementation(Library.SWIPE_REFRESH) +} + +fun DependencyHandler.constraintLayout() { + implementation(Library.ANDROIDX_CONSTRAINT) +} + +fun DependencyHandler.androidX() { + implementation(Library.ANDROIDX_CORE) + implementation(Library.ANDROIDX_APPCOMPAT) + implementationModule(Module.RoboSwag.KOTLIN_EXTENSIONS) +} + +fun DependencyHandler.recyclerView() { + implementation(Library.ANDROIDX_RECYCLER) + implementationModule(Module.RoboSwag.RECYCLER_VIEW_ADAPTERS) +} + +fun DependencyHandler.kotlinStd() { + implementation(Library.KOTLIN_STDLIB) +} + +fun DependencyHandler.navigation() { + implementation(Library.CICERONE) + implementationModule(Module.RoboSwag.NAVIGATION_CICERONE) +} + +fun DependencyHandler.featureModules() { + Module.Feature.ALL.forEach(this::implementationModule) +} + +fun DependencyHandler.mvi() { + implementationModule(Module.RoboSwag.MVI_ARCH) + fragment() + lifecycle() +} + +fun DependencyHandler.coreNetwork() { + implementationModule(Module.Core.NETWORK) +} + +fun DependencyHandler.retrofit() { + implementation(Library.RETROFIT) + implementation(Library.OKHTTP_LOGGING_INTERCEPTOR) + implementation(Library.OKHTTP) + implementation(Library.MOSHI_RETROFIT) +} + +fun DependencyHandler.dagger(withAssistedInject: Boolean = true) { + implementation(Library.DAGGER) + kapt(Library.DAGGER_COMPILER) + implementation(Library.DAGGER_COMPONENT_MANAGER) + + if (withAssistedInject) { + compileOnly(Library.DAGGER_INJECT_ASSISTED_ANNOTATIONS) + kapt(Library.DAGGER_INJECT_ASSISTED_PROCESSOR) + } +} + +fun DependencyHandler.glide() { + implementation(Library.GLIDE) + implementation(Library.GLIDE_OKHTTP_INTEGRATION) + kapt(Library.GLIDE_COMPILER) +} + +fun DependencyHandler.moshi() { + implementation(Library.MOSHI) + kapt(Library.MOSHI_CODEGEN) +} + +fun DependencyHandler.lifecycle() { + implementation(Library.ANDROID_LIFECYCLE_EXTENSIONS) + implementation(Library.ANDROID_LIFECYCLE_VIEW_MODEL_EXTENSIONS) + implementation(Library.ANDROID_LIFECYCLE_LIVE_DATA_EXTENSIONS) + implementationModule(Module.RoboSwag.LIFECYCLE) +} + +fun DependencyHandler.coroutines() { + implementation(Library.COROUTINES_CORE) + implementation(Library.COROUTINES_ANDROID) +} + +fun DependencyHandler.leakCanary() { + add("withTestPanelImplementation", Library.LEAK_CANARY) +} + +fun DependencyHandler.sharedPrefs() { + implementationModule(Module.RoboSwag.STORABLE) + implementationModule(Module.Core.PREFS) +} + +fun DependencyHandler.chucker() { + add("withTestPanelImplementation", Library.CHUCKER) + add("withoutTestPanelImplementation", Library.CHUCKER_NO_OP) +} + +fun DependencyHandler.implementationModule(moduleName: String) { + implementation(project(":$moduleName")) +} + +private fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? = + add("implementation", dependencyNotation) + +private fun DependencyHandler.kapt(dependencyNotation: Any): Dependency? = + add("kapt", dependencyNotation) + +private fun DependencyHandler.compileOnly(dependencyNotation: Any): Dependency? = + add("compileOnly", dependencyNotation) + +private fun DependencyHandler.project( + path: String, + configuration: String? = null +): ProjectDependency { + val notation = if (configuration != null) { + mapOf("path" to path, "configuration" to configuration) + } else { + mapOf("path" to path) + } + + return uncheckedCast(project(notation)) +} + +@Suppress("unchecked_cast", "nothing_to_inline", "detekt.UnsafeCast") +private inline fun uncheckedCast(obj: Any?): T = obj as T diff --git a/buildSrc/src/main/kotlin/Library.kt b/buildSrc/src/main/kotlin/Library.kt new file mode 100644 index 0000000..f29f72a --- /dev/null +++ b/buildSrc/src/main/kotlin/Library.kt @@ -0,0 +1,48 @@ +object Library { + const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:${Version.KOTLIN}" + + const val ANDROIDX_APPCOMPAT = "androidx.appcompat:appcompat:${Version.ANDROIDX_APPCOMPAT}" + const val ANDROIDX_CORE = "androidx.core:core-ktx:${Version.ANDROIDX_CORE}" + const val ANDROIDX_RECYCLER = "androidx.recyclerview:recyclerview:${Version.ANDROIDX}" + const val ANDROIDX_CONSTRAINT = "androidx.constraintlayout:constraintlayout:${Version.ANDROIDX_CONSTRAINT}" + + const val ANDROIDX_FRAGMENT = "androidx.fragment:fragment:${Version.ANDROIDX_FRAGMENT}" + const val ANDROIDX_FRAGMENT_KTX = "androidx.fragment:fragment-ktx:${Version.ANDROIDX_FRAGMENT}" + + const val ANDROID_MATERIAL = "com.google.android.material:material:${Version.ANDROID_MATERIAL}" + const val SWIPE_REFRESH = "androidx.swiperefreshlayout:swiperefreshlayout:${Version.SWIPE_REFRESH}" + + const val ANDROID_LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:${Version.ANDROID_LIFECYCLE}" + const val ANDROID_LIFECYCLE_VIEW_MODEL_EXTENSIONS = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.ANDROID_LIFECYCLE}" + const val ANDROID_LIFECYCLE_LIVE_DATA_EXTENSIONS = "androidx.lifecycle:lifecycle-livedata-ktx:${Version.ANDROID_LIFECYCLE}" + + const val DAGGER = "com.google.dagger:dagger:${Version.DAGGER}" + const val DAGGER_COMPILER = "com.google.dagger:dagger-compiler:${Version.DAGGER}" + + const val DAGGER_INJECT_ASSISTED_ANNOTATIONS = "com.squareup.inject:assisted-inject-annotations-dagger2:${Version.DAGGER_INJECT_ASSISTED}" + const val DAGGER_INJECT_ASSISTED_PROCESSOR = "com.squareup.inject:assisted-inject-processor-dagger2:${Version.DAGGER_INJECT_ASSISTED}" + + const val DAGGER_COMPONENT_MANAGER = "com.github.valeryponomarenko.componentsmanager:androidx:${Version.DAGGER_COMPONENT_MANAGER}" + + const val GLIDE = "com.github.bumptech.glide:glide:${Version.GLIDE}" + const val GLIDE_COMPILER = "com.github.bumptech.glide:compiler:${Version.GLIDE}" + const val GLIDE_OKHTTP_INTEGRATION = "com.github.bumptech.glide:okhttp3-integration:${Version.GLIDE}" + + const val RETROFIT = "com.squareup.retrofit2:retrofit:${Version.RETROFIT}" + + const val OKHTTP_LOGGING_INTERCEPTOR = "com.squareup.okhttp3:logging-interceptor:${Version.OKHTTP}" + const val OKHTTP = "com.squareup.okhttp3:okhttp-urlconnection:${Version.OKHTTP}" + + const val MOSHI = "com.squareup.moshi:moshi:${Version.MOSHI}" + const val MOSHI_CODEGEN = "com.squareup.moshi:moshi-kotlin-codegen:${Version.MOSHI}" + const val MOSHI_RETROFIT = "com.squareup.retrofit2:converter-moshi:${Version.RETROFIT}" + + const val COROUTINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.COROUTINES}" + const val COROUTINES_ANDROID = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.COROUTINES}" + + const val CICERONE = "ru.terrakok.cicerone:cicerone:${Version.CICERONE}" + + const val LEAK_CANARY = "com.squareup.leakcanary:leakcanary-android:${Version.LEAK_CANARY}" + const val CHUCKER = "com.github.chuckerteam.chucker:library:${Version.CHUCKER}" + const val CHUCKER_NO_OP = "com.github.chuckerteam.chucker:library-no-op:${Version.CHUCKER}" +} diff --git a/buildSrc/src/main/kotlin/Module.kt b/buildSrc/src/main/kotlin/Module.kt new file mode 100644 index 0000000..32b9c3b --- /dev/null +++ b/buildSrc/src/main/kotlin/Module.kt @@ -0,0 +1,32 @@ +object Module { + + object RoboSwag { + const val UTILS = "utils" + const val LOGGING = "logging" + const val MVI_ARCH = "mvi-arch" + const val NAVIGATION_BASE = "navigation-base" + const val NAVIGATION_CICERONE = "navigation-cicerone" + const val STORABLE = "storable" + const val LIFECYCLE = "lifecycle" + const val PAGINATION = "pagination" + const val VIEWS = "views" + const val RECYCLER_VIEW_ADAPTERS = "recyclerview-adapters" + const val KOTLIN_EXTENSIONS = "kotlin-extensions" + } + + object Feature { + const val LOGIN = "feature_login" + val ALL = listOf( + LOGIN + ) + } + + object Core { + const val NETWORK = "core_network" + const val PREFS = "core_prefs" + const val STRINGS = "core_strings" + const val UTILS = "core_utils" + const val UI = "core_ui" + } + +} diff --git a/buildSrc/src/main/kotlin/Plugins.kt b/buildSrc/src/main/kotlin/Plugins.kt new file mode 100644 index 0000000..dca7c0e --- /dev/null +++ b/buildSrc/src/main/kotlin/Plugins.kt @@ -0,0 +1,18 @@ +object Plugins { + const val ANDROID_APPLICATION = "com.android.application" + const val ANDROID_LIBRARY = "com.android.library" + + const val ANDROID_APP_PLUGIN_WITH_DEFAULT_CONFIG = "android_app" + const val ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG = "android_lib" + + const val KOTLIN_ANDROID = "kotlin-android" + const val KOTLIN_ANDROID_EXTENSIONS = "kotlin-android-extensions" + const val KOTLIN_KAPT = "kotlin-kapt" + + const val FIREBASE_CRASHLYTICS = "com.google.firebase.crashlytics" + + const val DEPENDENCY_GRAPH = "com.vanniktech.dependency.graph.generator" + + const val DETEKT = "io.gitlab.arturbosch.detekt" + const val CPD = "de.aaschmid.cpd" +} diff --git a/buildSrc/src/main/kotlin/SigningConfig.kt b/buildSrc/src/main/kotlin/SigningConfig.kt new file mode 100644 index 0000000..ad4c2af --- /dev/null +++ b/buildSrc/src/main/kotlin/SigningConfig.kt @@ -0,0 +1,37 @@ +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.kotlin.dsl.NamedDomainObjectContainerScope +import java.io.File + +sealed class SigningConfig( + val name: String, + val storeFile: File, + val storePassword: String, + val keyAlias: String, + val keyPassword: String +) { + object Test: SigningConfig( + name = "test", + storeFile = File("file/way"), + storePassword = "pass", + keyAlias = "alias", + keyPassword = "pass" + ) + + object Prod: SigningConfig( + name = "prod", + storeFile = File("fsdfsd"), + storePassword = "fsdf", + keyAlias = "sdfdsf", + keyPassword = "dsfsdf" + ) + +} + +fun NamedDomainObjectContainer.addConfig(config: SigningConfig) { + create(config.name) { + storeFile = config.storeFile + storePassword = config.storePassword + keyAlias = config.keyAlias + keyPassword = config.keyPassword + } +} diff --git a/buildSrc/src/main/kotlin/Version.kt b/buildSrc/src/main/kotlin/Version.kt new file mode 100644 index 0000000..c88f4ac --- /dev/null +++ b/buildSrc/src/main/kotlin/Version.kt @@ -0,0 +1,37 @@ +object Version { + + const val ANDROID_PLUGIN = "4.0.0" + + const val KOTLIN = "1.3.72" + + const val ANDROIDX = "1.1.0" + const val ANDROIDX_CORE = "1.2.0" + const val ANDROIDX_APPCOMPAT = "1.0.2" + const val ANDROIDX_CONSTRAINT = "2.0.0-beta4" + const val ANDROIDX_FRAGMENT = "1.2.1" + + const val ANDROID_MATERIAL = "1.1.0" + const val ANDROID_LIFECYCLE = "2.2.0" + const val SWIPE_REFRESH = "1.0.0" + + const val DAGGER = "2.26" + const val DAGGER_INJECT_ASSISTED = "0.5.2" + const val DAGGER_COMPONENT_MANAGER = "2.1.0" + + const val GLIDE = "4.10.0" + + const val RETROFIT = "2.8.1" + const val OKHTTP = "3.14.1" + + const val MOSHI = "1.9.2" + + const val COROUTINES = "1.3.6" + + const val CICERONE = "5.1.0" + + const val FIREBASE_CRASHLYTICS = "2.1.1" + + const val LEAK_CANARY = "2.4" + const val CHUCKER = "3.2.0" + +} diff --git a/buildSrc/src/main/kotlin/flavours/ApiFlavour.kt b/buildSrc/src/main/kotlin/flavours/ApiFlavour.kt new file mode 100644 index 0000000..7b0df4e --- /dev/null +++ b/buildSrc/src/main/kotlin/flavours/ApiFlavour.kt @@ -0,0 +1,39 @@ +import com.android.build.gradle.LibraryExtension + +sealed class ApiFlavour( + val name: String, + val apiUrl: String +) : Flavour(name, DIMENSION_NAME) { + + companion object { + const val DIMENSION_NAME = "apiEndpoint" + } + + object MockDev : ApiFlavour( + name = "mockDev", + apiUrl = "url1" + ) + + object TouchinTest : ApiFlavour( + name = "touchinTest", + apiUrl = "url2" + ) + + object CustomerProd : ApiFlavour( + name = "customerProd", + apiUrl = "url3" + ) + +} + +fun LibraryExtension.addFlavour(flavour: ApiFlavour, customEndpoint: String?) { + productFlavors { + create(flavour.name) { + dimension = flavour.dimensionName + buildConfigField("String", "API_URL", "\"${customEndpoint ?: flavour.apiUrl}\"") + } + } +} + + + diff --git a/buildSrc/src/main/kotlin/flavours/BuildType.kt b/buildSrc/src/main/kotlin/flavours/BuildType.kt new file mode 100644 index 0000000..c9a7f19 --- /dev/null +++ b/buildSrc/src/main/kotlin/flavours/BuildType.kt @@ -0,0 +1,31 @@ +import org.gradle.api.NamedDomainObjectContainer + +fun NamedDomainObjectContainer.addBuildType(type: BuildType) { + getByName(type.name) { + isMinifyEnabled = type.isMinifyEnabled + isShrinkResources = type.isShrinkResources + } +} + +sealed class BuildType( + val name: String, + val isMinifyEnabled: Boolean, + val isShrinkResources: Boolean +) { + + object Debug : BuildType( + name = "debug", + isMinifyEnabled = false, + isShrinkResources = false + ) + + object Release : BuildType( + name = "release", + isMinifyEnabled = true, + isShrinkResources = true + ) + +} + + + diff --git a/buildSrc/src/main/kotlin/flavours/Flavour.kt b/buildSrc/src/main/kotlin/flavours/Flavour.kt new file mode 100644 index 0000000..d2ba569 --- /dev/null +++ b/buildSrc/src/main/kotlin/flavours/Flavour.kt @@ -0,0 +1,10 @@ +import com.android.build.gradle.internal.dsl.ProductFlavor +import org.gradle.api.NamedDomainObjectContainer + +abstract class Flavour(val flavourName: String, val dimensionName: String) + +fun NamedDomainObjectContainer.addEmptyFlavour(flavour: Flavour) { + create(flavour.flavourName) { + dimension = flavour.dimensionName + } +} diff --git a/buildSrc/src/main/kotlin/flavours/ProguardFlavour.kt b/buildSrc/src/main/kotlin/flavours/ProguardFlavour.kt new file mode 100644 index 0000000..62731ce --- /dev/null +++ b/buildSrc/src/main/kotlin/flavours/ProguardFlavour.kt @@ -0,0 +1,6 @@ +object ProguardFlavour { + const val DIMENSION_NAME = "proguardSettings" + + const val NO_OBFUSCATE = "noObfuscate" + const val OBFUSCATE = "obfuscate" +} diff --git a/buildSrc/src/main/kotlin/flavours/SSLPinningFlavour.kt b/buildSrc/src/main/kotlin/flavours/SSLPinningFlavour.kt new file mode 100644 index 0000000..e763404 --- /dev/null +++ b/buildSrc/src/main/kotlin/flavours/SSLPinningFlavour.kt @@ -0,0 +1,30 @@ +import com.android.build.gradle.LibraryExtension + +sealed class SSLPinningFlavour( + val name: String, + val withSslPinning: Boolean +) : Flavour(name, DIMENSION_NAME) { + + companion object { + const val DIMENSION_NAME = "sslPinning" + } + + object OFF: SSLPinningFlavour( + name = "withoutSSLPinning", + withSslPinning = true + ) + + object ON: SSLPinningFlavour( + name = "withSSLPinning", + withSslPinning = true + ) +} + +fun LibraryExtension.addFlavour(flavour: SSLPinningFlavour) { + productFlavors { + create(flavour.name) { + dimension = flavour.dimensionName + buildConfigField("Boolean", "WithSSLPinning", flavour.withSslPinning.toString()) + } + } +} diff --git a/buildSrc/src/main/kotlin/flavours/TestPanelFlavour.kt b/buildSrc/src/main/kotlin/flavours/TestPanelFlavour.kt new file mode 100644 index 0000000..2183bb3 --- /dev/null +++ b/buildSrc/src/main/kotlin/flavours/TestPanelFlavour.kt @@ -0,0 +1,16 @@ +sealed class TestPanelFlavour( + name: String +) : Flavour(name, DIMENSION_NAME) { + + companion object { + const val DIMENSION_NAME = "testPanel" + } + + object OFF : TestPanelFlavour( + name = "withoutTestPanel" + ) + + object ON : TestPanelFlavour( + name = "withTestPanel" + ) +} diff --git a/buildSrc/src/main/kotlin/plugins/AndroidAppPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidAppPlugin.kt new file mode 100644 index 0000000..9d722c5 --- /dev/null +++ b/buildSrc/src/main/kotlin/plugins/AndroidAppPlugin.kt @@ -0,0 +1,13 @@ +package plugins + +import Plugins +import org.gradle.api.Project + +class AndroidAppPlugin : BaseAndroidPlugin() { + + override fun apply(target: Project) { + target.plugins.apply(Plugins.ANDROID_APPLICATION) + super.apply(target) + } + +} diff --git a/buildSrc/src/main/kotlin/plugins/AndroidLibPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidLibPlugin.kt new file mode 100644 index 0000000..ca976ba --- /dev/null +++ b/buildSrc/src/main/kotlin/plugins/AndroidLibPlugin.kt @@ -0,0 +1,13 @@ +package plugins + +import Plugins +import org.gradle.api.Project + +class AndroidLibPlugin : BaseAndroidPlugin() { + + override fun apply(target: Project) { + target.plugins.apply(Plugins.ANDROID_LIBRARY) + super.apply(target) + } + +} diff --git a/buildSrc/src/main/kotlin/plugins/BaseAndroidPlugin.kt b/buildSrc/src/main/kotlin/plugins/BaseAndroidPlugin.kt new file mode 100644 index 0000000..f6f5589 --- /dev/null +++ b/buildSrc/src/main/kotlin/plugins/BaseAndroidPlugin.kt @@ -0,0 +1,63 @@ +package plugins + +import AndroidConfig +import Plugins +import com.android.build.gradle.BaseExtension +import kotlinStd +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.getByType +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +abstract class BaseAndroidPlugin : Plugin { + override fun apply(target: Project) { + target.configurePlugins() + target.configureAndroid() + target.configureDependencies() + } + + private fun Project.configurePlugins() { + plugins.apply(Plugins.KOTLIN_ANDROID) + plugins.apply(Plugins.KOTLIN_ANDROID_EXTENSIONS) + plugins.apply(Plugins.KOTLIN_KAPT) + } + + private fun Project.configureAndroid() = extensions.getByType().run { + compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION) + buildToolsVersion = AndroidConfig.BUILD_TOOLS_VERSION + + defaultConfig { + minSdkVersion(AndroidConfig.MIN_SDK_VERSION) + targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION) + versionCode = AndroidConfig.VERSION_CODE + versionName = AndroidConfig.VERSION_NAME + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + coreLibraryDesugaringEnabled = true + } + + buildFeatures.viewBinding = true + + tasks.withType(KotlinCompile::class.java) { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + + private fun Project.configureDependencies() = dependencies { + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + kotlinStd() + add("coreLibraryDesugaring", "com.android.tools:desugar_jdk_libs:1.0.5") + } + + private fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? = + add("implementation", dependencyNotation) +} diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/android_app.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/android_app.properties new file mode 100644 index 0000000..e5d4100 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/android_app.properties @@ -0,0 +1 @@ +implementation-class=plugins.AndroidAppPlugin diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/android_lib.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/android_lib.properties new file mode 100644 index 0000000..6625555 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/android_lib.properties @@ -0,0 +1 @@ +implementation-class=plugins.AndroidLibPlugin diff --git a/core/core_network/.gitignore b/core/core_network/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/core/core_network/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core/core_network/build.gradle.kts b/core/core_network/build.gradle.kts new file mode 100644 index 0000000..1fcbdc5 --- /dev/null +++ b/core/core_network/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG) +} + +val customEndpoint: String? = System.getenv("CUSTOM_ENDPOINT")?.takeIf(String::isNotBlank) + +android { + defaultConfig { + rootProject.extensions.add("pathToApiSchemes", "$rootDir/common/api") + rootProject.extensions.add("applicationId", AndroidConfig.TEST_APP_ID) + } + + flavorDimensions( + ApiFlavour.DIMENSION_NAME, + SSLPinningFlavour.DIMENSION_NAME, + TestPanelFlavour.DIMENSION_NAME + ) + + addFlavour(ApiFlavour.MockDev, customEndpoint) + addFlavour(ApiFlavour.TouchinTest, customEndpoint) + addFlavour(ApiFlavour.CustomerProd, customEndpoint) + + addFlavour(SSLPinningFlavour.OFF) + addFlavour(SSLPinningFlavour.ON) + + productFlavors { + addEmptyFlavour(TestPanelFlavour.OFF) + addEmptyFlavour(TestPanelFlavour.ON) + } +} + +dependencies { + retrofit() + dagger() + moshi() + coroutines() + chucker() +} + +//afterEvaluate { +// tasks +// .asIterable() +// .filter { it.name.contains("compile") && it.name.contains("JavaWithJavac") } +// .forEach { it.dependsOn("apiGenerator") } +//} +// +//apply(from = "${rootProject.extra["buildScriptsDir"]}/gradle/apiGenerator.gradle") diff --git a/core/core_network/src/main/AndroidManifest.xml b/core/core_network/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7adec17 --- /dev/null +++ b/core/core_network/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/app/src/main/java/ru/touchin/template/extensions/ResponseBody.kt b/core/core_network/src/main/java/ru/touchin/mvitest/network/ResponseBodyExt.kt similarity index 62% rename from app/src/main/java/ru/touchin/template/extensions/ResponseBody.kt rename to core/core_network/src/main/java/ru/touchin/mvitest/network/ResponseBodyExt.kt index 9ade80c..caf820e 100644 --- a/app/src/main/java/ru/touchin/template/extensions/ResponseBody.kt +++ b/core/core_network/src/main/java/ru/touchin/mvitest/network/ResponseBodyExt.kt @@ -1,10 +1,10 @@ -package ru.touchin.template.extensions +package ru.touchin.mvitest.network import okhttp3.ResponseBody import java.nio.charset.Charset fun ResponseBody.cloneBody(): String? = source() - ?.also { it.request(Long.MAX_VALUE) } - ?.buffer() + .also { it.request(Long.MAX_VALUE) } + .buffer ?.clone() ?.readString(Charset.forName("UTF-8")) diff --git a/core/core_network/src/main/java/ru/touchin/mvitest/network/di/DefaultPageSize.kt b/core/core_network/src/main/java/ru/touchin/mvitest/network/di/DefaultPageSize.kt new file mode 100644 index 0000000..c5c2ab8 --- /dev/null +++ b/core/core_network/src/main/java/ru/touchin/mvitest/network/di/DefaultPageSize.kt @@ -0,0 +1,6 @@ +package ru.touchin.mvitest.network.di + +import javax.inject.Qualifier + +@Qualifier +annotation class DefaultPageSize diff --git a/core/core_network/src/main/java/ru/touchin/mvitest/network/di/NetworkModule.kt b/core/core_network/src/main/java/ru/touchin/mvitest/network/di/NetworkModule.kt new file mode 100644 index 0000000..aa4a14f --- /dev/null +++ b/core/core_network/src/main/java/ru/touchin/mvitest/network/di/NetworkModule.kt @@ -0,0 +1,55 @@ +package ru.touchin.mvitest.network.di + +import com.squareup.moshi.Moshi +import dagger.Module +import dagger.Provides +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import ru.touchin.mvitest.network.interceptor.ExceptionsInterceptor +import ru.touchin.template.core_network.BuildConfig +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +@Module +class NetworkModule { + + companion object { + private const val TIMEOUT = 30L + } + + @Singleton + @Provides + fun providePublicClient(exceptionsInterceptor: ExceptionsInterceptor): OkHttpClient = + buildPublicClient(exceptionsInterceptor) + + @Singleton + @Provides + fun provideMoshi() = buildMoshi() + + @Singleton + @Provides + fun provideRetrofit(client: OkHttpClient, moshi: Moshi) = buildRetrofitInstance(client, moshi) + + private fun buildMoshi() = Moshi.Builder() + .build() + + private fun buildRetrofitInstance(client: OkHttpClient, moshi: Moshi): Retrofit = Retrofit.Builder() + .baseUrl(BuildConfig.API_URL) + .client(client) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + + private fun buildPublicClient(exceptionsInterceptor: ExceptionsInterceptor): OkHttpClient = OkHttpClient.Builder() + .apply { + connectTimeout(TIMEOUT, TimeUnit.SECONDS) + readTimeout(TIMEOUT, TimeUnit.SECONDS) + writeTimeout(TIMEOUT, TimeUnit.SECONDS) + addInterceptor(exceptionsInterceptor) + if (BuildConfig.DEBUG) { + addNetworkInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) + } + }.build() + +} diff --git a/core/core_network/src/main/java/ru/touchin/mvitest/network/interceptor/ExceptionsInterceptor.kt b/core/core_network/src/main/java/ru/touchin/mvitest/network/interceptor/ExceptionsInterceptor.kt new file mode 100644 index 0000000..4de7c2c --- /dev/null +++ b/core/core_network/src/main/java/ru/touchin/mvitest/network/interceptor/ExceptionsInterceptor.kt @@ -0,0 +1,41 @@ +package ru.touchin.mvitest.network.interceptor + +import okhttp3.Interceptor +import okhttp3.Response +import okhttp3.ResponseBody +import org.json.JSONException +import org.json.JSONObject +import ru.touchin.mvitest.network.cloneBody +import ru.touchin.mvitest.network.models.ServerException +import java.io.IOException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ExceptionsInterceptor @Inject constructor() : Interceptor { + + companion object { + private const val ERROR_MESSAGE_FIELD = "errorMessage" + } + + override fun intercept(chain: Interceptor.Chain): Response = chain + .proceed(chain.request()) + .also { getError(it, it.body())?.let { exception -> throw exception } } + + @Suppress("detekt.NestedBlockDepth") + private fun getError(response: Response, body: ResponseBody?): IOException? = body + ?.cloneBody() + ?.let { responseBody -> + try { + val jsonObject = JSONObject(responseBody) + val message = jsonObject.optString(ERROR_MESSAGE_FIELD) + when { + response.code() != 200 -> ServerException(response.code(), message) + else -> null + } + } catch (error: JSONException) { + null + } + } + +} diff --git a/app/src/main/java/ru/touchin/template/api/exceptions/ServerException.kt b/core/core_network/src/main/java/ru/touchin/mvitest/network/models/ServerException.kt similarity index 83% rename from app/src/main/java/ru/touchin/template/api/exceptions/ServerException.kt rename to core/core_network/src/main/java/ru/touchin/mvitest/network/models/ServerException.kt index 2cb157c..c98b8be 100644 --- a/app/src/main/java/ru/touchin/template/api/exceptions/ServerException.kt +++ b/core/core_network/src/main/java/ru/touchin/mvitest/network/models/ServerException.kt @@ -1,6 +1,5 @@ -package ru.touchin.template.api.exceptions +package ru.touchin.mvitest.network.models -import ru.touchin.template.model.TemplateApiError import java.io.IOException open class ServerException(val code: Int, message: String? = null) : IOException(message) { diff --git a/core/core_network/src/main/java/ru/touchin/mvitest/network/models/TemplateApiError.kt b/core/core_network/src/main/java/ru/touchin/mvitest/network/models/TemplateApiError.kt new file mode 100644 index 0000000..93c7e77 --- /dev/null +++ b/core/core_network/src/main/java/ru/touchin/mvitest/network/models/TemplateApiError.kt @@ -0,0 +1,9 @@ +package ru.touchin.mvitest.network.models + +enum class TemplateApiError { + + INVALID_PARAMETERS, + + VALID_RESPONSE + +} diff --git a/core/core_network/src/main/res/values/strings.xml b/core/core_network/src/main/res/values/strings.xml new file mode 100644 index 0000000..e3a7072 --- /dev/null +++ b/core/core_network/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + My Library + diff --git a/core/core_prefs/.gitignore b/core/core_prefs/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/core/core_prefs/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core/core_prefs/build.gradle.kts b/core/core_prefs/build.gradle.kts new file mode 100644 index 0000000..ca6e8e2 --- /dev/null +++ b/core/core_prefs/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG) +} + +dependencies { + implementation(Library.DAGGER) + implementationModule(Module.RoboSwag.STORABLE) +} diff --git a/core/core_prefs/src/main/AndroidManifest.xml b/core/core_prefs/src/main/AndroidManifest.xml new file mode 100644 index 0000000..48c2fe8 --- /dev/null +++ b/core/core_prefs/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/core/core_prefs/src/main/java/ru/touchin/template/core_prefs/PreferencesModule.kt b/core/core_prefs/src/main/java/ru/touchin/template/core_prefs/PreferencesModule.kt new file mode 100644 index 0000000..afba1ad --- /dev/null +++ b/core/core_prefs/src/main/java/ru/touchin/template/core_prefs/PreferencesModule.kt @@ -0,0 +1,24 @@ +package ru.touchin.template.core_prefs + +import android.content.Context +import android.content.SharedPreferences +import android.preference.PreferenceManager +import dagger.Module +import dagger.Provides +import ru.touchin.roboswag.components.utils.storables.PreferenceUtils +import ru.touchin.roboswag.core.observables.storable.NonNullStorable +import javax.inject.Singleton + +@Module +class PreferencesModule(private val context: Context) { + + @Provides + @Singleton + fun provideDefaultSharedPreferences(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + @Provides + @Singleton + fun provideTutorialStorable(sharedPreferences: SharedPreferences): NonNullStorable = PreferenceUtils + .booleanStorable("TUTORIAL_STORABLE", sharedPreferences, false) + +} diff --git a/core/core_strings/.gitignore b/core/core_strings/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/core/core_strings/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core/core_strings/build.gradle.kts b/core/core_strings/build.gradle.kts new file mode 100644 index 0000000..6b554b4 --- /dev/null +++ b/core/core_strings/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG) +} diff --git a/core/core_strings/src/main/AndroidManifest.xml b/core/core_strings/src/main/AndroidManifest.xml new file mode 100644 index 0000000..321a923 --- /dev/null +++ b/core/core_strings/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/core/core_ui/build.gradle.kts b/core/core_ui/build.gradle.kts new file mode 100644 index 0000000..6b554b4 --- /dev/null +++ b/core/core_ui/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG) +} diff --git a/core/core_ui/src/main/AndroidManifest.xml b/core/core_ui/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3381bd9 --- /dev/null +++ b/core/core_ui/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/core/core_utils/.gitignore b/core/core_utils/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/core/core_utils/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core/core_utils/build.gradle.kts b/core/core_utils/build.gradle.kts new file mode 100644 index 0000000..6b554b4 --- /dev/null +++ b/core/core_utils/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG) +} diff --git a/core/core_utils/src/main/AndroidManifest.xml b/core/core_utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4d6d64c --- /dev/null +++ b/core/core_utils/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/features/feature_login/.gitignore b/features/feature_login/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/features/feature_login/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/feature_login/build.gradle.kts b/features/feature_login/build.gradle.kts new file mode 100644 index 0000000..a8d397e --- /dev/null +++ b/features/feature_login/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG) +} + +dependencies { + dagger() + mvi() +} diff --git a/features/feature_login/src/main/AndroidManifest.xml b/features/feature_login/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e338bcd --- /dev/null +++ b/features/feature_login/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/LoginDeps.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/LoginDeps.kt new file mode 100644 index 0000000..b966d88 --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/LoginDeps.kt @@ -0,0 +1,7 @@ +package ru.touchin.mvi_test.feature_login + +import ru.touchin.mvi_test.feature_login.navigation.LoginCoordinator + +interface LoginDeps { + fun loginCoordinator(): LoginCoordinator +} diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/di/LoginComponent.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/di/LoginComponent.kt new file mode 100644 index 0000000..083be7e --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/di/LoginComponent.kt @@ -0,0 +1,19 @@ +package ru.touchin.mvi_test.feature_login.di + +import dagger.Component +import ru.touchin.roboswag.navigation_base.scopes.FragmentScope +import ru.touchin.mvi_test.feature_login.LoginDeps +import ru.touchin.mvi_test.feature_login.presentation.LoginFragment + +@FragmentScope +@Component(modules = [ViewModelModule::class], dependencies = [LoginDeps::class]) +interface LoginComponent { + + fun inject(fragment: LoginFragment) + + @Component.Factory + interface Factory { + fun create(deps: LoginDeps): LoginComponent + } + +} diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/di/ViewModelModule.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/di/ViewModelModule.kt new file mode 100644 index 0000000..e98b233 --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/di/ViewModelModule.kt @@ -0,0 +1,24 @@ +package ru.touchin.mvi_test.feature_login.di + +import androidx.lifecycle.ViewModel +import com.squareup.inject.assisted.dagger2.AssistedModule +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import ru.touchin.mvi_test.feature_login.presentation.LoginViewModel +import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory +import ru.touchin.roboswag.mvi_arch.di.ViewModelKey + +@Module(includes = [ViewModelAssistedFactoriesModule::class]) +interface ViewModelModule { + + @Binds + @IntoMap + @ViewModelKey(LoginViewModel::class) + fun bindLoginByPinFactory(factory: LoginViewModel.Factory): ViewModelAssistedFactory + +} + +@AssistedModule +@Module(includes = [AssistedInject_ViewModelAssistedFactoriesModule::class]) +abstract class ViewModelAssistedFactoriesModule diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/navigation/LoginCoordinator.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/navigation/LoginCoordinator.kt new file mode 100644 index 0000000..8cc33d0 --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/navigation/LoginCoordinator.kt @@ -0,0 +1,8 @@ +package ru.touchin.mvi_test.feature_login.navigation + +import ru.touchin.roboswag.navigation_base.scopes.FragmentScope + +@FragmentScope +interface LoginCoordinator { + fun openMainScreen() +} diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginFragment.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginFragment.kt new file mode 100644 index 0000000..a554eff --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginFragment.kt @@ -0,0 +1,43 @@ +package ru.touchin.mvi_test.feature_login.presentation + +import android.os.Bundle +import android.view.View +import me.vponomarenko.injectionmanager.IHasComponent +import me.vponomarenko.injectionmanager.x.XInjectionManager +import ru.touchin.mvi_test.feature_login.R +import ru.touchin.mvi_test.feature_login.databinding.FragmentLoginBinding +import ru.touchin.mvi_test.feature_login.di.DaggerLoginComponent +import ru.touchin.mvi_test.feature_login.di.LoginComponent +import ru.touchin.roboswag.mvi_arch.core.MviFragment +import ru.touchin.roboswag.navigation_base.fragments.EmptyState +import ru.touchin.roboswag.navigation_base.fragments.viewBinding + +class LoginFragment : MviFragment(R.layout.fragment_login), + IHasComponent { + + private val binding by viewBinding(FragmentLoginBinding::bind) + + override val viewModel: LoginViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + injectDependencies() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.goToMainScreenButton.dispatchActionOnRippleClick(LoginViewAction.GoToMainScreenButtonClicked) + } + + private fun injectDependencies() { + XInjectionManager.bindComponent(this) + .inject(this) + } + + override fun getComponent(): LoginComponent = DaggerLoginComponent + .factory() + .create(XInjectionManager.findComponent()) + +} diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewAction.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewAction.kt new file mode 100644 index 0000000..e2452c6 --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewAction.kt @@ -0,0 +1,7 @@ +package ru.touchin.mvi_test.feature_login.presentation + +import ru.touchin.roboswag.mvi_arch.marker.ViewAction + +sealed class LoginViewAction : ViewAction { + object GoToMainScreenButtonClicked : LoginViewAction() +} diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewModel.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewModel.kt new file mode 100644 index 0000000..ce76e55 --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewModel.kt @@ -0,0 +1,25 @@ +package ru.touchin.mvi_test.feature_login.presentation + +import androidx.lifecycle.SavedStateHandle +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import ru.touchin.mvi_test.feature_login.navigation.LoginCoordinator +import ru.touchin.roboswag.mvi_arch.core.MviViewModel +import ru.touchin.roboswag.mvi_arch.di.ViewModelAssistedFactory +import ru.touchin.roboswag.navigation_base.fragments.EmptyState + +class LoginViewModel @AssistedInject constructor( + @Assisted arg0: SavedStateHandle, + private val coordinator: LoginCoordinator +) : MviViewModel(LoginViewState, arg0) { + + override fun dispatchAction(action: LoginViewAction) { + when (action) { + LoginViewAction.GoToMainScreenButtonClicked -> coordinator.openMainScreen() + } + } + + @AssistedInject.Factory + interface Factory : ViewModelAssistedFactory + +} diff --git a/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewState.kt b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewState.kt new file mode 100644 index 0000000..69c12a2 --- /dev/null +++ b/features/feature_login/src/main/java/ru/touchin/mvi_test/feature_login/presentation/LoginViewState.kt @@ -0,0 +1,5 @@ +package ru.touchin.mvi_test.feature_login.presentation + +import ru.touchin.roboswag.mvi_arch.marker.ViewState + +object LoginViewState : ViewState diff --git a/features/feature_login/src/main/res/layout/fragment_login.xml b/features/feature_login/src/main/res/layout/fragment_login.xml new file mode 100644 index 0000000..e566a1a --- /dev/null +++ b/features/feature_login/src/main/res/layout/fragment_login.xml @@ -0,0 +1,22 @@ + + + + + +