Instantly share code, notes, and snippets.

Embed
What would you like to do?
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import ru.terrakok.cicerone.Navigator
import ru.terrakok.cicerone.commands.*
import java.util.*
/**
* [Navigator] implementation based on the support fragments.
*
*
* [BackTo] navigation command will return to the root if
* needed screen isn't found in the screens chain.
* To change this behavior override [.backToUnexisting] method.
*
*
*
* [Back] command will call [.exit] method if current screen is the root.
*
*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*
* Migrated to AndroidX (and Kotlin) by David Whitman. Based on v3.0.0.
*
*/
abstract class CiceroneSupportFragmentNavigator
/**
* Creates SupportFragmentNavigator.
*
* @param fragmentManager support fragment manager
* @param containerId id of the fragments container layout
*/
(private val fragmentManager: FragmentManager, private val containerId: Int) : Navigator {
protected var localStackCopy: LinkedList<String?> = LinkedList()
/**
* Override this method to setup custom fragment transaction animation.
*
* @param command current navigation command. Will be only [Forward] or [Replace]
* @param currentFragment current fragment in container
* (for [Replace] command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
* @param fragmentTransaction fragment transaction
*/
protected open fun setupFragmentTransactionAnimation(
command: Command,
currentFragment: Fragment?,
nextFragment: Fragment,
fragmentTransaction: FragmentTransaction
) {
}
override fun applyCommands(commands: Array<Command>) {
fragmentManager.executePendingTransactions()
//copy stack before apply commands
copyStackToLocal()
for (command in commands) {
applyCommand(command)
}
}
private fun copyStackToLocal() {
localStackCopy = LinkedList()
val stackSize = fragmentManager.backStackEntryCount
for (i in 0 until stackSize) {
localStackCopy.add(fragmentManager.getBackStackEntryAt(i).name)
}
}
/**
* Perform transition described by the navigation command
*
* @param command the navigation command to apply
*/
protected open fun applyCommand(command: Command) {
if (command is Forward) {
forward(command)
} else if (command is Back) {
back()
} else if (command is Replace) {
replace(command)
} else if (command is BackTo) {
backTo(command)
} else if (command is SystemMessage) {
showSystemMessage(command.message)
}
}
/**
* Performs [Forward] command transition
*/
protected open fun forward(command: Forward) {
val fragment = createFragment(command.screenKey, command.transitionData)
if (fragment == null) {
unknownScreen(command)
return
}
val fragmentTransaction = fragmentManager.beginTransaction()
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
)
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(command.screenKey)
.commit()
localStackCopy.add(command.screenKey)
}
/**
* Performs [Back] command transition
*/
protected fun back() {
if (localStackCopy.size > 0) {
fragmentManager.popBackStack()
localStackCopy.pop()
} else {
exit()
}
}
/**
* Performs [Replace] command transition
*/
protected open fun replace(command: Replace) {
val fragment = createFragment(command.screenKey, command.transitionData)
if (fragment == null) {
unknownScreen(command)
return
}
if (localStackCopy.size > 0) {
fragmentManager.popBackStack()
localStackCopy.pop()
val fragmentTransaction = fragmentManager.beginTransaction()
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
)
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(command.screenKey)
.commit()
localStackCopy.add(command.screenKey)
} else {
val fragmentTransaction = fragmentManager.beginTransaction()
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
)
fragmentTransaction
.replace(containerId, fragment)
.commit()
}
}
/**
* Performs [BackTo] command transition
*/
protected fun backTo(command: BackTo) {
val key = command.screenKey
if (key == null) {
backToRoot()
} else {
val index = localStackCopy.indexOf(key)
val size = localStackCopy.size
if (index != -1) {
for (i in 1 until size - index) {
localStackCopy.pop()
}
fragmentManager.popBackStack(key, 0)
} else {
backToUnexisting(command.screenKey)
}
}
}
private fun backToRoot() {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
localStackCopy.clear()
}
/**
* Creates Fragment matching `screenKey`.
*
* @param screenKey screen key
* @param data initialization data
* @return instantiated fragment for the passed screen key
*/
protected abstract fun createFragment(screenKey: String, data: Any?): Fragment?
/**
* Shows system message.
*
* @param message message to show
*/
protected abstract fun showSystemMessage(message: String)
/**
* Called when we try to back from the root.
*/
protected abstract fun exit()
/**
* Called when we tried to back to some specific screen (via [BackTo] command),
* but didn't found it.
* @param screenKey screen key
*/
protected fun backToUnexisting(screenKey: String) {
backToRoot()
}
/**
* Called if we can't create a screen.
*/
protected fun unknownScreen(command: Command) {
throw RuntimeException("Can't create a screen for passed screenKey.")
}
}
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import ru.terrakok.cicerone.android.SupportFragmentNavigator
import ru.terrakok.cicerone.commands.BackTo
import ru.terrakok.cicerone.commands.Command
import ru.terrakok.cicerone.commands.Forward
import ru.terrakok.cicerone.commands.Replace
/**
* Extends [SupportFragmentNavigator] to allow
* open new or replace current activity.
*
*
* This navigator DOESN'T provide full featured Activity navigation,
* but can ease Activity start or replace from current navigator.
*
*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*
* Migrated to AndroidX (and Kotlin) by David Whitman. Based on v3.0.0.
*/
abstract class CiceroneSupportAppNavigator : CiceroneSupportFragmentNavigator {
private val activity: Activity
constructor(activity: FragmentActivity, containerId: Int) : super(
activity.supportFragmentManager,
containerId
) {
this.activity = activity
}
constructor(activity: FragmentActivity, fragmentManager: FragmentManager, containerId: Int) : super(
fragmentManager,
containerId
) {
this.activity = activity
}
/**
* Override this method to create option for start activity
*
* @param command current navigation command. Will be only [Forward] or [Replace]
* @param activityIntent activity intent
* @return transition options
*/
protected open fun createStartActivityOptions(command: Command, activityIntent: Intent): Bundle? {
return null
}
override fun forward(command: Forward) {
val activityIntent = createActivityIntent(activity, command.screenKey, command.transitionData)
// Start activity
if (activityIntent != null) {
val options = createStartActivityOptions(command, activityIntent)
checkAndStartActivity(command.screenKey, activityIntent, options)
} else {
super.forward(command)
}
}
override fun replace(command: Replace) {
val activityIntent = createActivityIntent(activity, command.screenKey, command.transitionData)
// Replace activity
if (activityIntent != null) {
val options = createStartActivityOptions(command, activityIntent)
checkAndStartActivity(command.screenKey, activityIntent, options)
activity!!.finish()
} else {
super.replace(command)
}
}
private fun checkAndStartActivity(screenKey: String, activityIntent: Intent, options: Bundle?) {
// Check if we can start activity
if (activityIntent.resolveActivity(activity!!.packageManager) != null) {
activity!!.startActivity(activityIntent, options)
} else {
unexistingActivity(screenKey, activityIntent)
}
}
/**
* Called when there is no activity to open `screenKey`.
*
* @param screenKey screen key
* @param activityIntent intent passed to start Activity for the `screenKey`
*/
protected fun unexistingActivity(screenKey: String, activityIntent: Intent) {
// Do nothing by default
}
/**
* Creates Intent to start Activity for `screenKey`.
*
*
* **Warning:** This method does not work with [BackTo] command.
*
*
* @param screenKey screen key
* @param data initialization data, can be null
* @return intent to start Activity for the passed screen key
*/
protected abstract fun createActivityIntent(context: Context, screenKey: String, data: Any?): Intent?
override fun showSystemMessage(message: String) {
// Toast by default
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
override fun exit() {
// Finish by default
activity!!.finish()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment