Merge pull request #125 from TouchInstinct/base_naviagation
navigation_new became navigation_base, tabbar navigation has appropriate postfix now
This commit is contained in:
commit
48bfcb0d1e
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.roboswag.bottom_navigation_base"/>
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<*, *, *>
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.roboswag.bottom_navigation_fragment"/>
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -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)!!)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.roboswag.bottom_navigation_viewcontroller"/>
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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)!!)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.lifecycle_viewcontroller"/>
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,8 +19,6 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
api project(":navigation")
|
||||
|
||||
compileOnly "javax.inject:javax.inject:1"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -15,4 +15,8 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation "androidx.annotation:annotation:$versions.androidx"
|
||||
|
||||
implementation("com.crashlytics.sdk.android:crashlytics:$versions.crashlytics@aar") {
|
||||
transitive = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.roboswag.navigation_base"/>
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ru.touchin.roboswag.components.navigation.activities;
|
||||
package ru.touchin.roboswag.navigation_base.activities;
|
||||
|
||||
public interface OnBackPressedListener {
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
) {
|
||||
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.touchin.roboswag.components.navigation_new"/>
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.roboswag.navigation_viewcontroller"/>
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.touchin.roboswag.components.navigation"/>
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<manifest
|
||||
package="ru.touchin.roboswag.components.tabbarnavigation_new"/>
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<manifest
|
||||
package="ru.touchin.roboswag.components.tabbarnavigation"/>
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue