feature TI-186: [Android] Настройка проекта

feature [TI-186](https://jira.touchin.ru/browse/TI-186): [Android] Настройка проекта
This commit is contained in:
Evgeny Dubravin 2024-04-15 21:37:53 +03:00
parent a2cad6cba2
commit fabb8564c5
102 changed files with 958 additions and 1264 deletions

5
.gitignore vendored
View File

@ -19,3 +19,8 @@ local.properties
*.iml
app/src/main/res/*/*strings.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties

10
.gitmodules vendored
View File

@ -1,9 +1,9 @@
[submodule "Template-common"]
path = Template-common
url = git@github.com:TouchInstinct/Template-common.git
[submodule "RoboSwag"]
path = RoboSwag
url = git@github.com:TouchInstinct/RoboSwag.git
url = https://git.svc.touchin.ru/TouchInstinct/RoboSwag.git
[submodule "BuildScripts"]
path = BuildScripts
url = git@github.com:TouchInstinct/BuildScripts.git
url = https://git.svc.touchin.ru/TouchInstinct/BuildScripts.git
[submodule "common-template"]
path = common-template
url = https://git.svc.touchin.ru/TouchInstinct/common-template.git

2
app/.gitignore vendored
View File

@ -1 +1,3 @@
/build
app/src/main/res/*/*strings.xml

View File

@ -1,74 +1,95 @@
plugins {
id(Plugins.ANDROID_APP_PLUGIN_WITH_DEFAULT_CONFIG)
id(Plugins.FIREBASE_CRASH)
id(Plugins.GOOGLE_SERVICES)
id(Plugins.LICENCE_PLUGIN)
id(libs.plugins.android.app.get().pluginId)
alias(libs.plugins.firebase.crashlytics)
alias(libs.plugins.firebase.perf)
id(libs.plugins.google.oss.licenses.plugin.get().pluginId)
}
val customEndpoint: String? = Environment.ENDPOINT.getenv()?.takeIf(String::isNotBlank)
android {
namespace = "ru.touchin.template"
configureSigningConfig(this@Build_gradle::file)
with(defaultConfig) {
applicationId = Environment.APP_ID.getenv() ?: AndroidConfig.TEST_APP_ID
signingConfig = signingConfigs.getByName(SigningConfig.CONFIG_NAME)
addResourceConfigurations("ru")
}
firebaseCrashlytics {
mappingFileUploadEnabled = true
}
addBuildType(type = BuildType.Develop, project = rootProject)
addBuildType(type = BuildType.Debug, project = rootProject)
addBuildType(type = BuildType.Customer, project = rootProject)
addBuildType(type = BuildType.Release, project = rootProject)
addBuildType(BuildType.Debug, buildScriptDir = buildScriptDir)
addBuildType(BuildType.Release, buildScriptDir = buildScriptDir)
addMobileServicesFlavor()
flavorDimensions(
ApiFlavour.DIMENSION_NAME,
SSLPinningFlavour.DIMENSION_NAME,
TestPanelFlavour.DIMENSION_NAME
)
addFlavour(flavour = ApiFlavour.CustomerStage, customEndpoint = customEndpoint)
addFlavour(flavour = ApiFlavour.CustomerProd, customEndpoint = customEndpoint)
addFlavour(SSLPinningFlavour.OFF)
addFlavour(SSLPinningFlavour.ON)
addEmptyFlavour(TestPanelFlavour.OFF)
addEmptyFlavour(TestPanelFlavour.ON)
ignoreCustomerProdFlavourIfReleaseIsDebuggable()
}
androidExtensions {
features = setOf("parcelize")
ext["languageMap"] = mapOf("ru" to "${rootProject.projectDir}/${AndroidConfig.COMMON_FOLDER}/strings/default_common_strings.json")
}
dependencies {
androidX()
featureModules()
mvi()
materialDesign()
dagger()
retrofit()
moshi()
navigation()
leakCanary()
sharedPrefs()
chucker()
implementation(Library.FIREBASE_ANAL)
implementation(Library.FIREBASE_CRASH)
implementation(Library.FIREBASE_PERF)
implementation(Library.ANDROIDX_SECURE)
coreNetwork()
coreStrings()
implementationModule(Module.Core.UI)
implementationModule(Module.Core.UTILS)
implementationModule(Module.Core.DATA)
implementationModule(Module.RoboSwag.UTILS)
// AndroidX
implementation(libs.bundles.androidX)
// KotlinX
implementation(libs.coroutines)
// UI
implementation(libs.bundles.ui)
// Lifecycle
implementation(libs.bundles.lifecycle)
kapt(libs.androidx.lifecycle.compiler)
// Dagger
implementation(libs.bundles.dagger)
kapt(libs.dagger.compiler)
kapt(libs.dagger.assisted.inject.processor)
// Glide
implementation(libs.glide)
implementation(libs.glide.okhttp3)
kapt(libs.glide.compiler)
// Retrofit2, OkHttp3
implementation(libs.retrofit)
implementation(libs.retrofit.converter.moshi)
implementation(libs.okhttp)
implementation(libs.okhttp.logging.interceptor)
// Moshi
implementation(libs.moshi)
implementation(libs.moshi.kotlin)
kapt(libs.moshi.codegen)
// Room
implementation(libs.room)
implementation(libs.room.ktx)
kapt(libs.room.compiler)
// LeakCanary
implementation(libs.leakcanary)
// Chucker
debugImplementation(libs.chucker.debug)
releaseImplementation(libs.chucker.release)
// GMS
implementation(platform(libs.firebase.bom))
implementation(libs.bundles.firebase)
implementation(libs.google.oss.licenses)
// Security
implementation(libs.androidx.security.crypto)
// Biometric
implementation(libs.androidx.biometric)
// Groupie
implementation(libs.groupie)
implementation(libs.groupie.viewbinding)
}
apply(from = "$buildScriptDir/gradle/scripts/applicationFileNaming.gradle")
apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/scripts/stringGenerator.gradle")
val Project.buildScriptDir: String
get() = rootProject.ext["buildScriptsDir"] as String

View File

@ -1,40 +0,0 @@
{
"project_info": {
"project_number": "1084813714260",
"firebase_url": "https://testproject-ac7fe.firebaseio.com",
"project_id": "testproject-ac7fe",
"storage_bucket": "testproject-ac7fe.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1084813714260:android:b6d7bb18a0acfe96255ec1",
"android_client_info": {
"package_name": "com.touchin.template"
}
},
"oauth_client": [
{
"client_id": "1084813714260-ijq13dkdc1h5i5j87t45tiibl8eg2v9e.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBVsh_CN-RCfU3LkHuvhLdqVS-ZUJbOljE"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "1084813714260-ijq13dkdc1h5i5j87t45tiibl8eg2v9e.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@ -1,7 +1,6 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="ru.touchin.template">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
@ -9,7 +8,7 @@
android:name="ru.touchin.template.App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/common_global_app_name"
android:label="@string/common_app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
tools:ignore="GoogleAppIndexingWarning">
@ -18,8 +17,8 @@
android:name="ru.touchin.template.SingleActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View File

@ -1,25 +1,5 @@
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.di.ApplicationComponent
import ru.touchin.template.di.DaggerApplicationComponent
import android.app.Application
class App : TouchinApp(), IHasComponent<ApplicationComponent> {
override fun onCreate() {
super.onCreate()
initDagger()
}
fun initDagger() {
XInjectionManager.init(this)
XInjectionManager.bindComponent(this)
}
override fun getComponent(): ApplicationComponent = DaggerApplicationComponent
.factory()
.create(this)
}
class App : Application()

View File

@ -1,62 +1,5 @@
package ru.touchin.template
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import me.vponomarenko.injectionmanager.x.XInjectionManager
import ru.terrakok.cicerone.NavigatorHolder
import ru.terrakok.cicerone.android.support.SupportAppNavigator
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.StartUpCoordinator
import javax.inject.Inject
import androidx.appcompat.app.AppCompatActivity
// TDOD: change package name everywhere
// TODO: change google play config
class SingleActivity : BaseActivity() {
@Inject
@MainNavigation
lateinit var navigatorHolder: NavigatorHolder
@Inject
lateinit var coordinator: StartUpCoordinator
private lateinit var firebaseAnalytics: FirebaseAnalytics
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
firebaseAnalytics = Firebase.analytics
setContentView(R.layout.activity_main)
injectDependencies()
lifecycle.addObserver(
CiceroneTuner(
navigatorHolder = navigatorHolder,
navigator = SupportAppNavigator(this, R.id.fragment_container)
)
)
coordinator.start()
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
coordinator.closeCurrentScreen()
}
})
}
private fun injectDependencies() {
XInjectionManager
.findComponent<ApplicationComponent>()
.inject(this)
}
}
class SingleActivity : AppCompatActivity()

