diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5103e8c..fce5ef5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,8 +1,5 @@ plugins { id(Plugins.ANDROID_APP_PLUGIN_WITH_DEFAULT_CONFIG) - id(libs.plugins.android.application.get().pluginId) - id(libs.plugins.kotlin.android.get().pluginId) - id(libs.plugins.kotlin.kapt.get().pluginId) alias(libs.plugins.google.services) alias(libs.plugins.firebase.crashlytics) alias(libs.plugins.firebase.perf) @@ -91,6 +88,9 @@ dependencies { // Groupie implementation(libs.groupie) implementation(libs.groupie.viewbinding) + + // Cicecrone + implementation(libs.cicerone) } apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/scripts/stringGenerator.gradle") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 498225a..0f6717f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,12 +11,12 @@ 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"> diff --git a/app/src/main/java/ru/touchin/template/App.kt b/app/src/main/java/ru/touchin/template/App.kt index c6ffdc1..0048de1 100644 --- a/app/src/main/java/ru/touchin/template/App.kt +++ b/app/src/main/java/ru/touchin/template/App.kt @@ -1,5 +1,18 @@ package ru.touchin.template import android.app.Application +import ru.touchin.template.di.DI +import ru.touchin.template.di.SharedComponent +import ru.touchin.template.di.SharedComponentProvider -class App : Application() +class App : Application(), SharedComponentProvider { + + override fun onCreate() { + super.onCreate() + + DI.init(applicationContext) + DI.getComponent().inject(this) + } + + override fun getModule(): SharedComponent = DI.getComponent() +} diff --git a/app/src/main/java/ru/touchin/template/SingleActivity.kt b/app/src/main/java/ru/touchin/template/SingleActivity.kt deleted file mode 100644 index e71c1db..0000000 --- a/app/src/main/java/ru/touchin/template/SingleActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ru.touchin.template - -import androidx.appcompat.app.AppCompatActivity - -class SingleActivity : AppCompatActivity() diff --git a/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt b/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt new file mode 100644 index 0000000..0100cf3 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/base/activity/BaseActivity.kt @@ -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 : AppCompatActivity() { + + protected val viewModel: T by lazy { createViewModelLazy().value } + + protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() } + + abstract fun createViewModelLazy(): Lazy + + protected fun getSharedComponent(): SharedComponent = getSharedModule() +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt b/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt new file mode 100644 index 0000000..39384c7 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/base/fragment/BaseFragment.kt @@ -0,0 +1,23 @@ +package ru.touchin.template.base.fragment + +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment +import ru.touchin.template.base.viewmodel.BaseViewModel +import ru.touchin.template.di.SharedComponent +import ru.touchin.template.di.getSharedModule +import ru.touchin.template.navigation.backpress.OnBackPressedListener + +abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId), OnBackPressedListener { + + protected val viewModel: T by lazy { createViewModelLazy().value } + + protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() } + + protected abstract fun createViewModelLazy(): Lazy + + protected fun getSharedComponent(): SharedComponent = getSharedModule() + + override fun onBackPressed(): Boolean { + return viewModel.onBackClicked() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/base/viewmodel/BaseController.kt b/app/src/main/java/ru/touchin/template/base/viewmodel/BaseController.kt new file mode 100644 index 0000000..106042e --- /dev/null +++ b/app/src/main/java/ru/touchin/template/base/viewmodel/BaseController.kt @@ -0,0 +1,6 @@ +package ru.touchin.template.base.viewmodel + +interface BaseController { + + fun onBackClicked(): Boolean = true +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/base/viewmodel/BaseViewModel.kt b/app/src/main/java/ru/touchin/template/base/viewmodel/BaseViewModel.kt new file mode 100644 index 0000000..cb7386f --- /dev/null +++ b/app/src/main/java/ru/touchin/template/base/viewmodel/BaseViewModel.kt @@ -0,0 +1,5 @@ +package ru.touchin.template.base.viewmodel + +import androidx.lifecycle.ViewModel + +abstract class BaseViewModel : ViewModel(), BaseController \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/AppComponent.kt b/app/src/main/java/ru/touchin/template/di/AppComponent.kt new file mode 100644 index 0000000..27efa14 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/AppComponent.kt @@ -0,0 +1,37 @@ +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.auth.AuthComponent +import ru.touchin.template.di.modules.AppModule +import ru.touchin.template.di.modules.NavigationModule +import ru.touchin.template.di.modules.ViewModelModule +import ru.touchin.template.feature.SingleActivity + +@Component( + modules = [ + AppModule::class, + ViewModelModule::class, + NavigationModule::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) +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/AppScope.kt b/app/src/main/java/ru/touchin/template/di/AppScope.kt new file mode 100644 index 0000000..f062861 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/AppScope.kt @@ -0,0 +1,6 @@ +package ru.touchin.template.di + +import javax.inject.Scope + +@Scope +annotation class AppScope \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/DI.kt b/app/src/main/java/ru/touchin/template/di/DI.kt new file mode 100644 index 0000000..626c007 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/DI.kt @@ -0,0 +1,16 @@ +package ru.touchin.template.di + +import android.content.Context + +object DI { + + private lateinit var appComponent: AppComponent + + fun init(context: Context) { + appComponent = DaggerAppComponent.builder() + .appContext(context) + .build() + } + + fun getComponent(): AppComponent = appComponent +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/SharedComponentExt.kt b/app/src/main/java/ru/touchin/template/di/SharedComponentExt.kt new file mode 100644 index 0000000..36993ee --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/SharedComponentExt.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt b/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt new file mode 100644 index 0000000..771a420 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/SharedComponentProvider.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/auth/AuthComponent.kt b/app/src/main/java/ru/touchin/template/di/auth/AuthComponent.kt new file mode 100644 index 0000000..e3c5dfc --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/auth/AuthComponent.kt @@ -0,0 +1,17 @@ +package ru.touchin.template.di.auth + +import dagger.Subcomponent +import ru.touchin.template.feature.first.FirstFragment + +@Subcomponent(modules = [AuthModule::class]) +@AuthScope +interface AuthComponent { + + @Subcomponent.Builder + interface Builder { + + fun build(): AuthComponent + } + + fun inject(entry: FirstFragment) +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/auth/AuthModule.kt b/app/src/main/java/ru/touchin/template/di/auth/AuthModule.kt new file mode 100644 index 0000000..e2e325a --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/auth/AuthModule.kt @@ -0,0 +1,13 @@ +package ru.touchin.template.di.auth + +import dagger.Module + +@Module +class AuthModule { + +// @Provides +// @AuthScope +// internal fun providesSecondRepository(): SecondRepository { +// return SecondRepositoryImpl() +// } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/auth/AuthScope.kt b/app/src/main/java/ru/touchin/template/di/auth/AuthScope.kt new file mode 100644 index 0000000..64dbe7a --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/auth/AuthScope.kt @@ -0,0 +1,6 @@ +package ru.touchin.template.di.auth + +import javax.inject.Scope + +@Scope +annotation class AuthScope \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/AppModule.kt b/app/src/main/java/ru/touchin/template/di/modules/AppModule.kt new file mode 100644 index 0000000..aab5a49 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/AppModule.kt @@ -0,0 +1,13 @@ +package ru.touchin.template.di.modules + +import dagger.Module +import ru.touchin.template.di.auth.AuthComponent + +@Module( + includes = [ + RepositoryModule::class, + NetworkModule::class + ], + subcomponents = [AuthComponent::class] +) +class AppModule \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/NavigationModule.kt b/app/src/main/java/ru/touchin/template/di/modules/NavigationModule.kt new file mode 100644 index 0000000..4838b23 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/NavigationModule.kt @@ -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 = Cicerone.create() + + @Provides + @AppScope + fun provideRouter(): Router { + return cicerone.router + } + + @Provides + @AppScope + fun provideNavigatorHolder(): NavigatorHolder { + return cicerone.getNavigatorHolder() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/NetworkModule.kt b/app/src/main/java/ru/touchin/template/di/modules/NetworkModule.kt new file mode 100644 index 0000000..8c14718 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/NetworkModule.kt @@ -0,0 +1,7 @@ +package ru.touchin.template.di.modules + +import dagger.Module + +@Module +class NetworkModule { +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/RepositoryModule.kt b/app/src/main/java/ru/touchin/template/di/modules/RepositoryModule.kt new file mode 100644 index 0000000..f995563 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/RepositoryModule.kt @@ -0,0 +1,7 @@ +package ru.touchin.template.di.modules + +import dagger.Module + +@Module +class RepositoryModule { +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt b/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt new file mode 100644 index 0000000..781d5ad --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/modules/ViewModelModule.kt @@ -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 + +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt new file mode 100644 index 0000000..699e68c --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelFactory.kt @@ -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, Provider> +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + val viewModel = viewModels[modelClass]?.get() + return viewModel as T + } +} + +inline fun Fragment.assistedViewModel( + crossinline creator: (SavedStateHandle) -> VM, +): Lazy = viewModels { createAbstractSavedStateViewModelFactory(arguments, creator) } + +inline fun SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory( + arguments: Bundle? = Bundle(), + crossinline creator: (SavedStateHandle) -> T, +): ViewModelProvider.Factory { + return object : AbstractSavedStateViewModelFactory( + owner = this@createAbstractSavedStateViewModelFactory, + defaultArgs = arguments, + ) { + @Suppress("UNCHECKED_CAST") + override fun create( + key: String, + modelClass: Class, + handle: SavedStateHandle, + ): T = creator(handle) as T + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt new file mode 100644 index 0000000..1f76f60 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/di/viewmodel/ViewModelKey.kt @@ -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) \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/SingleActivity.kt b/app/src/main/java/ru/touchin/template/feature/SingleActivity.kt new file mode 100644 index 0000000..45cbb69 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/feature/SingleActivity.kt @@ -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() { + + @Inject + lateinit var navigatorHolder: NavigatorHolder + + private val rootNavigator by lazy { + AppNavigator(this, R.id.activityScreensContainer) + } + + private lateinit var binding: ActivityMainBinding + + override fun createViewModelLazy(): Lazy = 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() + } + +} diff --git a/app/src/main/java/ru/touchin/template/feature/SingleViewModel.kt b/app/src/main/java/ru/touchin/template/feature/SingleViewModel.kt new file mode 100644 index 0000000..394f3d5 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/feature/SingleViewModel.kt @@ -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()) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/first/FirstFragment.kt b/app/src/main/java/ru/touchin/template/feature/first/FirstFragment.kt new file mode 100644 index 0000000..128df58 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/feature/first/FirstFragment.kt @@ -0,0 +1,29 @@ +package ru.touchin.template.feature.first + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.viewModels +import ru.touchin.template.R +import ru.touchin.template.base.fragment.BaseFragment +import ru.touchin.template.databinding.FragmentFirstBinding +import ru.touchin.template.utils.binding.viewBinding + +class FirstFragment : BaseFragment(R.layout.fragment_first) { + + private val binding by viewBinding { FragmentFirstBinding.bind(it) } + + override fun createViewModelLazy() = viewModels { viewModelFactory } + + 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" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/first/FirstViewModel.kt b/app/src/main/java/ru/touchin/template/feature/first/FirstViewModel.kt new file mode 100644 index 0000000..02941c6 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/feature/first/FirstViewModel.kt @@ -0,0 +1,16 @@ +package ru.touchin.template.feature.first + +import com.github.terrakok.cicerone.Router +import javax.inject.Inject +import ru.touchin.template.base.viewmodel.BaseViewModel +import ru.touchin.template.navigation.Screens + +class FirstViewModel @Inject constructor( + private val router: Router +) : BaseViewModel() { + + fun onNextButtonClicked(from: String) { + router.navigateTo(Screens.Second(from)) + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/second/SecondFragment.kt b/app/src/main/java/ru/touchin/template/feature/second/SecondFragment.kt new file mode 100644 index 0000000..7e17c92 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/feature/second/SecondFragment.kt @@ -0,0 +1,64 @@ +package ru.touchin.template.feature.second + +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import ru.touchin.template.R +import ru.touchin.template.base.fragment.BaseFragment +import ru.touchin.template.databinding.FragmentSecondBinding +import ru.touchin.template.di.viewmodel.assistedViewModel +import ru.touchin.template.utils.binding.viewBinding + +class SecondFragment : BaseFragment(R.layout.fragment_second) { + + 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 + } + } + + private val binding by viewBinding { FragmentSecondBinding.bind(it) } + + private val secondViewModelFactory by lazy { getSharedComponent().secondScreenViewModelFactory() } + + override fun createViewModelLazy() = assistedViewModel { + secondViewModelFactory.create( + arguments?.getString(FROM_KEY) ?: "Unkown Fragment", + arguments?.getString(SCREEN_NAME_KEY) ?: "Unknown" + ) + } + + 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 + } + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/feature/second/SecondViewModel.kt b/app/src/main/java/ru/touchin/template/feature/second/SecondViewModel.kt new file mode 100644 index 0000000..cc9fd9c --- /dev/null +++ b/app/src/main/java/ru/touchin/template/feature/second/SecondViewModel.kt @@ -0,0 +1,33 @@ +package ru.touchin.template.feature.second + +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.BaseViewModel + +class SecondViewModel @AssistedInject constructor( + @Assisted("from") from: String, + @Assisted("screenName") screenName: String, + private val rootRouter: Router +) : BaseViewModel() { + + private val _state = MutableStateFlow("$from to $screenName") + val state: StateFlow = _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() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/navigation/Screens.kt b/app/src/main/java/ru/touchin/template/navigation/Screens.kt new file mode 100644 index 0000000..51dabfd --- /dev/null +++ b/app/src/main/java/ru/touchin/template/navigation/Screens.kt @@ -0,0 +1,16 @@ +package ru.touchin.template.navigation + +import com.github.terrakok.cicerone.androidx.FragmentScreen +import ru.touchin.template.feature.first.FirstFragment +import ru.touchin.template.feature.second.SecondFragment + +object Screens { + + fun First(): FragmentScreen = FragmentScreen { + FirstFragment() + } + + fun Second(from: String): FragmentScreen = FragmentScreen { + SecondFragment.newInstance(from) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/navigation/backpress/OnBackPressedListener.kt b/app/src/main/java/ru/touchin/template/navigation/backpress/OnBackPressedListener.kt new file mode 100644 index 0000000..1679240 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/navigation/backpress/OnBackPressedListener.kt @@ -0,0 +1,6 @@ +package ru.touchin.template.navigation.backpress + +interface OnBackPressedListener { + + fun onBackPressed(): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/navigation/router/RouterProvider.kt b/app/src/main/java/ru/touchin/template/navigation/router/RouterProvider.kt new file mode 100644 index 0000000..14426c4 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/navigation/router/RouterProvider.kt @@ -0,0 +1,7 @@ +package ru.touchin.template.navigation.router + +import com.github.terrakok.cicerone.Router + +interface RouterProvider { + val router: Router +} \ No newline at end of file diff --git a/app/src/main/java/ru/touchin/template/utils/binding/FragmentViewBindingDelegate.kt b/app/src/main/java/ru/touchin/template/utils/binding/FragmentViewBindingDelegate.kt new file mode 100644 index 0000000..f5e15b1 --- /dev/null +++ b/app/src/main/java/ru/touchin/template/utils/binding/FragmentViewBindingDelegate.kt @@ -0,0 +1,50 @@ +package ru.touchin.template.utils.binding + +import android.view.View +import androidx.fragment.app.Fragment +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.viewbinding.ViewBinding +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +typealias ViewBindingFactory = (View) -> T + +class FragmentViewBindingDelegate( + val fragment: Fragment, + val viewBindingFactory: ViewBindingFactory, +) : ReadOnlyProperty { + private var binding: T? = null + + init { + fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + fragment.viewLifecycleOwnerLiveData.observe(fragment) { lifecylelOwner -> + lifecylelOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + binding = null + } + }) + } + } + }) + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>): T { + val binding = this.binding + + if (binding != null) return binding + + val lifecycle = fragment.viewLifecycleOwner.lifecycle + + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { + throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") + } + + return viewBindingFactory(thisRef.requireView()).also { this.binding = it } + } +} + +fun Fragment.viewBinding(viewBindingFactory: ViewBindingFactory) = + FragmentViewBindingDelegate(this, viewBindingFactory) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a066a0d..305c3c3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,22 @@ - - + android:layout_height="match_parent" + android:background="@color/colorPrimaryDark"> + + + + + + diff --git a/app/src/main/res/layout/fragment_first.xml b/app/src/main/res/layout/fragment_first.xml new file mode 100644 index 0000000..a36f0ea --- /dev/null +++ b/app/src/main/res/layout/fragment_first.xml @@ -0,0 +1,27 @@ + + + + + +