Merge pull request #125 from TouchInstinct/base_naviagation

navigation_new became navigation_base, tabbar navigation has appropriate postfix now
This commit is contained in:
PilotOfSparrow 2020-06-05 14:31:50 +03:00 committed by GitHub
commit 48bfcb0d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 1141 additions and 1257 deletions

View File

@ -19,14 +19,13 @@
package ru.touchin.templates;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.log.LcGroup;
@ -38,11 +37,6 @@ public abstract class ApiModel implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Logging group to log API validation errors.
*/
public static final LcGroup API_VALIDATION_LC_GROUP = new LcGroup("API_VALIDATION");
/**
* Validates list of objects. Use it if objects in list extends {@link ApiModel}.
*
@ -76,14 +70,14 @@ public abstract class ApiModel implements Serializable {
throw exception;
case EXCEPTION_IF_ALL_INVALID:
iterator.remove();
API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position);
LcGroup.API_VALIDATION.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position);
if (!iterator.hasNext() && !haveValidItem) {
throw new ValidationException("Whole list is invalid at " + Lc.getCodePoint(null, 1));
}
break;
case REMOVE_INVALID_ITEMS:
iterator.remove();
API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position);
LcGroup.API_VALIDATION.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position);
break;
default:
Lc.assertion("Unexpected rule " + collectionValidationRule);

View File

@ -12,10 +12,16 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
api project(":navigation-new")
implementation project(":logging")
implementation project(":navigation-base")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

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

View File

@ -1,19 +1,19 @@
package ru.touchin.roboswag.components.tabbarnavigation
package ru.touchin.roboswag.bottom_navigation_base
import android.os.Parcelable
import androidx.annotation.IdRes
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.components.navigation.activities.NavigationActivity
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewControllerNavigation
import ru.touchin.roboswag.navigation_base.FragmentNavigation
import ru.touchin.roboswag.navigation_base.activities.NavigationActivity
/**
* Created by Daniil Borisovskii on 15/08/2019.
* Activity to manage tab container navigation.
*/
abstract class BottomNavigationActivity : NavigationActivity() {
abstract class BaseBottomNavigationActivity<TNavigation, TNavigationFragment, TNavigationContainer> : NavigationActivity<TNavigation>()
where TNavigation : FragmentNavigation,
TNavigationFragment : BaseBottomNavigationFragment<*>,
TNavigationContainer : BaseNavigationContainerFragment<*, TNavigation>
{
val innerNavigation: ViewControllerNavigation<BottomNavigationActivity>
get() = getNavigationContainer(supportFragmentManager)?.navigation ?: navigation as ViewControllerNavigation<BottomNavigationActivity>
val innerNavigation: TNavigation
get() = getNavigationContainer(supportFragmentManager)?.navigation ?: navigation
/**
* Navigates to the given navigation tab.
@ -27,15 +27,15 @@ abstract class BottomNavigationActivity : NavigationActivity() {
// Clear all navigation stack unto the main bottom navigation (tagged as top)
popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
(primaryNavigationFragment as? BottomNavigationFragment)?.navigateTo(navigationTabId, state)
(primaryNavigationFragment as? TNavigationFragment)?.navigateTo(navigationTabId, state)
}
}
private fun getNavigationContainer(fragmentManager: FragmentManager?): NavigationContainerFragment? =
protected fun getNavigationContainer(fragmentManager: FragmentManager?): TNavigationContainer? =
fragmentManager
?.primaryNavigationFragment
?.let { navigationFragment ->
navigationFragment as? NavigationContainerFragment
navigationFragment as? TNavigationContainer
?: getNavigationContainer(navigationFragment.childFragmentManager)
}

View File

@ -0,0 +1,164 @@
package ru.touchin.roboswag.bottom_navigation_base
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.core.util.forEach
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.core.utils.ShouldNotHappenException
import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment
abstract class BaseBottomNavigationController<TNavigationTab : BaseNavigationTab>(
private val tabs: SparseArray<TNavigationTab>,
private val context: Context,
private val fragmentManager: FragmentManager,
@LayoutRes private val contentContainerLayoutId: Int,
@IdRes private val contentContainerViewId: Int,
@IdRes private val defaultTabId: Int = 0, // If it zero back press with empty fragment back stack would close the app
private val wrapWithNavigationContainer: Boolean = false,
private val onReselectListener: (() -> Unit)? = null
) {
private var callback: FragmentManager.FragmentLifecycleCallbacks? = null
private var tabsContainer: ViewGroup? = null
private var selectedTabId: Int? = null
fun attach(tabsContainer: ViewGroup) {
detach()
this.tabsContainer = tabsContainer
initializeCallback()
tabsContainer.children.forEach { itemView ->
tabs[itemView.id]?.let { tab ->
itemView.setOnClickListener { buttonView ->
if (isTabClass(tab, fragmentManager.primaryNavigationFragment)) {
onTabReselected()
} else {
navigateTo(buttonView.id)
}
}
}
}
}
fun detach() {
callback?.let(fragmentManager::unregisterFragmentLifecycleCallbacks)
callback = null
tabsContainer = null
}
fun navigateTo(@IdRes itemId: Int, state: Parcelable? = null) {
// Find fragment class that needs to open
val tabClass = tabs[itemId]?.cls ?: return
val defaultFragmentState = tabs[itemId]?.state ?: return
if (state != null && state::class != defaultFragmentState::class) {
throw ShouldNotHappenException(
"Incorrect state type for navigation tab root Fragment. Should be ${defaultFragmentState::class}"
)
}
val transaction = fragmentManager.beginTransaction()
// Detach current primary fragment
fragmentManager.primaryNavigationFragment?.let(transaction::detach)
val fragmentName = tabClass.canonicalName
var fragment = fragmentManager.findFragmentByTag(fragmentName)
if (state == null && fragment != null) {
transaction.attach(fragment)
} else {
// If fragment already exist remove it first
if (fragment != null) transaction.remove(fragment)
fragment = instantiateFragment(tabClass, state ?: defaultFragmentState)
transaction.add(contentContainerViewId, fragment, fragmentName)
}
transaction
.setPrimaryNavigationFragment(fragment)
.setReorderingAllowed(true)
.commit()
selectedTabId = itemId
}
// When you are in any tab instead of main you firstly navigate to main tab before exit application
fun onBackPressed() =
if (fragmentManager.primaryNavigationFragment?.childFragmentManager?.backStackEntryCount == 0
&& defaultTabId != 0
&& selectedTabId != defaultTabId) {
navigateTo(defaultTabId)
true
} else {
false
}
protected open fun getNavigationContainerClass(): Class<out BaseNavigationContainerFragment<*, *>> = BaseNavigationContainerFragment::class.java
protected open fun onTabReselected() {
onReselectListener?.invoke()
}
protected open fun isTabClass(tab: TNavigationTab, fragment: Fragment?) = if (wrapWithNavigationContainer) {
(fragment as BaseNavigationContainerFragment<*, *>).getContainedClass()
} else {
fragment?.javaClass
} == tab.cls
protected open fun instantiateFragment(clazz: Class<*>, state: Parcelable): Fragment =
if (wrapWithNavigationContainer) {
Fragment.instantiate(
context,
getNavigationContainerClass().name,
BaseNavigationContainerFragment.args(clazz, state, contentContainerViewId, contentContainerLayoutId)
)
} else {
Fragment.instantiate(
context,
clazz.name,
StatefulFragment.args(state)
)
}
private fun initializeCallback() {
callback = TabFragmentChangedCallback()
fragmentManager.registerFragmentLifecycleCallbacks(callback!!, false)
}
private inner class TabFragmentChangedCallback : FragmentManager.FragmentLifecycleCallbacks() {
// Set selected tab active, disabling all others. Used for styling
override fun onFragmentViewCreated(
fragmentManager: FragmentManager,
fragment: Fragment,
view: View,
savedInstanceState: Bundle?
) {
tabs.forEach { itemId, tab ->
if (isTabClass(tab, fragment)) {
tabsContainer!!.children.forEach { itemView -> itemView.isActivated = itemView.id == itemId }
}
}
}
}
}

View File

@ -0,0 +1,72 @@
package ru.touchin.roboswag.bottom_navigation_base
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import ru.touchin.roboswag.navigation_base.activities.BaseActivity
import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener
abstract class BaseBottomNavigationFragment<TNavigationType: BaseNavigationTab> : Fragment() {
protected abstract val rootLayoutId: Int
protected abstract val navigationContainerViewId: Int
protected abstract val contentContainerViewId: Int
protected abstract val contentContainerLayoutId: Int
protected abstract val defaultTabId: Int
protected abstract val wrapWithNavigationContainer: Boolean
protected abstract val tabs: SparseArray<TNavigationType>
protected open val backPressedListener = OnBackPressedListener { bottomNavigationController.onBackPressed() }
protected open val reselectListener: (() -> Unit) = { getNavigationActivity().innerNavigation.up(inclusive = true) }
private lateinit var bottomNavigationController: BaseBottomNavigationController<*>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bottomNavigationController = createNavigationController()
if (savedInstanceState == null) {
bottomNavigationController.navigateTo(defaultTabId)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val fragmentView = inflater.inflate(rootLayoutId, container, false)
bottomNavigationController.attach(fragmentView.findViewById(navigationContainerViewId))
(activity as BaseActivity).addOnBackPressedListener(backPressedListener)
return fragmentView
}
override fun onDestroyView() {
(activity as BaseActivity).removeOnBackPressedListener(backPressedListener)
bottomNavigationController.detach()
super.onDestroyView()
}
fun navigateTo(@IdRes navigationTabId: Int, state: Parcelable? = null) {
bottomNavigationController.navigateTo(navigationTabId, state)
}
protected abstract fun createNavigationController(): BaseBottomNavigationController<*>
protected fun getNavigationActivity() = requireActivity() as BaseBottomNavigationActivity<*, *, *>
}

View File

@ -0,0 +1,78 @@
package ru.touchin.roboswag.bottom_navigation_base
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.core.utils.ShouldNotHappenException
import ru.touchin.roboswag.navigation_base.FragmentNavigation
abstract class BaseNavigationContainerFragment<TContained, TNavigation : FragmentNavigation> : Fragment() {
companion object {
const val TRANSITION_ARG = "TRANSITION_ARG"
const val FRAGMENT_STATE_ARG = "FRAGMENT_STATE_ARG"
const val CONTAINED_CLASS_ARG = "FRAGMENT_CLASS_ARG"
const val CONTAINER_VIEW_ID_ARG = "CONTAINER_VIEW_ID_ARG"
const val CONTAINER_LAYOUT_ID_ARG = "CONTAINER_LAYOUT_ID_ARG"
fun args(
cls: Class<*>,
state: Parcelable,
@IdRes containerViewId: Int,
@LayoutRes containerLayoutId: Int,
transition: Int = FragmentTransaction.TRANSIT_NONE
) = Bundle().apply {
putInt(TRANSITION_ARG, transition)
putInt(CONTAINER_VIEW_ID_ARG, containerViewId)
putInt(CONTAINER_LAYOUT_ID_ARG, containerLayoutId)
putParcelable(FRAGMENT_STATE_ARG, state)
putSerializable(CONTAINED_CLASS_ARG, cls)
}
}
abstract val navigation: TNavigation
@IdRes
protected var containerViewId = 0
private set
@LayoutRes
protected var containerLayoutId = 0
private set
protected var transition = 0
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { args ->
transition = args.getInt(TRANSITION_ARG)
containerViewId = args.getInt(CONTAINER_VIEW_ID_ARG)
containerLayoutId = args.getInt(CONTAINER_LAYOUT_ID_ARG)
if (savedInstanceState == null) {
onContainerCreated()
}
} ?: throw ShouldNotHappenException("Fragment is not instantiable without arguments")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(containerLayoutId, container, false)
protected abstract fun onContainerCreated()
@Suppress("UNCHECKED_CAST")
fun getContainedClass(): Class<TContained> =
arguments?.getSerializable(CONTAINED_CLASS_ARG) as Class<TContained>
}

View File

@ -0,0 +1,25 @@
package ru.touchin.roboswag.bottom_navigation_base
import android.os.Parcelable
import ru.touchin.roboswag.navigation_base.extensions.copy
open class BaseNavigationTab(
open val cls: Class<*>,
state: Parcelable,
/**
* It can be useful in some cases when it is necessary to create ViewController
* with initial state every time when tab opens.
*/
val saveStateOnSwitching: Boolean = true
) {
/**
* It is value as class body property instead of value as constructor parameter to specify
* custom getter of this field which returns copy of Parcelable every time it be called.
* This is necessary to avoid modifying this value if it would be a value as constructor parameter
* and every getting of this value would return the same instance.
*/
val state = state
get() = field.copy()
}

View File

@ -12,10 +12,16 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
api project(":navigation")
implementation project(":navigation-base")
implementation project(":bottom-navigation-base")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

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

View File

@ -0,0 +1,18 @@
package ru.touchin.roboswag.bottom_navigation_fragment
import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationActivity
import ru.touchin.roboswag.navigation_base.FragmentNavigation
abstract class BottomNavigationActivity :
BaseBottomNavigationActivity<FragmentNavigation, BottomNavigationFragment, NavigationContainerFragment>() {
override val navigation by lazy {
FragmentNavigation(
this,
supportFragmentManager,
fragmentContainerViewId,
transition
)
}
}

View File

@ -0,0 +1,32 @@
package ru.touchin.roboswag.bottom_navigation_fragment
import android.content.Context
import android.util.SparseArray
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationController
class BottomNavigationController(
context: Context,
fragments: SparseArray<NavigationTab>,
fragmentManager: FragmentManager,
wrapWithNavigationContainer: Boolean = false,
@LayoutRes private val contentContainerLayoutId: Int,
@IdRes private val contentContainerViewId: Int,
@IdRes private val defaultTabId: Int = 0, // If it zero back press with empty fragment back stack would close the app
onReselectListener: (() -> Unit)? = null
) : BaseBottomNavigationController<NavigationTab>(
tabs = fragments,
context = context,
fragmentManager = fragmentManager,
defaultTabId = defaultTabId,
onReselectListener = onReselectListener,
contentContainerViewId = contentContainerViewId,
contentContainerLayoutId = contentContainerLayoutId,
wrapWithNavigationContainer = wrapWithNavigationContainer
) {
override fun getNavigationContainerClass() = NavigationContainerFragment::class.java
}

View File

@ -0,0 +1,18 @@
package ru.touchin.roboswag.bottom_navigation_fragment
import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationFragment
abstract class BottomNavigationFragment : BaseBottomNavigationFragment<NavigationTab>() {
override fun createNavigationController() = BottomNavigationController(
context = requireContext(),
fragments = tabs,
fragmentManager = childFragmentManager,
defaultTabId = defaultTabId,
contentContainerViewId = contentContainerViewId,
contentContainerLayoutId = contentContainerLayoutId,
wrapWithNavigationContainer = wrapWithNavigationContainer,
onReselectListener = reselectListener
)
}

View File

@ -0,0 +1,24 @@
package ru.touchin.roboswag.bottom_navigation_fragment
import android.os.Parcelable
import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationContainerFragment
import ru.touchin.roboswag.navigation_base.FragmentNavigation
import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment
class NavigationContainerFragment :
BaseNavigationContainerFragment<StatefulFragment<out BottomNavigationActivity, Parcelable>, FragmentNavigation>() {
override val navigation by lazy {
FragmentNavigation(
requireContext(),
childFragmentManager,
containerViewId,
transition
)
}
override fun onContainerCreated() {
navigation.setInitial(getContainedClass().kotlin, arguments?.getParcelable(FRAGMENT_STATE_ARG)!!)
}
}

View File

@ -0,0 +1,11 @@
package ru.touchin.roboswag.bottom_navigation_fragment
import android.os.Parcelable
import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationTab
import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment
class NavigationTab(
override val cls: Class<out StatefulFragment<*, *>>,
state: Parcelable,
saveStateOnSwitching: Boolean = true
) : BaseNavigationTab(cls, state, saveStateOnSwitching)

View File

@ -0,0 +1,32 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion versions.compileSdk
defaultConfig {
minSdkVersion 16
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation project(":navigation-base")
implementation project(":navigation-viewcontroller")
implementation project(":bottom-navigation-base")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.core:core-ktx:$versions.coreKtx"
implementation "androidx.appcompat:appcompat:$versions.appcompat"
}

View File

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

View File

@ -0,0 +1,18 @@
package ru.touchin.roboswag.bottom_navigation_viewcontroller
import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationActivity
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewControllerNavigation
abstract class BottomNavigationActivity :
BaseBottomNavigationActivity<ViewControllerNavigation<BottomNavigationActivity>, BottomNavigationFragment, NavigationContainerFragment>() {
final override val navigation by lazy {
ViewControllerNavigation<BottomNavigationActivity>(
this,
supportFragmentManager,
fragmentContainerViewId,
transition
)
}
}

View File

@ -0,0 +1,45 @@
package ru.touchin.roboswag.bottom_navigation_viewcontroller
import android.content.Context
import android.util.SparseArray
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationController
import ru.touchin.roboswag.navigation_viewcontroller.fragments.ViewControllerFragment
class BottomNavigationController(
context: Context,
fragmentManager: FragmentManager,
viewControllers: SparseArray<NavigationTab>,
private val wrapWithNavigationContainer: Boolean = false,
@IdRes private val defaultTabId: Int = 0, // If it zero back press with empty fragment back stack would close the app
@IdRes private val contentContainerViewId: Int,
@LayoutRes private val contentContainerLayoutId: Int,
private val onReselectListener: (() -> Unit)? = null
) : BaseBottomNavigationController<NavigationTab>(
context = context,
fragmentManager = fragmentManager,
tabs = viewControllers,
defaultTabId = defaultTabId,
contentContainerViewId = contentContainerViewId,
contentContainerLayoutId = contentContainerLayoutId,
wrapWithNavigationContainer = wrapWithNavigationContainer
) {
override fun onTabReselected() {
onReselectListener?.invoke()
}
override fun getNavigationContainerClass() = NavigationContainerFragment::class.java
override fun isTabClass(tab: NavigationTab, fragment: Fragment?): Boolean =
if (wrapWithNavigationContainer) {
super.isTabClass(tab, fragment)
} else {
(fragment as ViewControllerFragment<*, *>).viewControllerClass
} === tab.cls
}

View File

@ -0,0 +1,19 @@
package ru.touchin.roboswag.bottom_navigation_viewcontroller
import ru.touchin.roboswag.bottom_navigation_base.BaseBottomNavigationFragment
abstract class BottomNavigationFragment : BaseBottomNavigationFragment<NavigationTab>() {
override fun createNavigationController() = BottomNavigationController(
context = requireContext(),
fragmentManager = childFragmentManager,
viewControllers = tabs,
defaultTabId = defaultTabId,
contentContainerViewId = contentContainerViewId,
contentContainerLayoutId = contentContainerLayoutId,
wrapWithNavigationContainer = wrapWithNavigationContainer,
onReselectListener = reselectListener
)
}

View File

@ -0,0 +1,26 @@
package ru.touchin.roboswag.bottom_navigation_viewcontroller
import android.os.Parcelable
import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationContainerFragment
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewControllerNavigation
class NavigationContainerFragment :
BaseNavigationContainerFragment<
ViewController<out BottomNavigationActivity, Parcelable>,
ViewControllerNavigation<BottomNavigationActivity>>() {
override val navigation by lazy {
ViewControllerNavigation<BottomNavigationActivity>(
requireContext(),
childFragmentManager,
containerViewId,
transition
)
}
override fun onContainerCreated() {
navigation.setInitialViewController(getContainedClass(), arguments?.getParcelable(FRAGMENT_STATE_ARG)!!)
}
}

View File

@ -0,0 +1,15 @@
package ru.touchin.roboswag.bottom_navigation_viewcontroller
import android.os.Parcelable
import ru.touchin.roboswag.bottom_navigation_base.BaseNavigationTab
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
class NavigationTab(
override val cls: Class<out ViewController<*, *>>,
state: Parcelable,
/**
* It can be useful in some cases when it is necessary to create ViewController
* with initial state every time when tab opens.
*/
saveStateOnSwitching: Boolean = true
) : BaseNavigationTab(cls, state, saveStateOnSwitching)

View File

@ -0,0 +1,35 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion versions.compileSdk
defaultConfig {
minSdkVersion 16
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation project(":lifecycle")
implementation project(":navigation-viewcontroller")
compileOnly "javax.inject:javax.inject:1"
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 "androidx.lifecycle:lifecycle-extensions:$versions.lifecycle"
}

View File

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

View File

@ -0,0 +1,37 @@
package ru.touchin.lifecycle_viewcontroller.extensions
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import ru.touchin.lifecycle_viewcontroller.viewmodel.LifecycleViewModelProviders
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
import androidx.fragment.app.activityViewModels as androidActivityViewModels
import androidx.fragment.app.viewModels as androidViewModels
@MainThread
inline fun <reified VM : ViewModel> ViewController<*, *>.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this.fragment },
noinline factoryProducer: () -> ViewModelProvider.Factory = { LifecycleViewModelProviders.getViewModelFactory(this) }
) = this.fragment.androidViewModels<VM>(ownerProducer, factoryProducer)
@MainThread
inline fun <reified VM : ViewModel> ViewController<*, *>.parentViewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this.fragment.parentFragment!! },
noinline factoryProducer: () -> ViewModelProvider.Factory = {
LifecycleViewModelProviders.getViewModelFactory(this.fragment.parentFragment!!)
}
) = viewModels<VM>(ownerProducer, factoryProducer)
@MainThread
inline fun <reified VM : ViewModel> ViewController<*, *>.targetViewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this.fragment.targetFragment!! },
noinline factoryProducer: () -> ViewModelProvider.Factory = {
LifecycleViewModelProviders.getViewModelFactory(this.fragment.targetFragment!!)
}
) = viewModels<VM>(ownerProducer, factoryProducer)
@MainThread
inline fun <reified VM : ViewModel> ViewController<*, *>.activityViewModels(
noinline factoryProducer: () -> ViewModelProvider.Factory = { LifecycleViewModelProviders.getViewModelFactory(activity) }
) = this.fragment.androidActivityViewModels<VM>(factoryProducer)