View File

@ -1,37 +0,0 @@
package ru.touchin.template.di
import android.content.Context
import dagger.BindsInstance
import dagger.Component
import ru.terrakok.cicerone.Router
import ru.touchin.template.feature_login.LoginDeps
import ru.touchin.template.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 = [
ApplicationModule::class,
PreferencesModule::class,
MainNavigationModule::class,
NetworkModule::class,
CoordinatorsImpl::class
])
interface ApplicationComponent : LoginDeps {
@MainNavigation
fun router(): Router
fun inject(application: App)
fun inject(activity: SingleActivity)
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): ApplicationComponent
}
}

View File

@ -1,28 +0,0 @@
package ru.touchin.template.di
import android.content.Context
import com.chuckerteam.chucker.api.ChuckerInterceptor
import dagger.Module
import dagger.Provides
import okhttp3.Interceptor
import ru.touchin.template.network.di.ApiUrl
import ru.touchin.template.network.di.ChuckInterceptor
import ru.touchin.template.network.di.WithSslPinning
import ru.touchin.template.BuildConfig
@Module
class ApplicationModule {
@Provides
@ApiUrl
fun provideApiUrl() = BuildConfig.API_URL
@Provides
@WithSslPinning
fun providePluggerForSsl() = BuildConfig.WithSSLPinning
@Provides
@ChuckInterceptor
fun provideChucker(context: Context): Interceptor = ChuckerInterceptor(context)
}

View File

@ -1,13 +0,0 @@
package ru.touchin.template.di
import dagger.Binds
import dagger.Module
import ru.touchin.template.feature_login.navigation.LoginCoordinator
import ru.touchin.template.navigation.login.LoginCoordinatorImpl
@Module
abstract class CoordinatorsImpl {
@Binds
abstract fun loginCoordinator(impl: LoginCoordinatorImpl): LoginCoordinator
}

View File

@ -1,27 +0,0 @@
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<Router> = Cicerone.create()
@Provides
@MainNavigation
fun provideNavigatorHolder(@MainNavigation cicerone: Cicerone<Router>): NavigatorHolder = cicerone.navigatorHolder
@Provides
@MainNavigation
fun provideRouter(@MainNavigation cicerone: Cicerone<Router>): Router = cicerone.router
}

View File

@ -1,6 +0,0 @@
package ru.touchin.template.navigation
import javax.inject.Qualifier
@Qualifier
annotation class MainNavigation

View File

@ -1,13 +0,0 @@
package ru.touchin.template.navigation
import androidx.fragment.app.Fragment
import ru.terrakok.cicerone.android.support.SupportAppScreen
import ru.touchin.template.feature_login.presentation.LoginFragment
object Screens {
class Login : SupportAppScreen() {
override fun getFragment(): Fragment = LoginFragment()
}
}

View File

@ -1,18 +0,0 @@
package ru.touchin.template.navigation
import ru.terrakok.cicerone.Router
import javax.inject.Inject
class StartUpCoordinator @Inject constructor(
@MainNavigation private val router: Router
) {
fun start() {
router.newRootScreen(Screens.Login())
}
fun closeCurrentScreen() {
router.exit()
}
}

View File

@ -1,16 +0,0 @@
package ru.touchin.template.navigation.login
import ru.terrakok.cicerone.Router
import ru.touchin.template.feature_login.navigation.LoginCoordinator
import ru.touchin.template.navigation.MainNavigation
import javax.inject.Inject
class LoginCoordinatorImpl @Inject constructor(
@MainNavigation private val router: Router
) : LoginCoordinator {
override fun openMainScreen() {
router.exit()
}
}

View File

@ -1,3 +0,0 @@
<resources>
<string name="common_global_app_name">Template</string>
</resources>

View File

@ -1,43 +1,16 @@
buildscript {
repositories {
mavenCentral()
google()
jcenter()
mavenCentral()
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.gms:google-services:${Version.GOOGLE_SERVICES_PLUGIN}")
classpath("com.google.firebase:firebase-crashlytics-gradle:${Version.FIREBASE_CRASH_PLUGIN}")
classpath("com.vanniktech:gradle-dependency-graph-generator-plugin:0.5.0")
classpath("com.google.android.gms:oss-licenses-plugin:0.10.2")
classpath(libs.android.gradle.plugin)
classpath(libs.kotlin.gradle.plugin)
classpath(libs.google.oss.licenses.plugin)
}
}
plugins {
id(Plugins.DEPENDENCY_GRAPH).version("0.5.0")
id("static-analysis-android")
}
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)
staticAnalysis {
buildScriptDir = buildScriptsDir
}

