Compare commits

...

12 Commits

Author SHA1 Message Date
Evgeny Dubravin 7f51e68f46 Auth Component subcomponent dependency 2024-04-05 22:40:51 +07:00
Evgeny Dubravin 9b85bcf562 add Auth Component 2024-04-02 03:42:28 +07:00
Evgeny Dubravin d71fda5853 feature TI-195: [Android] Cicirone Navigation 2024-03-29 22:51:11 +07:00
Evgeny Dubravin c4b740f629 feature TI-194: [Android] DI 2024-03-29 22:36:19 +07:00
Evgeny Dubravin 417547d44e feature TI-193: [Android] Реализовать ViewModel Factory 2024-03-28 17:10:25 +07:00
Evgeny Dubravin 70819e7f41 set common-template submodule 2024-03-25 18:11:23 +07:00
Evgeny Dubravin df7adab2e7 feature TI-188: [Android] Настройка flavor, buildType 2024-03-23 00:24:09 +07:00
Evgeny Dubravin 601fbceb2b feature TI-187: [Android] Настройка зависимостей 2024-03-23 00:15:44 +07:00
Evgeny Dubravin 80681356fa feature TI-186: [Android] Настройка проекта 2024-03-22 20:25:18 +07:00
Evgeny Dubravin b99dec674c add template dependencies 2024-03-21 22:09:35 +07:00
Evgeny Dubravin f77bc0a8c5 удалены лишние модули 2024-03-19 16:47:50 +07:00
Evgeny Dubravin d03d5f9340 set submodules 2024-03-15 17:16:41 +07:00
123 changed files with 1434 additions and 1203 deletions

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

View File

@ -1,74 +1,99 @@
plugins {
id(Plugins.ANDROID_APP_PLUGIN_WITH_DEFAULT_CONFIG)
id(Plugins.FIREBASE_CRASH)
id(Plugins.GOOGLE_SERVICES)
id(Plugins.LICENCE_PLUGIN)
alias(libs.plugins.google.services)
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)
// Cicecrone
implementation(libs.cicerone)
}
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

@ -10,7 +10,7 @@
"client_info": {
"mobilesdk_app_id": "1:1084813714260:android:b6d7bb18a0acfe96255ec1",
"android_client_info": {
"package_name": "com.touchin.template"
"package_name": "ru.touchin.template"
}
},
"oauth_client": [

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,17 +8,17 @@
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">
tools:ignore="GoogleAppIndexingWarning"
android:theme="@style/AppTheme">
<activity
android:name="ru.touchin.template.SingleActivity"
android:name=".feature.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,18 @@
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
import ru.touchin.template.di.DI
import ru.touchin.template.di.SharedComponent
import ru.touchin.template.di.SharedComponentProvider
class App : TouchinApp(), IHasComponent<ApplicationComponent> {
class App : Application(), SharedComponentProvider {
override fun onCreate() {
super.onCreate()
initDagger()
DI.initAppComponent(applicationContext)
DI.getComponent().inject(this)
}
fun initDagger() {
XInjectionManager.init(this)
XInjectionManager.bindComponent(this)
}
override fun getComponent(): ApplicationComponent = DaggerApplicationComponent
.factory()
.create(this)
override fun getModule(): SharedComponent = DI.getComponent()
}

View File

@ -1,62 +0,0 @@
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
// 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)
}
}

View File

@ -0,0 +1,17 @@
package ru.touchin.template.base.activity
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import ru.touchin.template.di.SharedComponent
import ru.touchin.template.di.getSharedModule
abstract class BaseActivity<T : ViewModel> : AppCompatActivity() {
protected val viewModel: T by lazy { createViewModelLazy().value }
protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() }
abstract fun createViewModelLazy(): Lazy<T>
protected fun getSharedComponent(): SharedComponent = getSharedModule()
}

View File

@ -0,0 +1,23 @@
package ru.touchin.template.base.fragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import ru.touchin.template.base.viewmodel.BaseController
import ru.touchin.template.di.SharedComponent
import ru.touchin.template.di.getSharedModule
import ru.touchin.template.navigation.backpress.OnBackPressedListener
abstract class BaseFragment<T : ViewModel> : Fragment(), OnBackPressedListener {
protected val viewModel: T by lazy { createViewModelLazy().value }
protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() }
protected abstract fun createViewModelLazy(): Lazy<T>
protected fun getSharedComponent(): SharedComponent = getSharedModule()
override fun onBackPressed(): Boolean {
return (viewModel as BaseController).onBackClicked()
}
}

View File

@ -0,0 +1,6 @@
package ru.touchin.template.base.viewmodel
interface BaseController {
fun onBackClicked(): Boolean = true
}

View File