View File

@ -0,0 +1,45 @@
package ru.touchin.lifecycle_viewcontroller.viewmodel
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import ru.touchin.lifecycle.viewmodel.BaseLifecycleViewModelProviders
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
object LifecycleViewModelProviders : BaseLifecycleViewModelProviders() {
/**
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
* {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
* <p>
* It uses the given {@link Factory} to instantiate new ViewModels.
*
* @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
* @param factory a {@code Factory} to instantiate new ViewModels
* @return a ViewModelProvider instance
*/
override fun of(
lifecycleOwner: LifecycleOwner,
factory: ViewModelProvider.Factory
): ViewModelProvider =
when (lifecycleOwner) {
is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory)
else -> super.of(lifecycleOwner, factory)
}
/**
* Returns ViewModelProvider.Factory instance from current lifecycleOwner.
* Search #ViewModelFactoryProvider are produced according to priorities:
* 1. View controller;
* 2. Fragment;
* 3. Parent fragment recursively;
* 4. Activity;
* 5. Application.
*/
override fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
when (provider) {
is ViewController<*, *> -> getViewModelFactory(provider.fragment)
else -> super.getViewModelFactory(provider)
}
}

View File

@ -19,8 +19,6 @@ android {
}
dependencies {
api project(":navigation")
compileOnly "javax.inject:javax.inject:1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

@ -0,0 +1,47 @@
package ru.touchin.lifecycle.viewmodel
import android.app.Activity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
abstract class BaseLifecycleViewModelProviders {
/**
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
* {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
* <p>
* It uses the given {@link Factory} to instantiate new ViewModels.
*
* @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
* @param factory a {@code Factory} to instantiate new ViewModels
* @return a ViewModelProvider instance
*/
open fun of(
lifecycleOwner: LifecycleOwner,
factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)
): ViewModelProvider =
when (lifecycleOwner) {
is Fragment -> ViewModelProvider(lifecycleOwner, factory)
is FragmentActivity -> ViewModelProvider(lifecycleOwner, factory)
else -> throw IllegalArgumentException("Not supported LifecycleOwner.")
}
/**
* Returns ViewModelProvider.Factory instance from current lifecycleOwner.
* Search #ViewModelFactoryProvider are produced according to priorities:
* 1. Fragment;
* 2. Parent fragment recursively;
* 3. Activity;
* 4. Application.
*/
open fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
when (provider) {
is ViewModelFactoryProvider -> provider.viewModelFactory
is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity())
is Activity -> getViewModelFactory(provider.application)
else -> throw IllegalArgumentException("View model factory not found.")
}
}

View File

@ -1,49 +1,3 @@
package ru.touchin.lifecycle.viewmodel
import android.app.Activity
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
object LifecycleViewModelProviders {
/**
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
* {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}.
* <p>
* It uses the given {@link Factory} to instantiate new ViewModels.
*
* @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity)
* @param factory a {@code Factory} to instantiate new ViewModels
* @return a ViewModelProvider instance
*/
fun of(lifecycleOwner: LifecycleOwner, factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)): ViewModelProvider =
when (lifecycleOwner) {
is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory)
is Fragment -> ViewModelProviders.of(lifecycleOwner, factory)
is FragmentActivity -> ViewModelProviders.of(lifecycleOwner, factory)
else -> throw IllegalArgumentException("Not supported LifecycleOwner.")
}
/**
* Returns ViewModelProvider.Factory instance from current lifecycleOwner.
* Search #ViewModelFactoryProvider are produced according to priorities:
* 1. View controller;
* 2. Fragment;
* 3. Parent fragment recursively;
* 4. Activity;
* 5. Application.
*/
fun getViewModelFactory(provider: Any): ViewModelProvider.Factory =
when (provider) {
is ViewModelFactoryProvider -> provider.viewModelFactory
is ViewController<*, *> -> getViewModelFactory(provider.fragment)
is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity())
is Activity -> getViewModelFactory(provider.application)
else -> throw IllegalArgumentException("View model factory not found.")
}
}
object LifecycleViewModelProviders : BaseLifecycleViewModelProviders()

View File