View File

@ -1,14 +1,11 @@
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()
mavenCentral()
google()
maven {
url = uri("https://plugins.gradle.org/m2/")
@ -16,18 +13,7 @@ repositories {
}
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"))
implementation(libs.android.gradle.plugin)
implementation(libs.kotlin.gradle.plugin)
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}

View File

@ -0,0 +1,10 @@
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@ -1,29 +1,14 @@
import com.android.build.gradle.BaseExtension
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 = Environment.BUILD_NUMBER.getenv()?.toIntOrNull() ?: 10000
const val VERSION_NAME = "1.0.0"
// TODO: change test package name
const val TEST_APP_ID = "com.touchin.template"
const val TEST_APP_ID = "ru.touchin.template"
// TODO: change common file folder
const val COMMON_FOLDER = "Template-common"
const val RELEASE_DEBUGGABLE = false
}
fun BaseExtension.ignoreCustomerProdFlavourIfReleaseIsDebuggable() {
variantFilter {
ignore = name.contains(ApiFlavour.CustomerProd.name, ignoreCase = true) && AndroidConfig.RELEASE_DEBUGGABLE
}
const val COMMON_FOLDER = "common-template"
}

View File

@ -0,0 +1,135 @@
import com.android.build.gradle.BaseExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.kotlin.dsl.extra
import versioncatalog.versionName
fun BaseExtension.addFlavors(dimensionName: String, vararg flavorNames: String) {
if (flavorNames.isEmpty()) return
flavorDimensions(dimensionName)
flavorNames.forEach { flavor ->
productFlavors {
create(flavor) {
dimension = dimensionName
}
}
}
}
fun BaseExtension.addMobileServicesFlavor() {
addFlavors(dimensionName = "mobileServices", flavorNames = arrayOf("huawei", "google"))
}
fun BaseExtension.addBuildType(
type: BuildType,
project: Project,
) {
buildTypes {
maybeCreate(type.name)
getByName(type.name) {
isDebuggable = !type.optimizeAndObfuscate
isMinifyEnabled = type.optimizeAndObfuscate
isShrinkResources = type.optimizeAndObfuscate
setMatchingFallbacks(type.matchingFallbacks)
if (listOf(BuildType.Develop, BuildType.Debug).contains(type)) {
applicationIdSuffix = ".${type.name}"
}
if (type.optimizeAndObfuscate) {
setProguardFiles(
listOf(
project.file("proguard").listFiles(),
getDefaultProguardFile("proguard-android-optimize.txt")
).filterNotNull()
)
}
extra.set("enableCrashlytics", type.enableCrashlytics)
buildConfigField("Boolean", "ENABLE_SSL_PINNING", type.enableSslPinning.toString())
buildConfigField("Boolean", "ENABLE_LOGS", type.enabledLogs.toString())
buildConfigField("Boolean", "ENABLE_DEBUG_PANEL", type.enabledDebugPanel.toString())
}
}
}
fun BaseExtension.addLibBuildType(
type: BuildType,
serverType: String? = null,
enableConfig: Boolean = false,
versionCatalog: VersionCatalog
) {
buildTypes {
maybeCreate(type.name)
getByName(type.name) {
isDebuggable = !type.optimizeAndObfuscate
isMinifyEnabled = type.optimizeAndObfuscate
setMatchingFallbacks(type.matchingFallbacks)
buildConfigField("String", "VERSION_NAME", "\"${versionCatalog.versionName}\"")
if (enableConfig) {
val server = serverType ?: type.serverType
buildConfigField("ru.template.data.network.ServerUrl", "DEFAULT_SERVER", type.defaultServer)
buildConfigField("String", "DEFAULT_SERVER_TYPE", "\"$server\"")
buildConfigField("Boolean", "ENABLE_SSL_PINNING", type.enableSslPinning.toString())
buildConfigField("Boolean", "ENABLE_LOGS", type.enabledLogs.toString())
}
}
}
}
sealed class BuildType(
val name: String,
val optimizeAndObfuscate: Boolean,
val enableSslPinning: Boolean,
val enabledLogs: Boolean,
val enabledDebugPanel: Boolean,
val enableCrashlytics: Boolean = true,
val defaultServer: String = "ru.template.data.network.ServerUrl.CUSTOMER_TEST",
val serverType: String,
val matchingFallbacks: String = "debug",
) {
object Develop : BuildType(
name = "develop",
optimizeAndObfuscate = false,
enableSslPinning = false,
enabledLogs = true,
enabledDebugPanel = true,
enableCrashlytics = false,
serverType = "Test",
)
object Debug : BuildType(
name = "debug",
optimizeAndObfuscate = false,
enableSslPinning = false,
enabledLogs = true,
enabledDebugPanel = true,
serverType = "Test",
)
object Customer : BuildType(
name = "customer",
optimizeAndObfuscate = true,
enableSslPinning = true,
enabledLogs = false,
enabledDebugPanel = false,
defaultServer = "ru.template.data.network.ServerUrl.CUSTOMER_PROD",
serverType = "Prod",
matchingFallbacks = "release"
)
object Release : BuildType(
name = "release",
optimizeAndObfuscate = true,
enableSslPinning = true,
enabledLogs = false,
enabledDebugPanel = false,
defaultServer = "ru.template.data.network.ServerUrl.CUSTOMER_PROD",
serverType = "Prod",
matchingFallbacks = "release"
)
}

View File

@ -1,145 +0,0 @@
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_TO_REFRESH)
}
fun DependencyHandler.permissionDispatcher() {
implementation(Library.PERMISSION_DISPATCHER)
kapt(Library.PERMISSION_DISPATCHER_ANNOTATION_PROCESSOR)
}
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.coreStrings() {
implementationModule(Module.Core.STRINGS)
}
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 <T> uncheckedCast(obj: Any?): T = obj as T

View File