@ -0,0 +1,29 @@
package ru.touchin.template.di
import android.content.Context
import dagger.BindsInstance
import dagger.Component
import ru.touchin.template.App
import ru.touchin.template.di.modules.AppModule
import ru.touchin.template.feature.SingleActivity
import ru.touchin.template.feature.second.di.AuthComponent
@Component(modules = [AppModule::class])
@AppScope
interface AppComponent : SharedComponent {
fun authComponent(): AuthComponent.Builder
@Component.Builder
interface Builder {
@BindsInstance
fun appContext(appContext: Context): Builder
fun build(): AppComponent
}
fun inject(entry: App)
fun inject(entry: SingleActivity)
}

View File

@ -0,0 +1,6 @@
package ru.touchin.template.di
import javax.inject.Scope
@Scope
annotation class AppScope

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

@ -0,0 +1,16 @@
package ru.touchin.template.di
import android.content.Context
object DI {
private lateinit var appComponent: AppComponent
fun initAppComponent(context: Context) {
appComponent = DaggerAppComponent.builder()
.appContext(context)
.build()
}
fun getComponent(): AppComponent = appComponent
}

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

@ -0,0 +1,17 @@
package ru.touchin.template.di
import android.app.Activity
import android.content.Context
import androidx.fragment.app.Fragment
fun Context.getSharedModule(): SharedComponent {
return (applicationContext as SharedComponentProvider).getModule()
}
fun Activity.getSharedModule(): SharedComponent {
return (applicationContext as SharedComponentProvider).getModule()
}
fun Fragment.getSharedModule(): SharedComponent {
return requireContext().getSharedModule()
}

View File

@ -0,0 +1,17 @@
package ru.touchin.template.di
import dagger.Module
import ru.touchin.template.di.viewmodel.ViewModelFactory
import ru.touchin.template.feature.second.SecondViewModel
interface SharedComponent {
fun viewModelFactory(): ViewModelFactory
fun secondScreenViewModelFactory(): SecondViewModel.Factory
}
@Module
class SharedModule
interface SharedComponentProvider {
fun getModule(): SharedComponent
}

View File

@ -0,0 +1,15 @@
package ru.touchin.template.di.modules
import dagger.Module
import ru.touchin.template.feature.second.di.AuthComponent
@Module(
includes = [
RepositoryModule::class,
NetworkModule::class,
ViewModelModule::class,
NavigationModule::class
],
subcomponents = [AuthComponent::class]
)
class AppModule

View File

@ -0,0 +1,26 @@
package ru.touchin.template.di.modules
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Router
import dagger.Module
import dagger.Provides
import ru.touchin.template.di.AppScope
@Module
class NavigationModule {
private val cicerone: Cicerone<Router> = Cicerone.create()
@Provides
@AppScope
fun provideRouter(): Router {
return cicerone.router
}
@Provides
@AppScope
fun provideNavigatorHolder(): NavigatorHolder {
return cicerone.getNavigatorHolder()
}
}

View File

@ -0,0 +1,7 @@
package ru.touchin.template.di.modules
import dagger.Module
@Module
class NetworkModule {
}

View File

@ -0,0 +1,11 @@
package ru.touchin.template.di.modules
import dagger.Module
@Module
interface RepositoryModule {
// @Binds
// @AppScope
// fun bindSecondRepository(secondRepositoryImpl: SecondRepositoryImpl): SecondRepository
}

View File

@ -0,0 +1,29 @@
package ru.touchin.template.di.modules
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import ru.touchin.template.di.viewmodel.ViewModelFactory
import ru.touchin.template.di.viewmodel.ViewModelKey
import ru.touchin.template.feature.SingleViewModel
import ru.touchin.template.feature.first.FirstViewModel
@Module
interface ViewModelModule {
@Binds
fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(SingleViewModel::class)
fun bindsSingleViewModel(viewModel: SingleViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(FirstViewModel::class)
fun bindsFirstViewModel(viewModel: FirstViewModel): ViewModel
}

View File

@ -0,0 +1,44 @@
package ru.touchin.template.di.viewmodel
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.savedstate.SavedStateRegistryOwner
import javax.inject.Inject
import javax.inject.Provider
class ViewModelFactory @Inject constructor(
private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val viewModel = viewModels[modelClass]?.get()
return viewModel as T
}
}
inline fun <reified VM : ViewModel> Fragment.assistedViewModel(
crossinline creator: (SavedStateHandle) -> VM,
): Lazy<VM> = viewModels { createAbstractSavedStateViewModelFactory(arguments, creator) }
inline fun <reified T : ViewModel> SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory(
arguments: Bundle? = Bundle(),
crossinline creator: (SavedStateHandle) -> T,
): ViewModelProvider.Factory {
return object : AbstractSavedStateViewModelFactory(
owner = this@createAbstractSavedStateViewModelFactory,
defaultArgs = arguments,
) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle,
): T = creator(handle) as T
}
}

View File