@ -15,4 +15,8 @@ android {
dependencies {
implementation "androidx.annotation:annotation:$versions.androidx"
implementation("com.crashlytics.sdk.android:crashlytics:$versions.crashlytics@aar") {
transitive = true
}
}

View File

@ -19,12 +19,11 @@
package ru.touchin.roboswag.core.log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.SimpleDateFormat;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import ru.touchin.roboswag.core.utils.ThreadLocalValue;
@ -45,6 +44,10 @@ public class LcGroup {
* Logging group to log UI lifecycle (onCreate, onStart, onResume etc.).
*/
public static final LcGroup UI_LIFECYCLE = new LcGroup("UI_LIFECYCLE");
/**
* Logging group to log api validation errors.
*/
public static final LcGroup API_VALIDATION = new LcGroup("API_VALIDATION");
private static final ThreadLocalValue<SimpleDateFormat> DATE_TIME_FORMATTER
= new ThreadLocalValue<>(() -> new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()));

View File

@ -0,0 +1,66 @@
package ru.touchin.roboswag.core.utils;
import android.util.Log;
import com.crashlytics.android.Crashlytics;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.log.LcGroup;
import ru.touchin.roboswag.core.log.LcLevel;
import ru.touchin.roboswag.core.log.LogProcessor;
public class CrashlyticsLogProcessor extends LogProcessor {
@NonNull
private final Crashlytics crashlytics;
public CrashlyticsLogProcessor(@NonNull final Crashlytics crashlytics) {
super(LcLevel.INFO);
this.crashlytics = crashlytics;
}
@Override
public void processLogMessage(@NonNull final LcGroup group,
@NonNull final LcLevel level,
@NonNull final String tag,
@NonNull final String message,
@Nullable final Throwable throwable) {
if (group == LcGroup.UI_LIFECYCLE) {
crashlytics.core.log(level.getPriority(), tag, message);
} else if (!level.lessThan(LcLevel.ASSERT)
|| (group == LcGroup.API_VALIDATION && level == LcLevel.ERROR)) {
Log.e(tag, message);
if (throwable != null) {
crashlytics.core.log(level.getPriority(), tag, message);
crashlytics.core.logException(throwable);
} else {
final ShouldNotHappenException exceptionToLog = new ShouldNotHappenException(tag + ':' + message);
reduceStackTrace(exceptionToLog);
crashlytics.core.logException(exceptionToLog);
}
}
}
private void reduceStackTrace(@NonNull final Throwable throwable) {
final StackTraceElement[] stackTrace = throwable.getStackTrace();
final List<StackTraceElement> reducedStackTraceList = new ArrayList<>();
for (int i = stackTrace.length - 1; i >= 0; i--) {
final StackTraceElement stackTraceElement = stackTrace[i];
if (stackTraceElement.getClassName().contains(getClass().getSimpleName())
|| stackTraceElement.getClassName().contains(LcGroup.class.getName())
|| stackTraceElement.getClassName().contains(Lc.class.getName())) {
break;
}
reducedStackTraceList.add(0, stackTraceElement);
}
StackTraceElement[] reducedStackTrace = new StackTraceElement[reducedStackTraceList.size()];
reducedStackTrace = reducedStackTraceList.toArray(reducedStackTrace);
throwable.setStackTrace(reducedStackTrace);
}
}

1
navigation-base/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -13,17 +13,20 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
api project(":utils")
api project(":logging")
api project(":navigation")
api project(":api-logansquare")
implementation project(":utils")
implementation project(":logging")
api 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.multidex:multidex:2.0.1'
api 'net.danlew:android.joda:2.10.2'
implementation 'net.danlew:android.joda:2.10.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

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

View File

@ -17,7 +17,7 @@
*
*/
package ru.touchin.roboswag.components.navigation_new
package ru.touchin.roboswag.navigation_base
import android.content.Context
import android.os.Bundle
@ -28,8 +28,7 @@ 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_new.fragments.BaseFragment
import ru.touchin.roboswag.components.navigation.viewcontrollers.EmptyState
import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment
import kotlin.reflect.KClass
/**
@ -96,6 +95,7 @@ open class FragmentNavigation(
addToStack: Boolean,
args: Bundle?,
backStackName: String?,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)?
) {
if (fragmentManager.isDestroyed) {
@ -108,7 +108,7 @@ open class FragmentNavigation(
val fragmentTransaction = fragmentManager.beginTransaction()
transactionSetup?.invoke(fragmentTransaction)
fragmentTransaction.replace(containerViewId, fragment, null)
fragmentTransaction.replace(containerViewId, fragment, tag)
if (addToStack) {
fragmentTransaction
.addToBackStack(backStackName)
@ -155,9 +155,10 @@ open class FragmentNavigation(
args: Bundle? = null,
addToStack: Boolean = true,
backStackName: String? = null,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, backStackName, transactionSetup)
addToStack(fragmentClass, null, 0, addToStack, args, backStackName, tag, transactionSetup)
}
/**
@ -167,14 +168,15 @@ open class FragmentNavigation(
* @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 <T: Parcelable> push(
fragmentClass: KClass<out BaseFragment<*, out T>>,
state: T? = null,
fun <T : Parcelable> push(
fragmentClass: KClass<out StatefulFragment<*, out T>>,
state: T,
addToStack: Boolean = true,
backStackName: String? = null,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
push(fragmentClass.java, BaseFragment.args(state ?: EmptyState), addToStack, backStackName, transactionSetup)
push(fragmentClass.java, StatefulFragment.args(state), addToStack, backStackName, tag, transactionSetup)
}
/**
@ -190,6 +192,7 @@ open class FragmentNavigation(
targetFragment: Fragment,
targetRequestCode: Int,
args: Bundle? = null,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
@ -199,6 +202,7 @@ open class FragmentNavigation(
true,
args,
null,
tag,
transactionSetup
)
}
@ -211,14 +215,15 @@ open class FragmentNavigation(
* @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 <T: Parcelable> pushForResult(
fragmentClass: KClass<out BaseFragment<*, out T>>,
fun <T : Parcelable> pushForResult(
fragmentClass: KClass<out StatefulFragment<*, out T>>,
targetFragment: Fragment,
targetRequestCode: Int,
state: T? = null,
state: T,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
pushForResult(fragmentClass.java, targetFragment, targetRequestCode, BaseFragment.args(state ?: EmptyState), transactionSetup)
pushForResult(fragmentClass.java, targetFragment, targetRequestCode, StatefulFragment.args(state), tag, transactionSetup)
}
/**
@ -233,9 +238,10 @@ open class FragmentNavigation(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
addToStack: Boolean = true,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, transactionSetup)
addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, tag, transactionSetup)
}
/**
@ -249,10 +255,11 @@ open class FragmentNavigation(
fun setInitial(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
setAsTop(fragmentClass, args, false, transactionSetup)
setAsTop(fragmentClass, args, false, tag, transactionSetup)
}
/**
@ -262,13 +269,14 @@ open class FragmentNavigation(
* @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 <T: Parcelable> setInitial(
fragmentClass: KClass<out BaseFragment<*, out T>>,
state: T? = null,
fun <T : Parcelable> setInitial(
fragmentClass: KClass<out StatefulFragment<*, out T>>,
state: T,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
setAsTop(fragmentClass.java, BaseFragment.args(state ?: EmptyState), false, transactionSetup)
setAsTop(fragmentClass.java, StatefulFragment.args(state), false, tag, transactionSetup)
}
/**

View File

@ -17,7 +17,7 @@
*
*/
package ru.touchin.roboswag.components.navigation
package ru.touchin.roboswag.navigation_base
import android.animation.ValueAnimator
import android.view.MenuItem
@ -25,9 +25,9 @@ import android.view.View
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
import ru.touchin.roboswag.components.utils.UiUtils
import ru.touchin.roboswag.navigation_base.activities.BaseActivity
import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener
/**
* Created by Gavriil Sitnikov on 11/03/16.

View File

@ -17,7 +17,7 @@
*
*/
package ru.touchin.roboswag.components.navigation;
package ru.touchin.roboswag.navigation_base;
import android.app.Application;
import android.content.Context;
@ -28,20 +28,14 @@ import com.crashlytics.android.Crashlytics;
import net.danlew.android.joda.JodaTimeAndroid;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.multidex.MultiDex;
import io.fabric.sdk.android.Fabric;
import ru.touchin.roboswag.core.log.ConsoleLogProcessor;
import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.log.LcGroup;
import ru.touchin.roboswag.core.log.LcLevel;
import ru.touchin.roboswag.core.log.LogProcessor;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import ru.touchin.templates.ApiModel;
import ru.touchin.roboswag.core.utils.CrashlyticsLogProcessor;
/**
* Created by Gavriil Sitnikov on 10/03/16.
@ -93,55 +87,4 @@ public abstract class TouchinApp extends Application {
.build());
}
private static class CrashlyticsLogProcessor extends LogProcessor {
@NonNull
private final Crashlytics crashlytics;
public CrashlyticsLogProcessor(@NonNull final Crashlytics crashlytics) {
super(LcLevel.INFO);
this.crashlytics = crashlytics;
}
@Override
public void processLogMessage(@NonNull final LcGroup group,
@NonNull final LcLevel level,
@NonNull final String tag,
@NonNull final String message,
@Nullable final Throwable throwable) {
if (group == LcGroup.UI_LIFECYCLE) {
crashlytics.core.log(level.getPriority(), tag, message);
} else if (!level.lessThan(LcLevel.ASSERT)
|| (group == ApiModel.API_VALIDATION_LC_GROUP && level == LcLevel.ERROR)) {
Log.e(tag, message);
if (throwable != null) {
crashlytics.core.log(level.getPriority(), tag, message);
crashlytics.core.logException(throwable);
} else {
final ShouldNotHappenException exceptionToLog = new ShouldNotHappenException(tag + ':' + message);
reduceStackTrace(exceptionToLog);
crashlytics.core.logException(exceptionToLog);
}
}
}
private void reduceStackTrace(@NonNull final Throwable throwable) {
final StackTraceElement[] stackTrace = throwable.getStackTrace();
final List<StackTraceElement> reducedStackTraceList = new ArrayList<>();
for (int i = stackTrace.length - 1; i >= 0; i--) {
final StackTraceElement stackTraceElement = stackTrace[i];
if (stackTraceElement.getClassName().contains(getClass().getSimpleName())
|| stackTraceElement.getClassName().contains(LcGroup.class.getName())
|| stackTraceElement.getClassName().contains(Lc.class.getName())) {
break;
}
reducedStackTraceList.add(0, stackTraceElement);
}
StackTraceElement[] reducedStackTrace = new StackTraceElement[reducedStackTraceList.size()];
reducedStackTrace = reducedStackTraceList.toArray(reducedStackTrace);
throwable.setStackTrace(reducedStackTrace);
}
}
}

View File

@ -17,7 +17,7 @@
*
*/
package ru.touchin.roboswag.components.navigation.activities
package ru.touchin.roboswag.navigation_base.activities
import android.content.Context
import android.content.Intent
@ -26,10 +26,10 @@ import android.os.Bundle
import android.os.PersistableBundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import ru.touchin.roboswag.components.navigation.keyboard_resizeable.KeyboardBehaviorDetector
import ru.touchin.roboswag.components.navigation.viewcontrollers.LifecycleLoggingObserver
import ru.touchin.roboswag.core.log.Lc
import ru.touchin.roboswag.core.log.LcGroup
import ru.touchin.roboswag.navigation_base.fragments.LifecycleLoggingObserver
import ru.touchin.roboswag.navigation_base.keyboard_resizeable.KeyboardBehaviorDetector
/**
* Created by Gavriil Sitnikov on 08/03/2016.

View File

@ -0,0 +1,14 @@
package ru.touchin.roboswag.navigation_base.activities
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.navigation_base.FragmentNavigation
abstract class NavigationActivity<TNavigation : FragmentNavigation> : BaseActivity() {
protected abstract val fragmentContainerViewId: Int
protected open val transition = FragmentTransaction.TRANSIT_NONE
abstract val navigation: TNavigation
}

View File

@ -1,4 +1,4 @@
package ru.touchin.roboswag.components.navigation.activities;
package ru.touchin.roboswag.navigation_base.activities;
public interface OnBackPressedListener {

View File

@ -0,0 +1,50 @@
package ru.touchin.roboswag.navigation_base.extensions
import android.annotation.SuppressLint
import android.os.Parcel
import android.os.Parcelable
import ru.touchin.roboswag.navigation_base.fragments.EmptyState
// 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
@SuppressLint("Recycle")
fun <T : Parcelable> Parcelable.reserialize(): T {
var parcel = Parcel.obtain()
parcel.writeParcelable(this, 0)
val serializableBytes = parcel.marshall()
parcel.recycle()
parcel = Parcel.obtain().apply {
unmarshall(serializableBytes, 0, serializableBytes.size)
setDataPosition(0)
}
val result = parcel.readParcelable<T>(Thread.currentThread().contextClassLoader)
?: throw IllegalStateException("It must not be null")
parcel.recycle()
return result
}
@SuppressLint("Recycle")
fun Parcelable.copy(): Parcelable =
if (this is EmptyState) {
EmptyState
} else {
val parcel = Parcel.obtain()
parcel.writeParcelable(this, 0)
parcel.setDataPosition(0)
val result = parcel.readParcelable<Parcelable>(
javaClass.classLoader ?: Thread.currentThread().contextClassLoader
) ?: throw IllegalStateException("Failed to copy tab state")
parcel.recycle()
result
}

View File

@ -0,0 +1,53 @@
package ru.touchin.roboswag.navigation_base.fragments
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.os.Bundle
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
open class BaseFragment<TActivity : FragmentActivity> : Fragment {
constructor() : super()
constructor(@LayoutRes layoutRes: Int) : super(layoutRes)
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()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycle.addObserver(LifecycleLoggingObserver(this))
}
fun <T : View> 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)
}

View File

@ -1,4 +1,4 @@
package ru.touchin.roboswag.components.navigation.viewcontrollers
package ru.touchin.roboswag.navigation_base.fragments
import android.os.Parcel
import android.os.Parcelable

View File

@ -1,4 +1,4 @@
package ru.touchin.roboswag.components.navigation.viewcontrollers
package ru.touchin.roboswag.navigation_base.fragments
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver

View File

@ -0,0 +1,41 @@
package ru.touchin.roboswag.navigation_base.fragments
import android.os.Bundle
import android.os.Parcelable
import androidx.annotation.LayoutRes
import androidx.fragment.app.FragmentActivity
import ru.touchin.roboswag.navigation_base.BuildConfig
import ru.touchin.roboswag.navigation_base.extensions.reserialize
open class StatefulFragment<TActivity : FragmentActivity, TState : Parcelable>(
@LayoutRes layoutRes: Int
) : BaseFragment<TActivity>(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) }
}
protected lateinit var state: TState
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
state = savedInstanceState?.getParcelable<TState>(BASE_FRAGMENT_STATE_EXTRA)
?: arguments?.getParcelable(BASE_FRAGMENT_STATE_EXTRA)
?: throw IllegalStateException("Fragment state can't be null")
if (BuildConfig.DEBUG) {
state = state.reserialize()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state)
}
}

View File

@ -1,11 +1,11 @@
package ru.touchin.roboswag.components.navigation.keyboard_resizeable
package ru.touchin.roboswag.navigation_base.keyboard_resizeable
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
import ru.touchin.roboswag.navigation_base.activities.BaseActivity
/**
* This detector NOT detect landscape fullscreen keyboard

View File

@ -1,4 +1,4 @@
package ru.touchin.roboswag.components.navigation_new.keyboard_resizeable
package ru.touchin.roboswag.navigation_base.keyboard_resizeable
import android.os.Build
import android.os.Bundle
@ -6,14 +6,14 @@ import android.os.Parcelable
import android.view.View
import androidx.annotation.LayoutRes
import androidx.lifecycle.LifecycleObserver
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
import ru.touchin.roboswag.components.utils.UiUtils
import ru.touchin.roboswag.navigation_base.activities.BaseActivity
import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener
import ru.touchin.roboswag.navigation_base.fragments.StatefulFragment
abstract class KeyboardResizeableFragment<TActivity : BaseActivity, TState : Parcelable>(
@LayoutRes layoutRes: Int
) : BaseFragment<TActivity, TState>(
) : StatefulFragment<TActivity, TState>(
layoutRes
) {

View File

@ -1,3 +0,0 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.touchin.roboswag.components.navigation_new"/>

View File

@ -1,26 +0,0 @@
package ru.touchin.roboswag.components.navigation_new.activities
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
import ru.touchin.roboswag.components.navigation_new.FragmentNavigation
/**
* Created by Daniil Borisovskii on 15/08/2019.
* Base activity with nested navigation.
*/
abstract class NavigationActivity : BaseActivity() {
protected abstract val fragmentContainerViewId: Int
protected open val transition = FragmentTransaction.TRANSIT_NONE
open val navigation by lazy {
FragmentNavigation(
this,
supportFragmentManager,
fragmentContainerViewId,
transition
)
}
}

View File

@ -1,90 +0,0 @@
package ru.touchin.roboswag.components.navigation_new.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 ru.touchin.roboswag.components.navigation_new.BuildConfig
import ru.touchin.roboswag.components.navigation.viewcontrollers.LifecycleLoggingObserver
open class BaseFragment<TActivity : FragmentActivity, TState : Parcelable>(@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 <T : Parcelable> 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<T>(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
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
state = savedInstanceState?.getParcelable<TState>(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(this))
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(BASE_FRAGMENT_STATE_EXTRA, state)
}
fun <T : View> 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)
}