@ -1,13 +1,12 @@
object Environment {
const val APP_ID = "BUNDLE_ID"
const val STORE_PASSWORD = "STORE_PASSWORD"
const val KEY_ALIAS = "KEY_ALIAS"
const val ALIAS = "ALIAS"
const val KEY_PASSWORD = "KEY_PASSWORD"
const val ENDPOINT = "CUSTOM_ENDPOINT"
const val BUILD_NUMBER = "BUILD_NUMBER"
const val SERVER_ENVIRONMENT = "SERVER_ENVIRONMENT"
const val BUILD_TYPE = "BUILD_TYPE"
}
fun String.getenv(): String? = System.getenv(this)

View File

@ -1,60 +0,0 @@
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_TO_REFRESH = "androidx.swiperefreshlayout:swiperefreshlayout:${Version.SWIPE_TO_REFRESH}"
const val PERMISSION_DISPATCHER = "org.permissionsdispatcher:permissionsdispatcher:${Version.PERMISSION_DISPATCHER}"
const val PERMISSION_DISPATCHER_ANNOTATION_PROCESSOR = "org.permissionsdispatcher:permissionsdispatcher-processor:${Version.PERMISSION_DISPATCHER}"
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}"
const val FIREBASE_ANAL = "com.google.firebase:firebase-analytics-ktx:${Version.FIREBASE_ANAL}"
const val FIREBASE_PERF = "com.google.firebase:firebase-perf:${Version.FIREBASE_PERF}"
const val FIREBASE_CRASH = "com.google.firebase:firebase-crashlytics:${Version.FIREBASE_CRASH}"
const val ANDROIDX_SECURE = "androidx.security:security-crypto:${Version.ANDROIDX_SECURE}"
const val ANDROIDX_BIOMETRIC = "androidx.biometric:biometric:${Version.ANDROIDX_BIOMETRIC}"
const val LICENCE_LIBRARY = "com.google.android.gms:play-services-oss-licenses:${Version.LICENCE_LIBRARY}"
}

View File

@ -1,35 +0,0 @@
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 VIEWS = "views"
const val RECYCLER_VIEW_ADAPTERS = "recyclerview-adapters"
const val RECYCLER_VIEW_DECORATORS = "recyclerview-decorators"
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"
const val DATA = "core_data"
const val DOMAIN = "core_domain"
}
}

View File

@ -1,20 +0,0 @@
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 LICENCE_PLUGIN = "com.google.android.gms.oss-licenses-plugin"
const val GOOGLE_SERVICES = "com.google.gms.google-services"
const val FIREBASE_CRASH = "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"
}

View File

@ -1,47 +0,0 @@
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 ANDROIDX_SECURE = "1.0.0-rc02"
const val ANDROIDX_BIOMETRIC = "1.0.1"
const val ANDROID_MATERIAL = "1.2.0-rc01"
const val SWIPE_TO_REFRESH = "1.0.0"
const val ANDROID_LIFECYCLE = "2.2.0"
const val PERMISSION_DISPATCHER = "4.8.0"
const val DAGGER = "2.27"
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.7"
const val CICERONE = "5.1.0"
const val FIREBASE_ANAL = "17.4.3"
const val FIREBASE_CRASH = "17.1.0"
const val FIREBASE_PERF = "19.0.7"
const val GOOGLE_SERVICES_PLUGIN = "4.3.3"
const val FIREBASE_CRASH_PLUGIN = "2.2.0"
const val LEAK_CANARY = "2.4"
const val CHUCKER = "3.2.0"
const val LICENCE_LIBRARY = "17.0.0"
}

View File

@ -1,41 +0,0 @@
import com.android.build.gradle.BaseExtension
fun BaseExtension.addBuildType(
type: BuildType,
buildScriptDir: String
) {
buildTypes {
getByName(type.name) {
isMinifyEnabled = type.optimizeAndObfuscate
isShrinkResources = type.optimizeAndObfuscate
if (type.optimizeAndObfuscate) {
val proguardFile = if (AndroidConfig.RELEASE_DEBUGGABLE) "noObfuscate.pro" else "obfuscate.pro"
setProguardFiles(listOfNotNull(
getDefaultProguardFile("proguard-android-optimize.txt"),
"$buildScriptDir/proguard/$proguardFile",
"proguard/projectConfig.pro"
))
}
}
}
}
sealed class BuildType(
val name: String,
val optimizeAndObfuscate: Boolean
) {
object Debug : BuildType(
name = "debug",
optimizeAndObfuscate = false
)
object Release : BuildType(
name = "release",
optimizeAndObfuscate = true
)
}

View File

