diff --git a/.gitignore b/.gitignore index 09b993d..74cdfe4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ /local.properties /.idea .DS_Store -/build /captures .externalNativeBuild +**/build diff --git a/build.gradle b/build.gradle index 2d54007..6dfc56a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.11' + ext.kotlin_version = '1.3.50' repositories { google() jcenter() diff --git a/navigation/build.gradle b/navigation/build.gradle index 1110b4a..8d3b8e9 100644 --- a/navigation/build.gradle +++ b/navigation/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' android { compileSdkVersion versions.compileSdk @@ -21,14 +22,19 @@ dependencies { api 'androidx.multidex:multidex:2.0.1' - api 'net.danlew:android.joda:2.9.9.4' + api 'net.danlew:android.joda:2.10.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "androidx.appcompat:appcompat:$versions.appcompat" + + implementation "androidx.fragment:fragment:$versions.fragment" + implementation "androidx.fragment:fragment-ktx:$versions.fragment" + + implementation "com.jakewharton:butterknife:$versions.butterknife" + kapt "com.jakewharton:butterknife-compiler:$versions.butterknife" implementation("com.crashlytics.sdk.android:crashlytics:$versions.crashlytics@aar") { transitive = true } - } diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt index 2a202bb..2205dcc 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/FragmentNavigation.kt @@ -21,12 +21,16 @@ package ru.touchin.roboswag.components.navigation import android.content.Context import android.os.Bundle +import android.os.Parcelable import android.view.MenuItem import androidx.annotation.IdRes import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import ru.touchin.roboswag.core.log.Lc +import ru.touchin.roboswag.components.navigation.fragments.BaseFragment +import ru.touchin.roboswag.components.navigation.viewcontrollers.EmptyState +import kotlin.reflect.KClass /** * Created by Gavriil Sitnikov on 07/03/2016. @@ -150,9 +154,27 @@ open class FragmentNavigation( fragmentClass: Class, args: Bundle? = null, addToStack: Boolean = true, + backStackName: String? = null, transactionSetup: ((FragmentTransaction) -> Unit)? = null ) { - addToStack(fragmentClass, null, 0, addToStack, args, null, transactionSetup) + addToStack(fragmentClass, null, 0, addToStack, args, backStackName, transactionSetup) + } + + /** + * Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup. + * + * @param fragmentClass KClass of [Fragment] to instantiate; + * @param state State of instantiated [Fragment]; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + fun push( + fragmentClass: KClass>, + state: T? = null, + addToStack: Boolean = true, + backStackName: String? = null, + transactionSetup: ((FragmentTransaction) -> Unit)? = null + ) { + push(fragmentClass.java, BaseFragment.args(state ?: EmptyState), addToStack, backStackName, transactionSetup) } /** @@ -181,6 +203,24 @@ open class FragmentNavigation( ) } + /** + * Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup. + * + * @param fragmentClass KClass of [Fragment] to instantiate; + * @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment]; + * @param state State of instantiated [Fragment]; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + fun pushForResult( + fragmentClass: KClass>, + targetFragment: Fragment, + targetRequestCode: Int, + state: T? = null, + transactionSetup: ((FragmentTransaction) -> Unit)? = null + ) { + pushForResult(fragmentClass.java, targetFragment, targetRequestCode, BaseFragment.args(state ?: EmptyState), transactionSetup) + } + /** * Pushes [Fragment] on top of stack with specific transaction setup, arguments * and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation. @@ -215,6 +255,22 @@ open class FragmentNavigation( setAsTop(fragmentClass, args, false, transactionSetup) } + /** + * Pops all [Fragment]s and places new initial [Fragment] on top of stack with specific transaction setup and arguments. + * + * @param fragmentClass Class of [Fragment] to instantiate; + * @param state State of instantiated [Fragment]; + * @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info. + */ + fun setInitial( + fragmentClass: KClass>, + state: T? = null, + transactionSetup: ((FragmentTransaction) -> Unit)? = null + ) { + beforeSetInitialActions() + setAsTop(fragmentClass.java, BaseFragment.args(state ?: EmptyState), false, transactionSetup) + } + /** * Method calls every time before initial [Fragment] will be placed. */ diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/BaseFragment.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/BaseFragment.kt new file mode 100644 index 0000000..2e3d762 --- /dev/null +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/BaseFragment.kt @@ -0,0 +1,100 @@ +package ru.touchin.roboswag.components.navigation.fragments + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.os.Parcel +import android.os.Parcelable +import android.view.View +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.annotation.IdRes +import androidx.annotation.LayoutRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import butterknife.ButterKnife +import butterknife.Unbinder +import ru.touchin.roboswag.components.navigation.BuildConfig +import ru.touchin.roboswag.components.navigation.viewcontrollers.LifecycleLoggingObserver + +open class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes) { + + companion object { + private const val BASE_FRAGMENT_STATE_EXTRA = "BASE_FRAGMENT_STATE_EXTRA" + + fun args(state: Parcelable?): Bundle = Bundle().also { it.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state) } + + // This method used to check unique state of each fragment. + // If two fragments share same class for state, you should not pass state instance of current fragment to the one you transition to + private fun reserialize(parcelable: T): T { + var parcel = Parcel.obtain() + parcel.writeParcelable(parcelable, 0) + val serializableBytes = parcel.marshall() + parcel.recycle() + parcel = Parcel.obtain() + parcel.unmarshall(serializableBytes, 0, serializableBytes.size) + parcel.setDataPosition(0) + val result = parcel.readParcelable(Thread.currentThread().contextClassLoader) ?: throw IllegalStateException("It must not be null") + parcel.recycle() + return result + } + } + + protected val view: View + @JvmName("requireViewKtx") get() = requireView() + + protected val activity: TActivity + @JvmName("requireActivityKtx") get() = requireActivity() as TActivity + + protected val context: Context + @JvmName("requireContextKtx") get() = requireContext() + + protected lateinit var state: TState + private set + + private lateinit var butterKnifeUnbinder: Unbinder + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setHasOptionsMenu(true) + + state = savedInstanceState?.getParcelable(BASE_FRAGMENT_STATE_EXTRA) + ?: arguments?.getParcelable(BASE_FRAGMENT_STATE_EXTRA) + ?: throw IllegalStateException("Fragment state can't be null") + + if (BuildConfig.DEBUG) { + state = reserialize(state) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + lifecycle.addObserver(LifecycleLoggingObserver()) + butterKnifeUnbinder = ButterKnife.bind(this, view) + } + + override fun onDestroyView() { + butterKnifeUnbinder.unbind() + super.onDestroyView() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state) + } + + fun findViewById(@IdRes id: Int): T = view.findViewById(id) + + @ColorInt + fun getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(requireContext(), resId) + + fun getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId) + + fun getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId) + +} diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.kt b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.kt index ccb3663..21a2f33 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.kt +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.kt @@ -171,7 +171,7 @@ open class ViewControllerFragment