1
navigation-viewcontroller/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -13,16 +13,21 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
api project(":utils")
api project(":logging")
api project(":api-logansquare")
implementation project(":utils")
implementation project(":logging")
implementation project(":navigation-base")
api 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.multidex:multidex:2.0.1'
api 'net.danlew:android.joda:2.10.2'
implementation 'net.danlew:android.joda:2.10.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

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

View File

@ -17,7 +17,7 @@
*
*/
package ru.touchin.roboswag.components.navigation.fragments
package ru.touchin.roboswag.navigation_viewcontroller.fragments
import android.animation.Animator
import android.annotation.SuppressLint
@ -35,8 +35,8 @@ import android.view.animation.Animation
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import ru.touchin.roboswag.components.navigation.BuildConfig
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
import ru.touchin.roboswag.navigation_viewcontroller.BuildConfig
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
/**
* Created by Gavriil Sitnikov on 21/10/2015.

View File

@ -1,14 +1,14 @@
package ru.touchin.roboswag.components.navigation.keyboard_resizeable
package ru.touchin.roboswag.navigation_viewcontroller.keyboard_resizeable
import android.os.Build
import android.os.Parcelable
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.lifecycle.LifecycleObserver
import ru.touchin.roboswag.components.navigation.activities.BaseActivity
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
import ru.touchin.roboswag.components.utils.UiUtils
import ru.touchin.roboswag.navigation_base.activities.BaseActivity
import ru.touchin.roboswag.navigation_base.activities.OnBackPressedListener
import ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers.ViewController
abstract class KeyboardResizeableViewController<TActivity : BaseActivity, TState : Parcelable>(
@LayoutRes layoutRes: Int,
@ -17,6 +17,7 @@ abstract class KeyboardResizeableViewController<TActivity : BaseActivity, TState
creationContext,
layoutRes
) {
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
creationContext.container?.requestApplyInsets()
@ -85,4 +86,5 @@ abstract class KeyboardResizeableViewController<TActivity : BaseActivity, TState
if (isKeyboardVisible) onKeyboardHide()
isKeyboardVisible = false
}
}

View File

@ -17,7 +17,7 @@
*
*/
package ru.touchin.roboswag.components.navigation.viewcontrollers
package ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers
import android.animation.Animator
import android.content.Intent
@ -44,8 +44,9 @@ import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
import ru.touchin.roboswag.components.utils.UiUtils
import ru.touchin.roboswag.navigation_base.fragments.LifecycleLoggingObserver
import ru.touchin.roboswag.navigation_viewcontroller.fragments.ViewControllerFragment
/**
* Created by Gavriil Sitnikov on 21/10/2015.

View File

@ -17,7 +17,7 @@
*
*/
package ru.touchin.roboswag.components.navigation.viewcontrollers
package ru.touchin.roboswag.navigation_viewcontroller.viewcontrollers
import android.content.Context
import android.os.Parcelable
@ -26,9 +26,8 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.components.navigation.FragmentNavigation
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
import ru.touchin.roboswag.navigation_base.FragmentNavigation
import ru.touchin.roboswag.navigation_viewcontroller.fragments.ViewControllerFragment
/**
* Created by Gavriil Sitnikov on 07/03/2016.
@ -64,14 +63,14 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
ViewControllerFragment::class.java,
null,
0,
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
backStackName,
tag,
transactionSetup
fragmentClass = ViewControllerFragment::class.java,
targetFragment = null,
targetRequestCode = 0,
addToStack = addToStack,
args = ViewControllerFragment.args(viewControllerClass, state),
backStackName = backStackName,
tag = tag,
transactionSetup = transactionSetup
)
}
@ -98,14 +97,14 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
ViewControllerFragment::class.java,
targetFragment,
targetRequestCode,
true,
ViewControllerFragment.args(viewControllerClass, state),
backStackName,
tag,
transactionSetup
fragmentClass = ViewControllerFragment::class.java,
targetFragment = targetFragment,
targetRequestCode = targetRequestCode,
addToStack = true,
args = ViewControllerFragment.args(viewControllerClass, state),
backStackName = backStackName,
tag = tag,
transactionSetup = transactionSetup
)
}
@ -127,14 +126,14 @@ open class ViewControllerNavigation<TActivity : FragmentActivity>(
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
ViewControllerFragment::class.java,
null,
0,
addToStack,
ViewControllerFragment.args(viewControllerClass, state),
TOP_FRAGMENT_TAG_MARK,
tag,
transactionSetup
fragmentClass = ViewControllerFragment::class.java,
targetFragment = null,
targetRequestCode = 0,
addToStack = addToStack,
args = ViewControllerFragment.args(viewControllerClass, state),
backStackName = TOP_FRAGMENT_TAG_MARK,
tag = tag,
transactionSetup = transactionSetup
)
}

View File

@ -1,3 +0,0 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.touchin.roboswag.components.navigation"/>

View File

@ -1,244 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.navigation
import android.content.Context
import android.os.Bundle
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
/**
* Created by Gavriil Sitnikov on 07/03/2016.
* Navigation which is controlling fragments on activity using [FragmentManager].
* Basically there are 4 main actions to add fragments to activity.
* 1) [.setInitial] means to set fragment on top and remove all previously added fragments from stack;
* 2) [.push] means to simply add fragment on top of the stack;
* 3) [.setAsTop] means to push fragment on top of the stack with specific [.TOP_FRAGMENT_TAG_MARK] tag.
* It is useful to realize up/back navigation: if [.up] method will be called then stack will go to nearest fragment with TOP tag.
* If [.back] method will be called then stack will go to previous fragment.
* Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared;
* 4) [.pushForResult] means to push fragment with target fragment. It is also adding [.WITH_TARGET_FRAGMENT_TAG_MARK] tag.
* Also if such up/back navigation logic is not OK then [.backTo] method could be used with any condition to back to.
* In that case in any stack-change method it is allowed to setup fragment transactions.
*/
open class FragmentNavigation(
private val context: Context,
private val fragmentManager: FragmentManager,
@IdRes private val containerViewId: Int,
private val transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
) {
companion object {
const val TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT"
}
/**
* Returns if last fragment in stack is top (added by [.setAsTop] or [.setInitial]) like fragment from sidebar menu.
*
* @return True if last fragment on stack has TOP_FRAGMENT_TAG_MARK.
*/
fun isCurrentFragmentTop(): Boolean = if (fragmentManager.backStackEntryCount == 0) {
true
} else {
fragmentManager
.getBackStackEntryAt(fragmentManager.backStackEntryCount - 1)
.name
?.contains(TOP_FRAGMENT_TAG_MARK) ?: false
}
/**
* Allowed to react on [android.app.Activity]'s menu item selection.
*
* @param item Selected menu item;
* @return True if reaction fired.
*/
fun onOptionsItemSelected(item: MenuItem): Boolean = item.itemId == android.R.id.home && back()
/**
* Base method which is adding fragment to stack.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
* @param addToStack Flag to add this transaction to the back stack;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param backStackName Name of [Fragment] in back stack;
* @param tag Optional tag name for the [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun addToStack(
fragmentClass: Class<out Fragment>,
targetFragment: Fragment?,
targetRequestCode: Int,
addToStack: Boolean,
args: Bundle?,
backStackName: String?,
tag: String?,
transactionSetup: ((FragmentTransaction) -> Unit)?
) {
if (fragmentManager.isDestroyed) {
Lc.assertion("FragmentManager is destroyed")
return
}
val fragment = Fragment.instantiate(context, fragmentClass.name, args)
fragment.setTargetFragment(targetFragment, targetRequestCode)
val fragmentTransaction = fragmentManager.beginTransaction()
transactionSetup?.invoke(fragmentTransaction)
fragmentTransaction.replace(containerViewId, fragment, tag)
if (addToStack) {
fragmentTransaction
.addToBackStack(backStackName)
.setTransition(transition)
}
fragmentTransaction
.setPrimaryNavigationFragment(fragment)
.commit()
}
/**
* Simply calls [FragmentManager.popBackStack].
*
* @return True if it have back to some entry in stack.
*/
fun back(): Boolean {
if (fragmentManager.backStackEntryCount >= 1) {
fragmentManager.popBackStack()
return true
}
return false
}
/**
* Backs to fragment with specific [.TOP_FRAGMENT_TAG_MARK] tag.
* This tag is adding if fragment added to stack via [.setInitial] or [.setAsTop] methods.
* It can be used to create simple up/back navigation.
*
* @return True if it have back to some entry in stack.
*/
fun up(name: String? = null, inclusive: Boolean = false) {
fragmentManager.popBackStack(name, if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0)
}
/**
* Pushes [Fragment] on top of stack with specific arguments and transaction setup.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param tag Optional tag name for the [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun push(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
addToStack: Boolean = true,
backStackName: String? = null,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, backStackName, tag, transactionSetup)
}
/**
* Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param tag Optional tag name for the [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun pushForResult(
fragmentClass: Class<out Fragment>,
targetFragment: Fragment,
targetRequestCode: Int,
args: Bundle? = null,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(
fragmentClass,
targetFragment,
targetRequestCode,
true,
args,
null,
tag,
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.
*
* @param fragmentClass Class of [Fragment] to instantiate;
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param tag Optional tag name for the [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
fun setAsTop(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
addToStack: Boolean = true,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
addToStack(fragmentClass, null, 0, addToStack, args, TOP_FRAGMENT_TAG_MARK, tag, 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 args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
* @param tag Optional tag name for the [Fragment];
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
*/
@JvmOverloads
fun setInitial(
fragmentClass: Class<out Fragment>,
args: Bundle? = null,
tag: String? = null,
transactionSetup: ((FragmentTransaction) -> Unit)? = null
) {
beforeSetInitialActions()
setAsTop(fragmentClass, args, false, tag, transactionSetup)
}
/**
* Method calls every time before initial [Fragment] will be placed.
*/
protected fun beforeSetInitialActions() {
if (fragmentManager.isDestroyed) {
Lc.assertion("FragmentManager is destroyed")
return
}
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
}

View File

@ -1,25 +0,0 @@
package ru.touchin.roboswag.components.navigation.activities
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewControllerNavigation
/**
* Created by Daniil Borisovskii on 15/08/2019.
* Base activity with nested navigation.
*/
abstract class NavigationActivity : BaseActivity() {
protected abstract val fragmentContainerViewId: Int
protected open val transition = FragmentTransaction.TRANSIT_NONE
open val navigation by lazy {
ViewControllerNavigation<NavigationActivity>(
this,
supportFragmentManager,
fragmentContainerViewId,
transition
)
}
}

View File

@ -1,2 +0,0 @@
<manifest
package="ru.touchin.roboswag.components.tabbarnavigation_new"/>

View File

@ -1,42 +0,0 @@
package ru.touchin.roboswag.components.tabbarnavigation_new
import android.os.Parcelable
import androidx.annotation.IdRes
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.components.navigation_new.activities.NavigationActivity
import ru.touchin.roboswag.components.navigation_new.FragmentNavigation
/**
* Created by Daniil Borisovskii on 15/08/2019.
* Activity to manage tab container navigation.
*/
abstract class BottomNavigationActivity : NavigationActivity() {
val innerNavigation: FragmentNavigation
get() = getNavigationContainer(supportFragmentManager)?.navigation ?: navigation
/**
* Navigates to the given navigation tab.
* Can be called from any node of navigation graph so all back stack will be cleared.
*
* @param navigationTabId Id of navigation tab.
* @param state State of the given tab. If not null tab's fragment will be recreated, otherwise only in case it has not been created before.
*/
fun navigateTo(@IdRes navigationTabId: Int, state: Parcelable? = null) {
supportFragmentManager.run {
// Clear all navigation stack unto the main bottom navigation (tagged as top)
popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
(primaryNavigationFragment as? BottomNavigationFragment)?.navigateTo(navigationTabId, state)
}
}
private fun getNavigationContainer(fragmentManager: FragmentManager?): NavigationContainerFragment? =
fragmentManager
?.primaryNavigationFragment
?.let { navigationFragment ->
navigationFragment as? NavigationContainerFragment
?: getNavigationContainer(navigationFragment.childFragmentManager)
}
}

View File

@ -1,125 +0,0 @@
package ru.touchin.roboswag.components.tabbarnavigation_new
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.core.util.forEach
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
import ru.touchin.roboswag.core.utils.ShouldNotHappenException
class BottomNavigationController(
private val context: Context,
private val fragmentManager: FragmentManager,
private val fragments: SparseArray<Pair<Class<out BaseFragment<*, *>>, Parcelable>>,
@IdRes private val contentContainerViewId: Int,
@LayoutRes private val contentContainerLayoutId: Int,
private val wrapWithNavigationContainer: Boolean = false,
@IdRes private val topLevelFragmentId: Int = 0, // If it zero back press with empty fragment back stack would close the app
private val onReselectListener: (() -> Unit)? = null
) {
private var callback: FragmentManager.FragmentLifecycleCallbacks? = null
private var currentFragmentId = -1
fun attach(navigationTabsContainer: ViewGroup) {
detach()
//This is provides to set pressed tab status to isActivated providing an opportunity to specify custom style
callback = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewCreated(fragmentManager: FragmentManager, fragment: Fragment, view: View, savedInstanceState: Bundle?) {
fragments.forEach { itemId, (fragmentClass, _) ->
if (isFragment(fragment, fragmentClass)) {
navigationTabsContainer.children.forEach { itemView -> itemView.isActivated = itemView.id == itemId }
}
}
}
}
fragmentManager.registerFragmentLifecycleCallbacks(callback!!, false)
navigationTabsContainer.children.forEach { itemView ->
fragments[itemView.id]?.let { (fragmentClass, _) ->
itemView.setOnClickListener {
if (!isFragment(fragmentManager.primaryNavigationFragment, fragmentClass)) {
navigateTo(itemView.id)
} else {
onReselectListener?.invoke()
}
}
}
}
}
fun detach() = callback?.let(fragmentManager::unregisterFragmentLifecycleCallbacks)
fun navigateTo(@IdRes itemId: Int, state: Parcelable? = null) {
// Find fragment class that needs to open
val (fragmentClass, defaultFragmentState) = fragments[itemId] ?: return
if (state != null && state::class != defaultFragmentState::class) {
throw ShouldNotHappenException(
"Incorrect state type for navigation tab root Fragment. Should be ${defaultFragmentState::class}"
)
}
val fragmentState = state ?: defaultFragmentState
val transaction = fragmentManager.beginTransaction()
// Detach current primary fragment
fragmentManager.primaryNavigationFragment?.let(transaction::detach)
val fragmentName = fragmentClass.canonicalName
var fragment = fragmentManager.findFragmentByTag(fragmentName)
if (state == null && fragment != null) {
transaction.attach(fragment)
} else {
// If fragment already exist remove it first
if (fragment != null) transaction.remove(fragment)
fragment = if (wrapWithNavigationContainer) {
Fragment.instantiate(
context,
NavigationContainerFragment::class.java.name,
NavigationContainerFragment.args(fragmentClass, fragmentState, contentContainerViewId, contentContainerLayoutId)
)
} else {
Fragment.instantiate(
context,
fragmentClass.name,
BaseFragment.args(fragmentState)
)
}
transaction.add(contentContainerViewId, fragment, fragmentName)
}
transaction
.setPrimaryNavigationFragment(fragment)
.setReorderingAllowed(true)
.commit()
currentFragmentId = itemId
}
// When you are in any tab instead of main you firstly navigate to main tab before exit application
fun onBackPressed() =
if (fragmentManager.primaryNavigationFragment?.childFragmentManager?.backStackEntryCount == 0
&& topLevelFragmentId != 0
&& currentFragmentId != topLevelFragmentId) {
navigateTo(topLevelFragmentId)
true
} else {
false
}
private fun isFragment(fragment: Fragment?, fragmentClass: Class<out BaseFragment<*, *>>) =
if (wrapWithNavigationContainer) {
(fragment as NavigationContainerFragment).getFragmentClass()
} else {
(fragment as BaseFragment<*, *>).javaClass
} === fragmentClass
}