@ -11,7 +11,7 @@ sealed class SSLPinningFlavour(
object OFF : SSLPinningFlavour(
name = "withoutSSLPinning",
withSslPinning = true
withSslPinning = false
)
object ON : SSLPinningFlavour(

View File

@ -15,7 +15,7 @@ fun BaseExtension.configureSigningConfig(getRelativeFile: (String) -> File) {
create(SigningConfig.CONFIG_NAME) {
storeFile = getRelativeFile(SigningConfig.PATH_TO_KEYSTORE_FILE)
storePassword = Environment.STORE_PASSWORD.getenv() ?: SigningConfig.DEFAULT_STORE_PASSWORD
keyAlias = Environment.KEY_ALIAS.getenv() ?: SigningConfig.DEFAULT_KEY_ALIAS
keyAlias = Environment.ALIAS.getenv() ?: SigningConfig.DEFAULT_KEY_ALIAS
keyPassword = Environment.KEY_PASSWORD.getenv() ?: SigningConfig.DEFAULT_KEY_PASSWORD
}
}

View File

@ -1,12 +1,12 @@
package plugins
import Plugins
import versioncatalog.androidApplicationPlugin
import org.gradle.api.Project
class AndroidAppPlugin : BaseAndroidPlugin() {
override fun apply(target: Project) {
target.plugins.apply(Plugins.ANDROID_APPLICATION)
target.plugins.apply(target.libs.androidApplicationPlugin)
super.apply(target)
}

View File

@ -1,12 +1,12 @@
package plugins
import Plugins
import org.gradle.api.Project
import versioncatalog.androidLibraryPlugin
class AndroidLibPlugin : BaseAndroidPlugin() {
override fun apply(target: Project) {
target.plugins.apply(Plugins.ANDROID_LIBRARY)
target.plugins.apply(target.libs.androidLibraryPlugin)
super.apply(target)
}

View File

@ -1,18 +1,24 @@
package plugins
import AndroidConfig
import BuildType
import Plugins
import com.android.build.gradle.BaseExtension
import kotlinStd
import versioncatalog.jvmBytecode
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.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
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
import versioncatalog.compileSdk
import versioncatalog.kotlinAndroidPlugin
import versioncatalog.kotlinKaptPlugin
import versioncatalog.minSdk
import versioncatalog.targetSdk
import versioncatalog.versionCode
import versioncatalog.versionName
abstract class BaseAndroidPlugin : Plugin<Project> {
override fun apply(target: Project) {
@ -22,51 +28,46 @@ abstract class BaseAndroidPlugin : Plugin<Project> {
}
private fun Project.configurePlugins() {
plugins.apply(Plugins.KOTLIN_ANDROID)
plugins.apply(Plugins.KOTLIN_ANDROID_EXTENSIONS)
plugins.apply(Plugins.KOTLIN_KAPT)
plugins.apply(libs.kotlinAndroidPlugin)
plugins.apply(libs.kotlinKaptPlugin)
}
private fun Project.configureAndroid() = extensions.getByType<BaseExtension>().run {
compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION)
buildToolsVersion = AndroidConfig.BUILD_TOOLS_VERSION
compileSdkVersion(libs.compileSdk.toInt())
defaultConfig {
minSdkVersion(AndroidConfig.MIN_SDK_VERSION)
targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION)
versionCode = AndroidConfig.VERSION_CODE
versionName = AndroidConfig.VERSION_NAME
minSdk = libs.minSdk.toInt()
targetSdk = libs.targetSdk.toInt()
versionCode = libs.versionCode.toInt()
versionName = libs.versionName
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.toVersion(libs.jvmBytecode)
targetCompatibility = JavaVersion.toVersion(libs.jvmBytecode)
isCoreLibraryDesugaringEnabled = true
}
buildFeatures.viewBinding = true
buildFeatures.apply {
buildConfig = true
viewBinding = true
}
tasks.withType(KotlinCompile::class.java) {
kotlinOptions {
jvmTarget = "1.8"
}
}
if (AndroidConfig.RELEASE_DEBUGGABLE) {
buildTypes {
getByName(BuildType.Release.name) {
isDebuggable = true
}
jvmTarget = libs.jvmBytecode
}
}
}
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")
add("coreLibraryDesugaring", "com.android.tools:desugar_jdk_libs:2.0.4")
}
private fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? =
add("implementation", dependencyNotation)
}
internal val Project.libs: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")

View File

@ -0,0 +1,6 @@
package versioncatalog
import org.gradle.api.artifacts.VersionCatalog
private fun VersionCatalog.getLibrary(library: String) = findLibrary(library).get()

View File

@ -0,0 +1,15 @@
package versioncatalog
import org.gradle.api.artifacts.VersionCatalog
val VersionCatalog.androidApplicationPlugin: String
get() = findPlugin("android-application").get().orNull?.pluginId.toString()
val VersionCatalog.androidLibraryPlugin: String
get() = findPlugin("android-library").get().orNull?.pluginId.toString()
val VersionCatalog.kotlinAndroidPlugin: String
get() = findPlugin("kotlin-android").get().orNull?.pluginId.toString()
val VersionCatalog.kotlinKaptPlugin: String
get() = findPlugin("kotlin-kapt").get().orNull?.pluginId.toString()

View File

@ -0,0 +1,21 @@
package versioncatalog
import org.gradle.api.artifacts.VersionCatalog
val VersionCatalog.compileSdk: String
get() = findVersion("compileSdk").get().requiredVersion
val VersionCatalog.minSdk: String
get() = findVersion("minSdk").get().requiredVersion
val VersionCatalog.targetSdk: String
get() = findVersion("targetSdk").get().requiredVersion
val VersionCatalog.jvmBytecode: String
get() = findVersion("jvmBytecode").get().requiredVersion
val VersionCatalog.versionCode: String
get() = findVersion("versionCode").get().requiredVersion
val VersionCatalog.versionName: String
get() = findVersion("versionName").get().requiredVersion

View File

@ -1,3 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}

View File

@ -1 +0,0 @@
<manifest package="ru.touchin.template.core_data" />

View File

@ -1,3 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}

View File

@ -1 +0,0 @@
<manifest package="ru.touchin.template.core_domain" />

View File

@ -1 +0,0 @@
/build

View File

@ -1,20 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
// id("api-generator-android")
}
// TODO: uncomment api generator
//apiGenerator {
// pathToApiSchemes = "${AndroidConfig.COMMON_FOLDER}/api"
// outputPackageName = AndroidConfig.TEST_APP_ID
// outputLanguage = apigen.OutputLanguage.KotlinAndroid(
// methodOutputType = apigen.MethodOutputType.Coroutine
// )
//}
dependencies {
retrofit()
dagger()
moshi()
coroutines()
}

View File

@ -1 +0,0 @@
<manifest package="ru.touchin.template.core_network" />

View File

@ -1,10 +0,0 @@
package ru.touchin.template.network
import okhttp3.ResponseBody
import java.nio.charset.Charset
fun ResponseBody.cloneBody(): String? = source()
.also { it.request(Long.MAX_VALUE) }
.buffer
?.clone()
?.readString(Charset.forName("UTF-8"))

View File

@ -1,6 +0,0 @@
package ru.touchin.template.network.di
import javax.inject.Qualifier
@Qualifier
annotation class ApiUrl

View File

@ -1,6 +0,0 @@
package ru.touchin.template.network.di
import javax.inject.Qualifier
@Qualifier
annotation class ChuckInterceptor

View File

@ -1,69 +0,0 @@
package ru.touchin.template.network.di
import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
import okhttp3.CertificatePinner
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import ru.touchin.template.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,
@ChuckInterceptor chuckerInterceptor: Interceptor,
@WithSslPinning withSslPinning: Boolean
): OkHttpClient =
buildPublicClient(exceptionsInterceptor, chuckerInterceptor, withSslPinning)
@Singleton
@Provides
fun provideMoshi() = buildMoshi()
@Singleton
@Provides
fun provideRetrofit(client: OkHttpClient, moshi: Moshi, @ApiUrl apiUrl: String) = buildRetrofitInstance(client, moshi, apiUrl)
private fun buildMoshi() = Moshi.Builder()
.build()
private fun buildRetrofitInstance(client: OkHttpClient, moshi: Moshi, apiUrl: String): Retrofit = Retrofit.Builder()
.baseUrl(apiUrl)
.client(client)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
private fun buildPublicClient(
exceptionsInterceptor: ExceptionsInterceptor,
chuckerInterceptor: Interceptor,
withSslPinning: Boolean
): OkHttpClient = OkHttpClient.Builder()
.apply {
connectTimeout(TIMEOUT, TimeUnit.SECONDS)
readTimeout(TIMEOUT, TimeUnit.SECONDS)
writeTimeout(TIMEOUT, TimeUnit.SECONDS)
addInterceptor(exceptionsInterceptor)
addInterceptor(chuckerInterceptor)
if (BuildConfig.DEBUG) {
addNetworkInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY })
}
if (withSslPinning) {
certificatePinner(CertificatePinner.DEFAULT)
}
}.build()
}