@ -0,0 +1,9 @@
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<out ViewModel>)

View File

@ -0,0 +1,47 @@
package ru.touchin.template.feature
import android.os.Bundle
import androidx.activity.viewModels
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.androidx.AppNavigator
import javax.inject.Inject
import ru.touchin.template.R
import ru.touchin.template.base.activity.BaseActivity
import ru.touchin.template.databinding.ActivityMainBinding
import ru.touchin.template.di.DI
class SingleActivity : BaseActivity<SingleViewModel>() {
@Inject
lateinit var navigatorHolder: NavigatorHolder
private val rootNavigator by lazy {
AppNavigator(this, R.id.activityScreensContainer)
}
private lateinit var binding: ActivityMainBinding
override fun createViewModelLazy(): Lazy<SingleViewModel> = viewModels { viewModelFactory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
DI.getComponent().inject(this)
viewModel.navigate()
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(rootNavigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
}

View File

@ -0,0 +1,14 @@
package ru.touchin.template.feature
import androidx.lifecycle.ViewModel
import com.github.terrakok.cicerone.Router
import javax.inject.Inject
import ru.touchin.template.navigation.Screens
class SingleViewModel @Inject constructor(
private val router: Router,
) : ViewModel() {
fun navigate() {
router.newRootScreen(Screens.First())
}
}

View File

@ -0,0 +1,53 @@
package ru.touchin.template.feature.first
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import javax.inject.Inject
import ru.touchin.template.base.fragment.BaseFragment
import ru.touchin.template.databinding.FragmentFirstBinding
import ru.touchin.template.di.DI
import ru.touchin.template.feature.second.SecondRepository
class FirstFragment : BaseFragment<FirstViewModel>() {
@Inject
lateinit var secondRepository: SecondRepository
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
override fun createViewModelLazy() = viewModels<FirstViewModel> { viewModelFactory }
override fun onAttach(context: Context) {
DI.getComponent().authComponent().build().inject(this)
super.onAttach(context)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentFirstBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
button.apply {
text = "Next"
setOnClickListener { viewModel.onNextButtonClicked(this@FirstFragment::class.toString()) }
}
textView.text = "First Fragment"
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,16 @@
package ru.touchin.template.feature.first
import androidx.lifecycle.ViewModel
import com.github.terrakok.cicerone.Router
import javax.inject.Inject
import ru.touchin.template.navigation.Screens
class FirstViewModel @Inject constructor(
private val router: Router,
// private val secondRepository: SecondRepository
) : ViewModel() {
fun onNextButtonClicked(from: String) {
router.navigateTo(Screens.Second(from))
}
}

View File

@ -0,0 +1,86 @@
package ru.touchin.template.feature.second
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import javax.inject.Inject
import kotlinx.coroutines.launch
import ru.touchin.template.base.fragment.BaseFragment
import ru.touchin.template.databinding.FragmentSecondBinding
import ru.touchin.template.di.DI
import ru.touchin.template.di.viewmodel.assistedViewModel
class SecondFragment : BaseFragment<SecondViewModel>() {
companion object {
private const val FROM_KEY = "FROM"
private const val SCREEN_NAME_KEY = "SCREEN_NAME"
private const val FRAGMENT_NAME = "Second Fragment"
fun newInstance(from: String): SecondFragment {
val args = bundleOf(FROM_KEY to from, SCREEN_NAME_KEY to FRAGMENT_NAME)
val fragment = SecondFragment().apply {
arguments = args
}
return fragment
}
}
@Inject
lateinit var secondRepository: SecondRepository
private var _binding: FragmentSecondBinding? = null
private val binding get() = _binding!!
private val secondViewModelFactory by lazy { getSharedComponent().secondScreenViewModelFactory() }
override fun createViewModelLazy() = assistedViewModel {
secondViewModelFactory.create(
arguments?.getString(FROM_KEY) ?: "Unknown Fragment",
arguments?.getString(SCREEN_NAME_KEY) ?: "Unknown"
)
}
override fun onAttach(context: Context) {
DI.getComponent().authComponent().build().inject(this)
super.onAttach(context)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentSecondBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding) {
button.apply {
text = "Back"
setOnClickListener { onBackPressed() }
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.state
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect {
textView.text = it
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,11 @@
package ru.touchin.template.feature.second
import javax.inject.Inject
interface SecondRepository {
}
class SecondRepositoryImpl @Inject constructor() : SecondRepository {
}

View File

@ -0,0 +1,36 @@
package ru.touchin.template.feature.second
import androidx.lifecycle.ViewModel
import com.github.terrakok.cicerone.Router
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import ru.touchin.template.base.viewmodel.BaseController
class SecondViewModel @AssistedInject constructor(
@Assisted("from") from: String,
@Assisted("screenName") screenName: String,
private val rootRouter: Router,
// private val secondRepository: SecondRepository
) : ViewModel(), BaseController {
private val _state = MutableStateFlow("$from to $screenName")
val state: StateFlow<String> = _state.asStateFlow()
@AssistedFactory
interface Factory {
fun create(
@Assisted("from") from: String,
@Assisted("screenName") screenName: String,
): SecondViewModel
}
override fun onBackClicked(): Boolean {
rootRouter.exit()
return super.onBackClicked()
}
}

View File

@ -0,0 +1,20 @@
package ru.touchin.template.feature.second.di
import dagger.Subcomponent
import ru.touchin.template.feature.first.FirstFragment
import ru.touchin.template.feature.second.SecondFragment
@Subcomponent(modules = [AuthModule::class])
@AuthScope
interface AuthComponent {
@Subcomponent.Builder
interface Builder {
fun build(): AuthComponent
}
fun inject(entry: SecondFragment)
fun inject(entry: FirstFragment)
}

View File

@ -0,0 +1,14 @@
package ru.touchin.template.feature.second.di
import dagger.Binds
import dagger.Module
import ru.touchin.template.feature.second.SecondRepository
import ru.touchin.template.feature.second.SecondRepositoryImpl
@Module
interface AuthModule {
@Binds
@AuthScope
fun bindSecondRepository(secondRepositoryImpl: SecondRepositoryImpl): SecondRepository
}

View File

@ -0,0 +1,6 @@
package ru.touchin.template.feature.second.di
import javax.inject.Scope
@Scope
annotation class AuthScope

View File

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

View File

@ -1,13 +1,16 @@
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
import com.github.terrakok.cicerone.androidx.FragmentScreen
import ru.touchin.template.feature.first.FirstFragment
import ru.touchin.template.feature.second.SecondFragment
object Screens {
class Login : SupportAppScreen() {
override fun getFragment(): Fragment = LoginFragment()
fun First(): FragmentScreen = FragmentScreen {
FirstFragment()
}
}
fun Second(from: String): FragmentScreen = FragmentScreen {
SecondFragment.newInstance(from)
}
}

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

@ -0,0 +1,6 @@
package ru.touchin.template.navigation.backpress
interface OnBackPressedListener {
fun onBackPressed(): Boolean
}

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

@ -0,0 +1,7 @@
package ru.touchin.template.navigation.router
import com.github.terrakok.cicerone.Router
interface RouterProvider {
val router: Router
}

View File

@ -1,6 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:background="@color/biometric_error_color">
<FrameLayout
android:id="@+id/activityScreensContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="40dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="common_global_app_name">Template</string>
</resources>
<string name="common_app_name" formatted="false">android-project-template</string>
</resources>

View File

@ -1,43 +1,18 @@
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) {
exclude(group = "com.google.protobuf")
}
}
}
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,133 @@
import com.android.build.gradle.BaseExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.kotlin.dsl.extra
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) {
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

@ -5,16 +5,6 @@ object Plugins {
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_ANDROID = "org.jetbrains.kotlin.android"
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

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

View File

@ -0,0 +1,19 @@
import org.gradle.api.artifacts.VersionCatalog
val VersionCatalog.sdkCompile: String
get() = findVersion("compileSdk").get().requiredVersion
val VersionCatalog.sdkMin: String
get() = findVersion("minSdk").get().requiredVersion
val VersionCatalog.sdkTarget: 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,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

@ -2,6 +2,9 @@ package plugins
import Plugins
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
class AndroidAppPlugin : BaseAndroidPlugin() {

View File

@ -4,15 +4,23 @@ import AndroidConfig
import BuildType
import Plugins
import com.android.build.gradle.BaseExtension
import kotlinStd
import 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.internal.impldep.junit.runner.Version.id
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import sdkCompile
import sdkMin
import sdkTarget
import versionCode
import versionName
abstract class BaseAndroidPlugin : Plugin<Project> {
override fun apply(target: Project) {
@ -23,50 +31,45 @@ 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)
}
private fun Project.configureAndroid() = extensions.getByType<BaseExtension>().run {
compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION)
buildToolsVersion = AndroidConfig.BUILD_TOOLS_VERSION
compileSdkVersion(libs.sdkCompile.toInt())
defaultConfig {
minSdkVersion(AndroidConfig.MIN_SDK_VERSION)
targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION)
versionCode = AndroidConfig.VERSION_CODE
versionName = AndroidConfig.VERSION_NAME
minSdk = libs.sdkMin.toInt()
targetSdk = libs.sdkTarget.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")

1
common-template Submodule

@ -0,0 +1 @@
Subproject commit d6f303bf879a2da1706cfdacaf2bbe0c326044bd

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(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}
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,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(Plugins.ANDROID_LIB_PLUGIN_WITH_DEFAULT_CONFIG)
}
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"))
}

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