View File

@ -1,75 +0,0 @@
package ru.touchin.roboswag.components.tabbarnavigation_new
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
abstract class BottomNavigationFragment : Fragment() {
private lateinit var bottomNavigationController: BottomNavigationController
private val backPressedListener = OnBackPressedListener { bottomNavigationController.onBackPressed() }
protected abstract val rootLayoutId: Int
protected abstract val navigationContainerViewId: Int
protected abstract val contentContainerViewId: Int
protected abstract val contentContainerLayoutId: Int
protected abstract val topLevelFragmentId: Int
protected abstract val wrapWithNavigationContainer: Boolean
protected abstract val navigationFragments: SparseArray<Pair<Class<out BaseFragment<*, *>>, Parcelable>>
protected open val reselectListener: (() -> Unit) = { getNavigationActivity().innerNavigation.up(inclusive = true) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bottomNavigationController = BottomNavigationController(
context = requireContext(),
fragmentManager = childFragmentManager,
fragments = navigationFragments,
contentContainerViewId = contentContainerViewId,
contentContainerLayoutId = contentContainerLayoutId,
topLevelFragmentId = topLevelFragmentId,
wrapWithNavigationContainer = wrapWithNavigationContainer,
onReselectListener = reselectListener
)
if (savedInstanceState == null) {
bottomNavigationController.navigateTo(topLevelFragmentId)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val fragmentView = inflater.inflate(rootLayoutId, container, false)
bottomNavigationController.attach(fragmentView.findViewById(navigationContainerViewId))
(activity as BottomNavigationActivity).addOnBackPressedListener(backPressedListener)
return fragmentView
}
override fun onDestroyView() {
super.onDestroyView()
(activity as BottomNavigationActivity).removeOnBackPressedListener(backPressedListener)
bottomNavigationController.detach()
}
fun navigateTo(@IdRes navigationTabId: Int, state: Parcelable? = null) {
bottomNavigationController.navigateTo(navigationTabId, state)
}
private fun getNavigationActivity() = requireActivity() as BottomNavigationActivity
}

View File

@ -1,77 +0,0 @@
package ru.touchin.roboswag.components.tabbarnavigation_new
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.components.navigation_new.FragmentNavigation
import ru.touchin.roboswag.components.navigation_new.fragments.BaseFragment
import ru.touchin.roboswag.core.utils.ShouldNotHappenException
class NavigationContainerFragment : Fragment() {
companion object {
private const val FRAGMENT_CLASS_ARG = "FRAGMENT_CLASS_ARG"
private const val FRAGMENT_STATE_ARG = "FRAGMENT_STATE_ARG"
private const val CONTAINER_VIEW_ID_ARG = "CONTAINER_VIEW_ID_ARG"
private const val CONTAINER_LAYOUT_ID_ARG = "CONTAINER_LAYOUT_ID_ARG"
private const val TRANSITION_ARG = "TRANSITION_ARG"
fun args(
cls: Class<out BaseFragment<*, *>>,
state: Parcelable,
@IdRes containerViewId: Int,
@LayoutRes containerLayoutId: Int,
transition: Int = FragmentTransaction.TRANSIT_NONE
) = Bundle().apply {
putSerializable(FRAGMENT_CLASS_ARG, cls)
putParcelable(FRAGMENT_STATE_ARG, state)
putInt(CONTAINER_VIEW_ID_ARG, containerViewId)
putInt(CONTAINER_LAYOUT_ID_ARG, containerLayoutId)
putInt(TRANSITION_ARG, transition)
}
}
val navigation by lazy {
FragmentNavigation(
requireContext(),
childFragmentManager,
containerViewId,
transition
)
}
@IdRes
private var containerViewId = 0
@LayoutRes
private var containerLayoutId = 0
private var transition = 0
@Suppress("UNCHECKED_CAST")
fun getFragmentClass(): Class<out BaseFragment<out BottomNavigationActivity, Parcelable>> =
arguments?.getSerializable(FRAGMENT_CLASS_ARG) as Class<out BaseFragment<out BottomNavigationActivity, Parcelable>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val args = arguments ?: throw ShouldNotHappenException("Fragment is not instantiable without arguments")
with(args) {
containerViewId = getInt(CONTAINER_VIEW_ID_ARG)
containerLayoutId = getInt(CONTAINER_LAYOUT_ID_ARG)
transition = getInt(TRANSITION_ARG)
}
navigation.setInitial(getFragmentClass().kotlin, args.getParcelable(FRAGMENT_STATE_ARG))
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(containerLayoutId, container, false)
}

View File

@ -1,2 +0,0 @@
<manifest
package="ru.touchin.roboswag.components.tabbarnavigation"/>

View File

@ -1,127 +0,0 @@
package ru.touchin.roboswag.components.tabbarnavigation
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.core.util.forEach
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
import ru.touchin.roboswag.core.utils.ShouldNotHappenException
class BottomNavigationController(
private val context: Context,
private val fragmentManager: FragmentManager,
private val viewControllers: SparseArray<BottomNavigationFragment.TabData>,
@IdRes private val contentContainerViewId: Int,
@LayoutRes private val contentContainerLayoutId: Int,
private val wrapWithNavigationContainer: Boolean = false,
@IdRes private val topLevelViewControllerId: Int = 0, // If it zero back press with empty fragment back stack would close the app
private val onReselectListener: (() -> Unit)? = null
) {
private var callback: FragmentManager.FragmentLifecycleCallbacks? = null
private var currentViewControllerId = -1
fun attach(navigationTabsContainer: ViewGroup) {
detach()
//This is provides to set pressed tab status to isActivated providing an opportunity to specify custom style
callback = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewCreated(fragmentManager: FragmentManager, fragment: Fragment, view: View, savedInstanceState: Bundle?) {
viewControllers.forEach { itemId, (viewControllerClass, _) ->
if (isViewControllerFragment(fragment, viewControllerClass)) {
navigationTabsContainer.children.forEach { itemView -> itemView.isActivated = itemView.id == itemId }
}
}
}
}
fragmentManager.registerFragmentLifecycleCallbacks(callback!!, false)
navigationTabsContainer.children.forEach { itemView ->
viewControllers[itemView.id]?.let { (viewControllerClass, _) ->
itemView.setOnClickListener {
if (!isViewControllerFragment(fragmentManager.primaryNavigationFragment, viewControllerClass)) {
navigateTo(itemView.id)
} else {
onReselectListener?.invoke()
}
}
}
}
}
fun detach() = callback?.let(fragmentManager::unregisterFragmentLifecycleCallbacks)
@Suppress("detekt.ComplexMethod")
fun navigateTo(@IdRes itemId: Int, state: Parcelable? = null) {
// Find view controller class that needs to open
val (viewControllerClass, defaultViewControllerState, saveStateOnSwitching) = viewControllers[itemId] ?: return
if (state != null && state::class != defaultViewControllerState::class) {
throw ShouldNotHappenException(
"Incorrect state type for navigation tab root ViewController. Should be ${defaultViewControllerState::class}"
)
}
val viewControllerState = state ?: defaultViewControllerState
val transaction = fragmentManager.beginTransaction()
// Detach current primary fragment
fragmentManager.primaryNavigationFragment?.let(transaction::detach)
val viewControllerName = viewControllerClass.canonicalName
var fragment = fragmentManager.findFragmentByTag(viewControllerName)
if (saveStateOnSwitching && state == null && fragment != null) {
transaction.attach(fragment)
} else {
// If fragment already exist remove it first
if (fragment != null) transaction.remove(fragment)
fragment = if (wrapWithNavigationContainer) {
Fragment.instantiate(
context,
NavigationContainerFragment::class.java.name,
NavigationContainerFragment.args(viewControllerClass, viewControllerState, contentContainerViewId, contentContainerLayoutId)
)
} else {
Fragment.instantiate(
context,
ViewControllerFragment::class.java.name,
ViewControllerFragment.args(viewControllerClass, viewControllerState)
)
}
transaction.add(contentContainerViewId, fragment, viewControllerName)
}
transaction
.setPrimaryNavigationFragment(fragment)
.setReorderingAllowed(true)
.commit()
currentViewControllerId = itemId
}
// When you are in any tab instead of main you firstly navigate to main tab before exit application
fun onBackPressed() =
if (fragmentManager.primaryNavigationFragment?.childFragmentManager?.backStackEntryCount == 0
&& topLevelViewControllerId != 0
&& currentViewControllerId != topLevelViewControllerId) {
navigateTo(topLevelViewControllerId)
true
} else {
false
}
private fun isViewControllerFragment(fragment: Fragment?, viewControllerClass: Class<out ViewController<*, *>>) =
if (wrapWithNavigationContainer) {
(fragment as NavigationContainerFragment).getViewControllerClass()
} else {
(fragment as ViewControllerFragment<*, *>).viewControllerClass
} === viewControllerClass
}