View File

@ -1,6 +0,0 @@
package ru.touchin.template.network.di
import javax.inject.Qualifier
@Qualifier
annotation class WithSslPinning

View File

@ -1,41 +0,0 @@
package ru.touchin.template.network.interceptor
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.ResponseBody
import org.json.JSONException
import org.json.JSONObject
import ru.touchin.template.network.cloneBody
import ru.touchin.template.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
}
}
}

View File

@ -1,18 +0,0 @@
package ru.touchin.template.network.models
import java.io.IOException
open class ServerException(val code: Int, message: String? = null) : IOException(message) {
companion object {
private val codeToErrorTypeMap = mapOf(
1 to TemplateApiError.VALID_RESPONSE,
2 to TemplateApiError.INVALID_PARAMETERS
)
fun getErrorTypeByCode(code: Int) = codeToErrorTypeMap[code]
}
fun getErrorType(): TemplateApiError? = codeToErrorTypeMap[code]
}

View File

@ -1,9 +0,0 @@
package ru.touchin.template.network.models
enum class TemplateApiError {
INVALID_PARAMETERS,
VALID_RESPONSE
}

View File

@ -1,3 +0,0 @@
<resources>
<string name="app_name">My Library</string>
</resources>

View File

@ -1 +0,0 @@
/build

View File

@ -1,8 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}
dependencies {
implementation(Library.DAGGER)
implementationModule(Module.RoboSwag.STORABLE)
}

View File

@ -1 +0,0 @@
<manifest package="ru.touchin.template.core_prefs" />

View File

@ -1,24 +0,0 @@
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 {
@Provides
@Singleton
fun provideDefaultSharedPreferences(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@Provides
@Singleton
fun provideTutorialStorable(sharedPreferences: SharedPreferences): NonNullStorable<String, Boolean, Boolean> = PreferenceUtils
.booleanStorable("TUTORIAL_STORABLE", sharedPreferences, false)
}

View File

@ -1 +0,0 @@
/build

View File

@ -1,15 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}
android {
ext["languageMap"] = mapOf("ru" to "${AndroidConfig.COMMON_FOLDER}/strings/default_common_strings_ru.json")
ext["rootPath"] = "core/core_strings"
}
//gradle.projectsEvaluated {
// tasks.named("preBuild") {
// dependsOn("stringGenerator")
// }
//}
//
//apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/stringGenerator.gradle")

View File

@ -1 +0,0 @@
<manifest package="ru.touchin.template" />

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="common_global_yes" formatted="false">Да</string>
</resources>

View File

@ -1,3 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}

View File

@ -1 +0,0 @@
<manifest package="ru.touchin.template.core_ui" />

View File

@ -1 +0,0 @@
/build

View File

@ -1,3 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}

View File

@ -1 +0,0 @@
<manifest package="ru.touchin.template.core_utils" />

1
data/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

48
data/build.gradle.kts Normal file
View File

@ -0,0 +1,48 @@
plugins {
id(libs.plugins.android.lib.get().pluginId)
}
private val serverType = Environment.SERVER_ENVIRONMENT.getenv()?.takeIf(String::isNotBlank)
private val versionCatalog: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")
android {
namespace = "ru.template.data"
addLibBuildType(type = BuildType.Develop, enableConfig = true, versionCatalog = versionCatalog)
addLibBuildType(type = BuildType.Debug, enableConfig = true, versionCatalog = versionCatalog)
addLibBuildType(type = BuildType.Customer, enableConfig = true, versionCatalog = versionCatalog)
addLibBuildType(type = BuildType.Release, enableConfig = true, versionCatalog = versionCatalog)
sourceSets {
getByName("main") {
java.srcDirs("src/main/kotlin")
}
getByName("androidTest") {
java.srcDirs("src/androidTest/kotlin")
}
getByName("test") {
java.srcDirs("src/test/kotlin")
}
}
addMobileServicesFlavor()
testOptions {
unitTests {
isReturnDefaultValues = true
}
}
}
dependencies {
implementation(project(":domain"))
implementation(project(":mobile_services"))
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
}
val Project.buildScriptDir: String
get() = rootProject.ext["buildScriptsDir"] as String

View File

@ -0,0 +1,24 @@
package ru.template.data
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("ru.template.data.test", appContext.packageName)
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,73 @@
import ru.template.data.network.sslpinning.ServerInfo
import ru.template.data.network.sslpinning.UrlInfo
enum class ServerUrl(
val title: String,
val serverType: String,
val serverInfo: ServerInfo
) {
CUSTOMER_PROD(
title = "customerProd",
serverType = "Prod",
ServerInfo(
UrlInfo(
protocol = "https",
url = "customerProd", // TODO change this url
),
pinsHashSet = hashSetOf()
)
),
CUSTOMER_STAGE(
title = "customerStage",
serverType = "Stage",
ServerInfo(
UrlInfo(
protocol = "https",
url = "customerStage", // TODO change this url
),
pinsHashSet = hashSetOf()
)
),
TOUCHIN_MOCK(
title = "touchinMock",
serverType = "Dev",
ServerInfo(
UrlInfo(
protocol = "https",
url = "touchinMock", // TODO change this url
),
pinsHashSet = hashSetOf()
)
),
CUSTOMER_TEST(
title = "customerTest",
serverType = "Test",
ServerInfo(
UrlInfo(
protocol = "https",
url = "customerTest", // TODO change this url
),
pinsHashSet = hashSetOf()
)
);
companion object {
fun fromTitle(title: String?): ServerUrl? = values().associateBy { it.title }[title]
fun fromServerType(type: String?): ServerUrl? = values().associateBy { it.serverType }[type]
}
override fun toString() = title
fun getBaseUrl() = serverInfo.urlInfo.getBaseUrl()
fun getAddress() = serverInfo.urlInfo.getAddress()
fun getId() = ordinal
fun getPins() = serverInfo.getPins()
}

View File

@ -0,0 +1,18 @@
package ru.template.data.network.sslpinning
import ru.template.data.network.storage.EncryptedStorage
data class ServerInfo(
val urlInfo: UrlInfo,
val pinsHashSet: HashSet<String>
) {
fun getPins() = pinsHashSet.toSet()
fun addPins(pinSet: Set<String>) = pinsHashSet.addAll(pinSet)
fun updatePins(baseUrl: String, encryptedStorage: EncryptedStorage) {
with(pinsHashSet) {
addAll(encryptedStorage.getCertificatesSet(baseUrl))
}
}
}