View File

@ -1,118 +0,0 @@
package ru.touchin.roboswag.components.tabbarnavigation
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener
import ru.touchin.roboswag.components.navigation.viewcontrollers.EmptyState
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
abstract class BottomNavigationFragment : Fragment() {
private lateinit var bottomNavigationController: BottomNavigationController
private val backPressedListener = OnBackPressedListener { bottomNavigationController.onBackPressed() }
protected abstract val rootLayoutId: Int
protected abstract val navigationContainerViewId: Int
protected abstract val contentContainerViewId: Int
protected abstract val contentContainerLayoutId: Int
protected abstract val topLevelViewControllerId: Int
protected abstract val wrapWithNavigationContainer: Boolean
protected abstract val navigationViewControllers: SparseArray<TabData>
protected open val reselectListener: (() -> Unit) = { getNavigationActivity().innerNavigation.up(inclusive = true) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bottomNavigationController = BottomNavigationController(
context = requireContext(),
fragmentManager = childFragmentManager,
viewControllers = navigationViewControllers,
contentContainerViewId = contentContainerViewId,
contentContainerLayoutId = contentContainerLayoutId,
topLevelViewControllerId = topLevelViewControllerId,
wrapWithNavigationContainer = wrapWithNavigationContainer,
onReselectListener = reselectListener
)
if (savedInstanceState == null) {
bottomNavigationController.navigateTo(topLevelViewControllerId)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val fragmentView = inflater.inflate(rootLayoutId, container, false)
bottomNavigationController.attach(fragmentView.findViewById(navigationContainerViewId))
(activity as BottomNavigationActivity).addOnBackPressedListener(backPressedListener)
return fragmentView
}
override fun onDestroyView() {
super.onDestroyView()
(activity as BottomNavigationActivity).removeOnBackPressedListener(backPressedListener)
bottomNavigationController.detach()
}
fun navigateTo(@IdRes navigationTabId: Int, state: Parcelable? = null) {
bottomNavigationController.navigateTo(navigationTabId, state)
}
private fun getNavigationActivity() = requireActivity() as BottomNavigationActivity
class TabData(
val viewControllerClass: Class<out ViewController<*, *>>,
viewControllerState: Parcelable,
/**
* It can be useful in some cases when it is necessary to create ViewController
* with initial state every time when tab opens.
*/
val saveStateOnSwitching: Boolean = true
) {
/**
* It is value as class body property instead of value as constructor parameter to specify
* custom getter of this field which returns copy of Parcelable every time it be called.
* This is necessary to avoid modifying this value if it would be a value as constructor parameter
* and every getting of this value would return the same instance.
*/
val viewControllerState = viewControllerState
get() = field.copy()
operator fun component1() = viewControllerClass
operator fun component2() = viewControllerState
operator fun component3() = saveStateOnSwitching
private fun Parcelable.copy(): Parcelable =
if (this is EmptyState) {
EmptyState
} else {
val parcel = Parcel.obtain()
parcel.writeParcelable(this, 0)
parcel.setDataPosition(0)
val result = parcel.readParcelable<Parcelable>(
javaClass.classLoader ?: Thread.currentThread().contextClassLoader
) ?: throw IllegalStateException("Failed to copy tab state")
parcel.recycle()
result
}
}
}

View File

@ -1,79 +0,0 @@
package ru.touchin.roboswag.components.tabbarnavigation
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController
import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewControllerNavigation
import ru.touchin.roboswag.core.utils.ShouldNotHappenException
class NavigationContainerFragment : Fragment() {
companion object {
private const val VIEW_CONTROLLER_CLASS_ARG = "VIEW_CONTROLLER_CLASS_ARG"
private const val VIEW_CONTROLLER_STATE_ARG = "VIEW_CONTROLLER_STATE_ARG"
private const val CONTAINER_VIEW_ID_ARG = "CONTAINER_VIEW_ID_ARG"
private const val CONTAINER_LAYOUT_ID_ARG = "CONTAINER_LAYOUT_ID_ARG"
private const val TRANSITION_ARG = "TRANSITION_ARG"
fun args(
cls: Class<out ViewController<*, *>>,
state: Parcelable,
@IdRes containerViewId: Int,
@LayoutRes containerLayoutId: Int,
transition: Int = FragmentTransaction.TRANSIT_NONE
) = Bundle().apply {
putSerializable(VIEW_CONTROLLER_CLASS_ARG, cls)
putParcelable(VIEW_CONTROLLER_STATE_ARG, state)
putInt(CONTAINER_VIEW_ID_ARG, containerViewId)
putInt(CONTAINER_LAYOUT_ID_ARG, containerLayoutId)
putInt(TRANSITION_ARG, transition)
}
}
val navigation by lazy {
ViewControllerNavigation<BottomNavigationActivity>(
requireContext(),
childFragmentManager,
containerViewId,
transition
)
}
@IdRes
private var containerViewId = 0
@LayoutRes
private var containerLayoutId = 0
private var transition = 0
@Suppress("UNCHECKED_CAST")
fun getViewControllerClass(): Class<out ViewController<out BottomNavigationActivity, Parcelable>> =
arguments?.getSerializable(VIEW_CONTROLLER_CLASS_ARG) as Class<out ViewController<out BottomNavigationActivity, Parcelable>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { args ->
transition = args.getInt(TRANSITION_ARG)
containerViewId = args.getInt(CONTAINER_VIEW_ID_ARG)
containerLayoutId = args.getInt(CONTAINER_LAYOUT_ID_ARG)
if (savedInstanceState == null) {
navigation.setInitialViewController(getViewControllerClass(), args.getParcelable(VIEW_CONTROLLER_STATE_ARG)
?: throw ShouldNotHappenException("Fragment state must not be null"))
}
} ?: throw ShouldNotHappenException("Fragment is not instantiable without arguments")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(containerLayoutId, container, false)
}