View File

@ -0,0 +1,16 @@
package com.redmadrobot.data.network.sslpinning
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
object TrustManagerUnsafe: X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
override fun getAcceptedIssuers() = emptyArray<X509Certificate>()
}

View File

@ -0,0 +1,49 @@
package com.redmadrobot.data.network.sslpinning
import android.annotation.SuppressLint
import com.redmadrobot.data.network.NetworkConfig
import com.redmadrobot.domain.extension.toHex
import com.redmadrobot.domain.repository.ssl.SslPublicKeyRepository
import java.security.MessageDigest
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.Locale
import javax.net.ssl.X509TrustManager
@SuppressLint("CustomX509TrustManager")
class TrustManagerWithoutTls(
private val networkConfig: NetworkConfig,
private val sslPublicKeyRepository: SslPublicKeyRepository
) : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
if (networkConfig.isSslPinningEnabled()) {
chain?.let { checkCertificateFingerprint(it) }
}
}
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
private fun checkCertificateFingerprint(chain: Array<X509Certificate?>) {
val pinFromServer = chain[0]?.let { getSha256FingerprintFormatted(it) }
networkConfig.getCurrentServer().getPins().also {
if (it.contains(pinFromServer)) {
sslPublicKeyRepository.setPublicKey(isValid = true)
return
}
}
sslPublicKeyRepository.setPublicKey(isValid = false)
throw CertificateException("Cannot validate server certificate")
}
private fun getSha256FingerprintFormatted(certificate: X509Certificate): String {
return MessageDigest
.getInstance("SHA-256")
.digest(certificate.encoded)
.toHex(separator = ":").toUpperCase(Locale.getDefault())
}
}

View File

@ -0,0 +1,11 @@
package ru.template.data.network.sslpinning
data class UrlInfo(
private val protocol: String,
private val url: String,
) {
fun getAddress(): String = "$protocol://$url/api/v1/"
fun getBaseUrl(): String = url
}

View File

@ -0,0 +1,15 @@
package ru.template.data.network.storage
import android.content.SharedPreferences
class EncryptedStorage(private val preferences: SharedPreferences) {
fun saveCertificatesToSet(host: String, certificates: Set<String>) =
preferences.edit().putStringSet(host,
getCertificatesSet(host).toMutableSet().apply { addAll(certificates) }.toSet()).apply()
fun getCertificatesSet(host: String): Set<String>
= preferences.getStringSet(host, emptySet()) ?: emptySet()
}

View File

@ -0,0 +1,17 @@
package ru.template.data
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

1
domain/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

29
domain/build.gradle.kts Normal file
View File

@ -0,0 +1,29 @@
plugins {
id(libs.plugins.android.lib.get().pluginId)
}
private val versionCatalog: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")
android {
namespace = "ru.template.domain"
buildTypes {
addLibBuildType(BuildType.Develop, versionCatalog = versionCatalog)
addLibBuildType(BuildType.Debug, versionCatalog = versionCatalog)
addLibBuildType(BuildType.Customer, versionCatalog = versionCatalog)
addLibBuildType(BuildType.Release, versionCatalog = versionCatalog)
}
addMobileServicesFlavor()
sourceSets {
getByName("main") {
java.srcDirs("src/main/kotlin")
}
}
}
dependencies {
implementation(project(":mobile_services"))
}

View File

@ -0,0 +1,24 @@
package ru.template.domain
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("ru.template.domain.test", appContext.packageName)
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,17 @@
package ru.template.domain
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -1 +0,0 @@
/build

View File

@ -1,8 +0,0 @@
plugins {
id(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}
dependencies {
dagger()
mvi()
}

View File

@ -1,3 +0,0 @@
<manifest package="ru.touchin.template.feature_login">
</manifest>

View File

@ -1,7 +0,0 @@
package ru.touchin.template.feature_login
import ru.touchin.template.feature_login.navigation.LoginCoordinator
interface LoginDeps {
fun loginCoordinator(): LoginCoordinator
}

View File

@ -1,19 +0,0 @@
package ru.touchin.template.feature_login.di
import dagger.Component
import ru.touchin.roboswag.navigation_base.scopes.FragmentScope
import ru.touchin.template.feature_login.LoginDeps
import ru.touchin.template.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
}
}

View File

@ -1,24 +0,0 @@
package ru.touchin.template.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.template.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<out ViewModel>
}
@AssistedModule
@Module(includes = [AssistedInject_ViewModelAssistedFactoriesModule::class])
abstract class ViewModelAssistedFactoriesModule

View File

@ -1,8 +0,0 @@
package ru.touchin.template.feature_login.navigation
import ru.touchin.roboswag.navigation_base.scopes.FragmentScope
@FragmentScope
interface LoginCoordinator {
fun openMainScreen()
}

View File

@ -1,43 +0,0 @@
package ru.touchin.template.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.template.feature_login.R
import ru.touchin.template.feature_login.databinding.FragmentLoginBinding
import ru.touchin.template.feature_login.di.DaggerLoginComponent
import ru.touchin.template.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<EmptyState, LoginViewState, LoginViewAction, LoginViewModel>(R.layout.fragment_login),
IHasComponent<LoginComponent> {
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())
}

View File

@ -1,7 +0,0 @@
package ru.touchin.template.feature_login.presentation
import ru.touchin.roboswag.mvi_arch.marker.ViewAction
sealed class LoginViewAction : ViewAction {
object GoToMainScreenButtonClicked : LoginViewAction()
}

View File

@ -1,25 +0,0 @@
package ru.touchin.template.feature_login.presentation
import androidx.lifecycle.SavedStateHandle
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import ru.touchin.template.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<EmptyState, LoginViewAction, LoginViewState>(LoginViewState, arg0) {
override fun dispatchAction(action: LoginViewAction) {
when (action) {
LoginViewAction.GoToMainScreenButtonClicked -> coordinator.openMainScreen()
}
}
@AssistedInject.Factory
interface Factory : ViewModelAssistedFactory<LoginViewModel>
}

View File

@ -1,5 +0,0 @@
package ru.touchin.template.feature_login.presentation
import ru.touchin.roboswag.mvi_arch.marker.ViewState
object LoginViewState : ViewState

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:text="Экран логина" />
<Button
android:id="@+id/go_to_main_screen_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="На главный экран" />
</LinearLayout>

184
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,184 @@
[versions]
# Project
versionCode = "1"
versionName = "1.0.0"
compileSdk = "34"
targetSdk = "34"
minSdk = "23"
jvmBytecode = "17"
kotlin = "1.8.22"
androidGradlePlugin = "8.1.4"
# AndroidX
androidxCompat = "1.6.1"
androidxConstraint = "2.1.4"
androidxCore = "1.12.0"
androidxRecycler = "1.3.2"
# UI
androidxActivity = "1.8.2"
androidxBiometric = "1.1.0"
androidxFragment = "1.6.2"
androidxSecurity = "1.1.0-alpha06"
androidxSwipeRefresh = "1.1.0"
material = "1.11.0"
# Lifecyle
androidxLifecycle = "2.7.0"
# Dagger
dagger = "2.51"
daggerAssistedInject = "0.8.1"
daggerComponentsManager = "2.1.1"
# Glide
glide = "4.16.0"
# Retrofit2, OkHttp3
retrofit = "2.9.0"
okhttp = "4.12.0"
# Moshi
moshi = "1.15.1"
#Room
room = "2.6.1"
# KotlinX
coroutines = "1.8.0"
cicerone = "7.1"
leakCanary = "2.13"
chucker = "4.0.0"
javapoet = "1.13.0"
# GMS
googleServices = "4.4.1"
googleLicenses = "17.0.1"
googleLicensesPlugin = "0.10.6"
firebaseBom = "32.7.4"
firebaseCrashlytics = "2.9.9"
firebasePerf = "1.4.2"
# Groupie
groupie = "2.10.1"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
[libraries]
# AndroidX
androidx-compat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxCompat" }
androidx-constraint = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidxConstraint" }
androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
androidx-recycler = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "androidxRecycler" }
android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
# KotlinX
coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
# UI
androidx-activity = { group = "androidx.activity", name = "activity-ktx", version.ref = "androidxActivity" }
anroidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "androidxFragment" }
androidx-swipe-refresh = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "androidxSwipeRefresh" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
# Lifecyle
androidx-lifecycle-compiler = { group = "androidx.lifecycle", name = "lifecycle-compiler", version.ref = "androidxLifecycle" }
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "androidxLifecycle" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" }
# Dagger
dagger = { group = "com.google.dagger", name = "dagger", version.ref = "dagger" }
dagger-compiler = { group = "com.google.dagger", name = "dagger-compiler", version.ref = "dagger" }
dagger-assisted-inject-annotations = { group = "com.squareup.inject", name = "assisted-inject-annotations-dagger2", version.ref = "daggerAssistedInject" }
dagger-assisted-inject-processor = { group = "com.squareup.inject", name = "assisted-inject-processor-dagger2", version.ref = "daggerAssistedInject" }
dagger-components-manager = { group = "com.github.valeryponomarenko.componentsmanager", name = "androidx", version.ref = "daggerComponentsManager" }
# Glide
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
glide-compiler = { group = "com.github.bumptech.glide", name = "compiler", version.ref = "glide" }
glide-okhttp3 = { group = "com.github.bumptech.glide", name = "okhttp3-integration", version.ref = "glide" }
# Retrofit2, OkHttp3
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter-moshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "retrofit" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
# Moshi
moshi = { group = "com.squareup.moshi", name = "moshi", version.ref = "moshi" }
moshi-codegen = { group = "com.squareup.moshi", name = "moshi-kotlin-codegen", version.ref = "moshi" }
moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin", version.ref = "moshi" }
# Room
room = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
# Cicerone
cicerone = { group = "com.github.terrakok", name = "cicerone", version.ref = "cicerone" }
# LeakCanary
leakcanary= { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakCanary" }
# Chucker
chucker-debug = { group = "com.github.chuckerteam.chucker", name = "library", version.ref = "chucker" }
chucker-release = { group = "com.github.chuckerteam.chucker", name = "library-no-op", version.ref = "chucker" }
# GMS
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }
firebase-perf = { module = "com.google.firebase:firebase-perf" }
google-oss-licenses = { group = "com.google.android.gms", name = "play-services-oss-licenses", version.ref = "googleLicenses" }
google-oss-licenses-plugin = { group = "com.google.android.gms", name = "oss-licenses-plugin", version.ref = "googleLicensesPlugin" }
# Security
androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "androidxSecurity" }
# Biometric
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "androidxBiometric" }
# Groupie
groupie = { group = "com.github.lisawray.groupie", name = "groupie", version.ref = "groupie" }
groupie-viewbinding = { group = "com.github.lisawray.groupie", name = "groupie-viewbinding", version.ref = "groupie" }
javapoet = { module = "com.squareup:javapoet", version = "javapoet" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
google-oss-licenses-plugin = { id = "com.google.android.gms.oss-licenses-plugin", version.ref = "googleLicensesPlugin" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" }
firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebasePerf" }
android-app = { id = "android_app" }
android-lib = { id = "android_lib" }
[bundles]
androidX = ["androidx-compat", "androidx-constraint", "androidx-core", "androidx-recycler"]
ui = ["androidx-activity", "anroidx-fragment-ktx", "androidx-swipe-refresh", "material"]
lifecycle = ["androidx-lifecycle-livedata-ktx", "androidx-lifecycle-runtime-ktx", "androidx-lifecycle-viewmodel-ktx"]
dagger = ["dagger", "dagger-assisted-inject-annotations", "dagger-components-manager"]
firebase = ["firebase-analytics", "firebase-crashlytics", "firebase-perf"]

View File

@ -1,6 +1,6 @@
#Thu Feb 13 13:31:53 MSK 2020
#Fri Mar 22 17:00:19 GMT+07:00 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

1
mobile_services/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,25 @@
plugins {
id(libs.plugins.android.lib.get().pluginId)
}
private val versionCatalog: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")
android {
namespace = "ru.template.mobile.services"
buildTypes {
addLibBuildType(BuildType.Develop, versionCatalog = versionCatalog)
addLibBuildType(BuildType.Debug, versionCatalog = versionCatalog)
addLibBuildType(BuildType.Customer, versionCatalog = versionCatalog)
addLibBuildType(BuildType.Release, versionCatalog = versionCatalog)
}
addMobileServicesFlavor()
}
dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
}

View File

@ -0,0 +1,24 @@
package ru.template.mobile.services
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("ru.template.mobile.services.test", appContext.packageName)
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

Some files were not shown because too many files have changed in this diff Show More