Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b2654ee2b | |||
| 8c8521d2c5 | |||
| 14b94927e4 | |||
| 1e40fb29bb | |||
| dbc8c3363f | |||
| c3873c6e9d | |||
| f44410fcfe | |||
| 608a32c3be | |||
| 17428f0189 | |||
| 1701bc4f36 | |||
| 9c9c56bc9b | |||
| 3da8fe52ca | |||
| a943aab08e | |||
| 86ac421fb8 | |||
| f7009f53e8 | |||
| 994c497147 | |||
| bb13ace1c9 | |||
| dbe065275d | |||
| 2ebbc98a99 | |||
| ba7eea60f5 | |||
| 6347fe7d95 | |||
| 2b6e2ee874 | |||
| f50a4d8106 | |||
| 3535f491f5 | |||
| 1c5b89d2ec | |||
| 127cce1fe4 |
@@ -6,15 +6,39 @@
|
||||
[](http://androidweekly.net/issues/issue-250)
|
||||
[](http://androidweekly.net/issues/issue-271)
|
||||
|
||||

|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="https://github.com/terrakok/Cicerone/raw/master/media/navigation.gif" width="256"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="https://github.com/terrakok/Cicerone/raw/master/media/insta_tabs.gif" width="256"/>
|
||||
</td>
|
||||
<td>
|
||||
<img src="https://github.com/terrakok/Cicerone/raw/master/media/animations.gif" width="256"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Power navigation
|
||||
</td>
|
||||
<td>
|
||||
Multibackstack
|
||||
</td>
|
||||
<td>
|
||||
Result listeners
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Cicerone (a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
|
||||
Cicerone (means - a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
|
||||
It was designed to be used with the MVP/MVVM/MVI patterns but will work great with any architecture.
|
||||
|
||||
## Main advantages
|
||||
+ is not tied to Fragments
|
||||
+ not a framework
|
||||
+ not a framework (very lightweight)
|
||||
+ short navigation calls (no builders)
|
||||
+ static typed checks for screen parameters!
|
||||
+ lifecycle-safe!
|
||||
+ functionality is simple to extend
|
||||
+ suitable for Unit Testing
|
||||
@@ -109,13 +133,13 @@ override fun onPause() {
|
||||
## Navigation commands
|
||||
This commands set will fulfill the needs of the most applications. But if you need something special - just add it!
|
||||
+ Forward - Opens new screen
|
||||

|
||||

|
||||
+ Back - Rolls back the last transition
|
||||

|
||||

|
||||
+ BackTo - Rolls back to the needed screen in the screens chain
|
||||

|
||||

|
||||
+ Replace - Replaces the current screen
|
||||

|
||||

|
||||
|
||||
## Predefined navigator
|
||||
The library provides predefined navigator for _Fragments_ and _Activity_.
|
||||
@@ -147,21 +171,22 @@ private val navigator = object : AppNavigator(this, R.id.container) {
|
||||
Describe your screens as you like e.g. create Kotlin `object` with all application screens:
|
||||
```kotlin
|
||||
object Screens {
|
||||
val Main = FragmentScreen("MainFragment") { MainFragment() }
|
||||
val AddressSearch = FragmentScreen("AddressSearchFragment") { AddressSearchFragment() }
|
||||
fun Profile(userId: Long) = FragmentScreen("ProfileFragment") { ProfileFragment(userId) }
|
||||
fun Main() = FragmentScreen { MainFragment() }
|
||||
fun AddressSearch() = FragmentScreen { AddressSearchFragment() }
|
||||
fun Profile(userId: Long) = FragmentScreen("Profile_$userId") { ProfileFragment(userId) }
|
||||
fun Browser(url: String) = ActivityScreen { Intent(Intent.ACTION_VIEW, Uri.parse(url)) }
|
||||
}
|
||||
```
|
||||
|
||||
Additional you can use `FragmentFactory` for creating your screens:
|
||||
```kotlin
|
||||
val SomeScreen = FragmentScreen("SomeScreenId") { factory: FragmentFactory -> ... }
|
||||
fun SomeScreen() = FragmentScreen { factory: FragmentFactory -> ... }
|
||||
```
|
||||
|
||||
## Screen parameters and result listener
|
||||
```kotlin
|
||||
//you have to specify screen parameters via new FragmentScreen creation
|
||||
fun SelectPhoto(resultKey: String) = FragmentScreen("SelectPhoto") {
|
||||
fun SelectPhoto(resultKey: String) = FragmentScreen {
|
||||
SelectPhotoFragment.getNewInstance(resultKey)
|
||||
}
|
||||
```
|
||||
@@ -188,9 +213,16 @@ To see how to add, initialize and use the library and predefined navigators see
|
||||
|
||||
For more complex use case check out the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
|
||||
|
||||

|
||||

|
||||

|
||||
## Applications with Cicerone inside
|
||||
<a href="https://play.google.com/store/apps/details?id=ru.foodfox.client"><img src="https://play-lh.googleusercontent.com/gWYedIqy8QujCQOn0kzEIBEkGLBSpuKvFm-fMcfkWnJ1Oirtv847xAE4OyhAaohdcp5V=s360" width="64" /> Яндекс.Еда — доставка еды/продуктов. Food delivery</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.foodient.whisk"><img src="https://play-lh.googleusercontent.com/eKotZjJcZOU2_L9t2l34EEY7aGl5zhvKVuEbF0Kc4MRs_pAC2SJgOnWMkMTFjR_e9EY=s360" width="64" /> Whisk: Recipe Saver, Meal Planner & Grocery List</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=kz.beeline.odp"><img src="https://play-lh.googleusercontent.com/hzgjpQQpy6Z-Byye0aVKSv9P7h8yx58i6pVkQtiM6jB99iWFXjYfKeaPqJ3wm6Rtb38=s360" width="64" /> Мой Beeline (Казахстан)</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.mercuryo.app"><img src="https://play-lh.googleusercontent.com/FKulXdc15r5PWX6hTZi2i3iaJjcQHwd9xParp6YPiQ2KiBqza7jwEt_b_tqLwXpyEHg=s360" width="64" /> Mercuryo Bitcoin Cryptowallet</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.warefly.checkscan"><img src="https://play-lh.googleusercontent.com/2c2uuiSl2vwGgp-vdI-VArQEMdSSXk1neUK5A-Udc0WANPcvp5kBJFEugrFiXnxUc7k=s360" width="64" /> ЧекСкан - кэшбэк за чеки, цены и акции в магазинах</a><br>
|
||||
<a href="https://github.com/eduard1abdulmanov123/News"><img src="https://raw.githubusercontent.com/eduard1abdulmanov123/News/dev/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" width="64" /> RSS Reader для Вести.Ru</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.epam.connect.android"><img src="https://play-lh.googleusercontent.com/aN7R6BiR7yt7b3oEoBI30pVwzsdzaWe3TWpw8c9igqoOj79Pm2xVh4_C4qwjSKwjVio=s360" width="64" /> EPAM Connect</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=org.consumerreports.ratings"><img src="https://play-lh.googleusercontent.com/dEdOwZOjXAdamytxY1TgY8LS-Hc9FKCcit5HP1RyaKqRAWjDJEyFSQS1XlqQPpeY5UI=s360" width="64" /> Consumer Reports: Product Reviews & Ratings</a><br>
|
||||
<a href="https://play.google.com/store/apps/details?id=ru.zakaz.android"><img src="https://play-lh.googleusercontent.com/jj18yK2dB2MHZ_QdO21aXyznGXteIF2q4mgxY4ubLhFv9gwZqHVDeu1i2FmanS-0Furm=s360" width="64" /> Zakaz.ru</a><br>
|
||||
|
||||
## Participants
|
||||
+ idea and code - Konstantin Tskhovrebov (@terrakok)
|
||||
|
||||
@@ -18,7 +18,7 @@ ext {
|
||||
bintrayName = 'cicerone-kotlin'
|
||||
publishedGroupId = 'com.github.terrakok'
|
||||
artifact = 'cicerone'
|
||||
libraryVersion = '6.2'
|
||||
libraryVersion = '6.6'
|
||||
gitUrl = 'https://github.com/terrakok/Cicerone'
|
||||
allLicenses = ['MIT']
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ abstract class BaseRouter {
|
||||
private val resultWire = ResultWire()
|
||||
|
||||
/**
|
||||
* Sets data listener with given key.
|
||||
* Sets data listener with given key
|
||||
* and returns [ResultListenerHandler] for availability to dispose subscription.
|
||||
*
|
||||
* After first call listener will be removed.
|
||||
*/
|
||||
fun setResultListener(key: String, listener: (data: Any) -> Unit) {
|
||||
resultWire.setResultListener(key, listener)
|
||||
fun setResultListener(
|
||||
key: String,
|
||||
listener: ResultListener
|
||||
): ResultListenerHandler {
|
||||
return resultWire.setResultListener(key, listener)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,6 +36,5 @@ abstract class BaseRouter {
|
||||
*/
|
||||
protected fun executeCommands(vararg commands: Command) {
|
||||
commandBuffer.executeCommands(commands)
|
||||
resultWire.flush()
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,14 @@ class Cicerone<T : BaseRouter> private constructor(val router: T) {
|
||||
/**
|
||||
* Creates the Cicerone instance with the default [Router]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create() = create(Router())
|
||||
|
||||
/**
|
||||
* Creates the Cicerone instance with the custom router.
|
||||
* @param customRouter the custom router extending [BaseRouter]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun <T : BaseRouter> create(customRouter: T) = Cicerone(customRouter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
package com.github.terrakok.cicerone
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
/**
|
||||
* Interface definition for a result callback.
|
||||
*/
|
||||
fun interface ResultListener {
|
||||
fun onResult(data: Any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for manual delete subscription and avoid leak
|
||||
*/
|
||||
fun interface ResultListenerHandler {
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
internal class ResultWire {
|
||||
private val listeners = mutableMapOf<String, WeakReference<(Any) -> Unit>>()
|
||||
private val listeners = mutableMapOf<String, ResultListener>()
|
||||
|
||||
fun setResultListener(key: String, listener: (data: Any) -> Unit) {
|
||||
listeners[key] = WeakReference(listener)
|
||||
}
|
||||
|
||||
fun sendResult(key: String, data: Any) {
|
||||
listeners.remove(key)?.get()?.let { listener ->
|
||||
listener(data)
|
||||
fun setResultListener(key: String, listener: ResultListener): ResultListenerHandler {
|
||||
listeners[key] = listener
|
||||
return ResultListenerHandler {
|
||||
listeners.remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
fun flush() {
|
||||
listeners.entries
|
||||
.filter { it.value.get() == null }
|
||||
.forEach { listeners.remove(it.key) }
|
||||
fun sendResult(key: String, data: Any) {
|
||||
listeners.remove(key)?.onResult(data)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ open class Router : BaseRouter() {
|
||||
* @param screen screen
|
||||
* @param clearContainer if FALSE then new screen shows over previous
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun navigateTo(screen: Screen, clearContainer: Boolean = true) {
|
||||
executeCommands(Forward(screen, clearContainer))
|
||||
}
|
||||
@@ -58,6 +59,7 @@ open class Router : BaseRouter() {
|
||||
* @param screens
|
||||
* @param showOnlyTopScreenView if FALSE then all screen views show together
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun newChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
|
||||
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
|
||||
executeCommands(*commands.toTypedArray())
|
||||
@@ -69,6 +71,7 @@ open class Router : BaseRouter() {
|
||||
* @param screens
|
||||
* @param showOnlyTopScreenView if FALSE then all screen views show together
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun newRootChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
|
||||
val commands = screens.mapIndexed { index, screen ->
|
||||
if (index == 0)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.github.terrakok.cicerone
|
||||
|
||||
/**
|
||||
* Screen is class for description application screen.
|
||||
* Screen is interface for description application screen.
|
||||
*/
|
||||
abstract class Screen {
|
||||
open val screenKey: String = this::class.qualifiedName!!
|
||||
interface Screen {
|
||||
val screenKey: String get() = this::class.java.name
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.terrakok.cicerone.androidx
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.*
|
||||
import com.github.terrakok.cicerone.*
|
||||
@@ -13,11 +14,11 @@ import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.REPLACE
|
||||
*
|
||||
* Recommendation: most useful for Single-Activity application.
|
||||
*/
|
||||
open class AppNavigator constructor(
|
||||
open class AppNavigator @JvmOverloads constructor(
|
||||
protected val activity: FragmentActivity,
|
||||
protected val containerId: Int,
|
||||
protected val fragmentManager: FragmentManager = activity.supportFragmentManager,
|
||||
protected val fragmentFactory: FragmentFactory = FragmentFactory()
|
||||
protected val fragmentFactory: FragmentFactory = fragmentManager.fragmentFactory
|
||||
) : Navigator {
|
||||
|
||||
protected val localStackCopy = mutableListOf<TransactionInfo>()
|
||||
@@ -109,6 +110,7 @@ open class AppNavigator constructor(
|
||||
) {
|
||||
val fragment = screen.createFragment(fragmentFactory)
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
transaction.setReorderingAllowed(true)
|
||||
setupFragmentTransaction(
|
||||
transaction,
|
||||
fragmentManager.findFragmentById(containerId),
|
||||
@@ -170,9 +172,9 @@ open class AppNavigator constructor(
|
||||
private fun checkAndStartActivity(screen: ActivityScreen) {
|
||||
// Check if we can start activity
|
||||
val activityIntent = screen.createIntent(activity)
|
||||
if (activityIntent.resolveActivity(activity.packageManager) != null) {
|
||||
try {
|
||||
activity.startActivity(activityIntent, screen.startActivityOptions)
|
||||
} else {
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
unexistingActivity(screen, activityIntent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,25 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentFactory
|
||||
import com.github.terrakok.cicerone.Screen
|
||||
|
||||
sealed class AppScreen : Screen()
|
||||
sealed class AppScreen : Screen
|
||||
|
||||
open class FragmentScreen(
|
||||
override val screenKey: String,
|
||||
val createFragment: (FragmentFactory) -> Fragment
|
||||
) : AppScreen()
|
||||
fun interface Creator<A, R> {
|
||||
fun create(argument: A): R
|
||||
}
|
||||
|
||||
open class ActivityScreen(
|
||||
override val screenKey: String,
|
||||
val createIntent: (context: Context) -> Intent
|
||||
open class FragmentScreen @JvmOverloads constructor(
|
||||
private val key: String? = null,
|
||||
private val fragmentCreator: Creator<FragmentFactory, Fragment>
|
||||
) : AppScreen() {
|
||||
override val screenKey: String get() = key ?: super.screenKey
|
||||
fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
|
||||
}
|
||||
|
||||
open class ActivityScreen @JvmOverloads constructor(
|
||||
private val key: String? = null,
|
||||
private val intentCreator: Creator<Context, Intent>
|
||||
) : AppScreen() {
|
||||
override val screenKey: String get() = key ?: super.screenKey
|
||||
open val startActivityOptions: Bundle? = null
|
||||
fun createIntent(context: Context) = intentCreator.create(context)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ data class TransactionInfo(
|
||||
override fun toString() = screenKey + type.symbol
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromString(str: String) = TransactionInfo(
|
||||
str.dropLast(1),
|
||||
if (str.last() == Type.ADD.symbol) Type.ADD else Type.REPLACE
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.github.terrakok.cicerone.graph
|
||||
|
||||
import com.github.terrakok.cicerone.*
|
||||
|
||||
|
||||
class GraphRouter(
|
||||
private val root: Root
|
||||
) : BaseRouter() {
|
||||
private val vertexes: Map<String, Vertex>
|
||||
private val currentPath: MutableList<String>
|
||||
val currentVertex get() = vertexes.getValue(currentPath.last())
|
||||
|
||||
init {
|
||||
val allVertexes = mutableMapOf<String, Vertex>()
|
||||
val links = mutableSetOf<VertexLink>()
|
||||
val jumps = mutableListOf<List<String>>()
|
||||
validateGraph(root.vertex, allVertexes, links, jumps)
|
||||
|
||||
val linkAndJumpIds = links.map { it.id } + jumps.flatten()
|
||||
linkAndJumpIds.forEach { id ->
|
||||
require(allVertexes.containsKey(id)) { "Not found vertex for id=$id" }
|
||||
}
|
||||
|
||||
vertexes = allVertexes
|
||||
currentPath = mutableListOf(root.vertex.id)
|
||||
|
||||
jumps.forEach {
|
||||
require(validateJump(it)) { "Invalid jump path=$it" }
|
||||
}
|
||||
|
||||
if (root.defaultDestination != null) {
|
||||
require(root.vertex.edges.any { it.id == root.defaultDestination }) { "Not found default destination" }
|
||||
navigateTo(root.defaultDestination)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateGraph(
|
||||
vertex: Vertex,
|
||||
allVertexes: MutableMap<String, Vertex>,
|
||||
links: MutableSet<VertexLink>,
|
||||
jumps: MutableList<List<String>>
|
||||
) {
|
||||
if (allVertexes.containsKey(vertex.id)) error("Graph contains duplicate id ${vertex.id}")
|
||||
allVertexes[vertex.id] = vertex
|
||||
|
||||
links.addAll(vertex.edges.filterIsInstance<VertexLink>())
|
||||
jumps.addAll(vertex.jumps.map { listOf(it.backTo ?: vertex.id, *it.chain.toTypedArray()) })
|
||||
vertex.edges.forEach {
|
||||
if (it !is VertexLink) validateGraph(it, allVertexes, links, jumps)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateJump(path: List<String>): Boolean {
|
||||
if (path.isEmpty()) return false
|
||||
if (path.size == 1) return true
|
||||
|
||||
val v = vertexes.getValue(path[0])
|
||||
v.edges.firstOrNull { it.id == path[1] } ?: return false
|
||||
return validateJump(path.subList(1, path.size))
|
||||
}
|
||||
|
||||
fun navigateTo(
|
||||
vertexId: String,
|
||||
screenFactory: (vertexId: String) -> Screen? = { null }
|
||||
) {
|
||||
val destination = currentVertex.edges.first { it.id == vertexId }
|
||||
val screen = createScreen(destination.id, screenFactory)
|
||||
val command =
|
||||
if (currentVertex.id == Root.ID) Replace(screen)
|
||||
else Forward(screen, destination.destroyPreviousView)
|
||||
currentPath.add(destination.id)
|
||||
executeCommands(command)
|
||||
}
|
||||
|
||||
fun jumpTo(
|
||||
jumpId: String,
|
||||
screenFactory: (vertexId: String) -> Screen? = { null }
|
||||
) {
|
||||
val jump = currentVertex.jumps.first { it.id == jumpId }
|
||||
|
||||
if (jump.backTo == Root.ID && jump.chain.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
val commands = mutableListOf<Command>().apply {
|
||||
if (jump.backTo != null) {
|
||||
val id = jump.backTo
|
||||
val index = currentPath.indexOfFirst { it == id }
|
||||
if (index == -1) error("Current path doesn't contain vertex $id")
|
||||
currentPath.subList(index + 1, currentPath.size).clear()
|
||||
if (jump.backTo == Root.ID) add(BackTo(null))
|
||||
else add(BackTo(Key(id)))
|
||||
}
|
||||
jump.chain.forEachIndexed { index, vertexId ->
|
||||
val screen = createScreen(vertexId, screenFactory)
|
||||
currentPath.add(vertexId)
|
||||
if (index == 0 && jump.backTo == Root.ID) {
|
||||
add(Replace(screen))
|
||||
} else {
|
||||
add(Forward(screen, vertexes.getValue(vertexId).destroyPreviousView))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeCommands(*commands.toTypedArray())
|
||||
}
|
||||
|
||||
fun exit() {
|
||||
currentPath.removeLast()
|
||||
executeCommands(Back())
|
||||
}
|
||||
|
||||
private fun finish() {
|
||||
currentPath.clear()
|
||||
currentPath.add(Root.ID)
|
||||
executeCommands(BackTo(null), Back())
|
||||
}
|
||||
|
||||
private fun createScreen(
|
||||
vertexId: String,
|
||||
screenFactory: (vertexId: String) -> Screen?
|
||||
): Screen {
|
||||
val screen = screenFactory(vertexId)
|
||||
?: vertexes.getValue(vertexId).screenFactory(vertexId)
|
||||
?: error("Unknown screen for vertex $vertexId")
|
||||
require(screen.screenKey == vertexId) { "Screen key must be equal vertex id!" }
|
||||
return screen
|
||||
}
|
||||
|
||||
private class Key(
|
||||
override val screenKey: String
|
||||
) : Screen
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.github.terrakok.cicerone.graph
|
||||
|
||||
import com.github.terrakok.cicerone.*
|
||||
|
||||
open class Vertex internal constructor(
|
||||
val id: String,
|
||||
val edges: Set<Vertex>,
|
||||
val jumps: Set<Jump>,
|
||||
val destroyPreviousView: Boolean = true,
|
||||
val screenFactory: (id: String) -> Screen? = { null }
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Vertex
|
||||
|
||||
if (id != other.id) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
internal class VertexLink(
|
||||
id: String,
|
||||
destroyPreviousView: Boolean = true
|
||||
): Vertex(id, emptySet(), emptySet(), destroyPreviousView)
|
||||
|
||||
class Jump(
|
||||
val id: String,
|
||||
val backTo: String?,
|
||||
val chain: List<String>
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Jump
|
||||
|
||||
if (id != other.id) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
//Graph DSL
|
||||
|
||||
class GraphInfo(
|
||||
var edges: MutableSet<Vertex>.() -> Unit = {},
|
||||
var jumps: MutableSet<Jump>.() -> Unit = {}
|
||||
)
|
||||
|
||||
class Root(
|
||||
val vertex: Vertex,
|
||||
val defaultDestination: String?
|
||||
) {
|
||||
companion object {
|
||||
const val ID = "graph-root"
|
||||
}
|
||||
}
|
||||
|
||||
fun graph(
|
||||
defaultDestination: String? = null,
|
||||
setup: GraphInfo.() -> Unit
|
||||
): Root {
|
||||
val info = GraphInfo().apply(setup)
|
||||
val v = Vertex(
|
||||
Root.ID,
|
||||
mutableSetOf<Vertex>().apply(info.edges),
|
||||
mutableSetOf<Jump>().apply(info.jumps)
|
||||
)
|
||||
return Root(v, defaultDestination)
|
||||
}
|
||||
|
||||
class VertexInfo(
|
||||
var edges: MutableSet<Vertex>.() -> Unit = {},
|
||||
var jumps: MutableSet<Jump>.() -> Unit = {},
|
||||
var screen: (id: String) -> Screen? = { null }
|
||||
)
|
||||
|
||||
fun MutableSet<Vertex>.dest(
|
||||
id: String,
|
||||
destroyPreviousView: Boolean = true,
|
||||
setup: VertexInfo.() -> Unit = {}
|
||||
) {
|
||||
val info = VertexInfo().apply(setup)
|
||||
add(Vertex(
|
||||
id,
|
||||
mutableSetOf<Vertex>().apply(info.edges),
|
||||
mutableSetOf<Jump>().apply(info.jumps),
|
||||
destroyPreviousView,
|
||||
info.screen
|
||||
))
|
||||
}
|
||||
|
||||
fun MutableSet<Vertex>.edge(
|
||||
id: String,
|
||||
destroyPreviousView: Boolean = true
|
||||
) {
|
||||
add(VertexLink(id, destroyPreviousView))
|
||||
}
|
||||
|
||||
class JumpInfo(
|
||||
var backTo: String? = null,
|
||||
internal var chain: List<String> = emptyList()
|
||||
) {
|
||||
fun chain(vararg id: String) {
|
||||
chain = id.toList()
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableSet<Jump>.jump(
|
||||
id: String,
|
||||
setup: JumpInfo.() -> Unit
|
||||
) {
|
||||
val info = JumpInfo().apply(setup)
|
||||
add(Jump(
|
||||
id,
|
||||
info.backTo,
|
||||
info.chain
|
||||
))
|
||||
}
|
||||
|
||||
fun MutableSet<Jump>.finish(id: String) {
|
||||
add(Jump(id, Root.ID, emptyList()))
|
||||
}
|
||||
@@ -36,6 +36,10 @@ public class FragmentManager {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public FragmentFactory getFragmentFactory() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public interface BackStackEntry {
|
||||
int getId();
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ package androidx.fragment.app;
|
||||
*/
|
||||
|
||||
public class FragmentTransaction {
|
||||
public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
@@ -21,5 +21,6 @@
|
||||
<activity android:name=".ui.main.MainActivity"/>
|
||||
<activity android:name=".ui.bottom.BottomNavigationActivity"/>
|
||||
<activity android:name=".ui.animations.ProfileActivity"/>
|
||||
<activity android:name=".ui.graph.GraphActivity"/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.github.terrakok.cicerone.sample
|
||||
|
||||
import com.github.terrakok.cicerone.androidx.FragmentScreen
|
||||
import com.github.terrakok.cicerone.graph.*
|
||||
import com.github.terrakok.cicerone.sample.ui.graph.ForkFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.graph.RoadFragment
|
||||
|
||||
private val RoadScreen = { id: String -> FragmentScreen(id) { RoadFragment.getNewInstance(id) } }
|
||||
private val ForkScreen = { id: String -> FragmentScreen(id) { ForkFragment.getNewInstance(id) } }
|
||||
|
||||
fun Graph() = graph("8") {
|
||||
edges = {
|
||||
dest("1") {
|
||||
screen = RoadScreen
|
||||
edges = {
|
||||
dest("2") {
|
||||
screen = ForkScreen
|
||||
edges = {
|
||||
dest("3") {
|
||||
screen = RoadScreen
|
||||
edges = {
|
||||
dest("4") {
|
||||
screen = RoadScreen
|
||||
edges = {
|
||||
edge("5")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dest("5") {
|
||||
screen = ForkScreen
|
||||
edges = {
|
||||
dest("6") {
|
||||
screen = RoadScreen
|
||||
}
|
||||
dest("7") {
|
||||
screen = RoadScreen
|
||||
jumps = {
|
||||
finish("1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dest("8") {
|
||||
screen = ForkScreen
|
||||
edges = {
|
||||
dest("9") {
|
||||
screen = RoadScreen
|
||||
edges = {
|
||||
edge("1")
|
||||
}
|
||||
jumps = {
|
||||
jump("1") {
|
||||
backTo = Root.ID
|
||||
chain("1", "2", "3", "4", "5", "7")
|
||||
}
|
||||
}
|
||||
}
|
||||
dest("10") {
|
||||
screen = ForkScreen
|
||||
edges = {
|
||||
dest("11") {
|
||||
screen = RoadScreen
|
||||
}
|
||||
edge("10")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
|
||||
import com.github.terrakok.cicerone.sample.ui.bottom.ForwardFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.graph.GraphActivity
|
||||
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
|
||||
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
|
||||
@@ -24,39 +25,43 @@ object Screens {
|
||||
SampleFragment.getNewInstance(number)
|
||||
}
|
||||
|
||||
fun Start() = ActivityScreen("Start") {
|
||||
fun Start() = ActivityScreen {
|
||||
Intent(it, StartActivity::class.java)
|
||||
}
|
||||
|
||||
fun Main() = ActivityScreen("Main") {
|
||||
fun Main() = ActivityScreen {
|
||||
Intent(it, MainActivity::class.java)
|
||||
}
|
||||
|
||||
fun BottomNavigation() = ActivityScreen("BottomNavigation") {
|
||||
fun BottomNavigation() = ActivityScreen {
|
||||
Intent(it, BottomNavigationActivity::class.java)
|
||||
}
|
||||
|
||||
fun Tab(tabName: String) = FragmentScreen("Tab") {
|
||||
fun Tab(tabName: String) = FragmentScreen {
|
||||
TabContainerFragment.getNewInstance(tabName)
|
||||
}
|
||||
|
||||
fun Forward(containerName: String, number: Int) = FragmentScreen("Forward") {
|
||||
fun Forward(containerName: String, number: Int) = FragmentScreen {
|
||||
ForwardFragment.getNewInstance(containerName, number)
|
||||
}
|
||||
|
||||
fun Github() = ActivityScreen("Github") {
|
||||
fun Github() = ActivityScreen {
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"))
|
||||
}
|
||||
|
||||
fun Profile() = ActivityScreen("Profile") {
|
||||
fun Profile() = ActivityScreen {
|
||||
Intent(it, ProfileActivity::class.java)
|
||||
}
|
||||
|
||||
fun ProfileInfo() = FragmentScreen("ProfileInfo") {
|
||||
fun ProfileInfo() = FragmentScreen {
|
||||
ProfileFragment()
|
||||
}
|
||||
|
||||
fun SelectPhoto(resultKey: String) = FragmentScreen("SelectPhoto") {
|
||||
fun SelectPhoto(resultKey: String) = FragmentScreen {
|
||||
SelectPhotoFragment.getNewInstance(resultKey)
|
||||
}
|
||||
|
||||
fun Graph() = ActivityScreen {
|
||||
Intent(it, GraphActivity::class.java)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragm
|
||||
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
|
||||
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.graph.ForkFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.graph.GraphActivity
|
||||
import com.github.terrakok.cicerone.sample.ui.graph.RoadFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
|
||||
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
|
||||
@@ -37,4 +40,10 @@ interface AppComponent {
|
||||
fun inject(fragment: SelectPhotoFragment)
|
||||
|
||||
fun inject(activity: ProfileActivity)
|
||||
|
||||
fun inject(activity: GraphActivity)
|
||||
|
||||
fun inject(fragment: RoadFragment)
|
||||
|
||||
fun inject(fragment: ForkFragment)
|
||||
}
|
||||
+17
@@ -4,8 +4,11 @@ import com.github.terrakok.cicerone.Cicerone
|
||||
import com.github.terrakok.cicerone.Cicerone.Companion.create
|
||||
import com.github.terrakok.cicerone.NavigatorHolder
|
||||
import com.github.terrakok.cicerone.Router
|
||||
import com.github.terrakok.cicerone.graph.GraphRouter
|
||||
import com.github.terrakok.cicerone.sample.Graph
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
@@ -14,6 +17,7 @@ import javax.inject.Singleton
|
||||
@Module
|
||||
class NavigationModule {
|
||||
private val cicerone: Cicerone<Router> = create()
|
||||
private val graphCicerone: Cicerone<GraphRouter> = create(GraphRouter(Graph()))
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@@ -26,4 +30,17 @@ class NavigationModule {
|
||||
fun provideNavigatorHolder(): NavigatorHolder {
|
||||
return cicerone.getNavigatorHolder()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGraphRouter(): GraphRouter {
|
||||
return graphCicerone.router
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("graph")
|
||||
fun provideGraphNavigatorHolder(): NavigatorHolder {
|
||||
return graphCicerone.getNavigatorHolder()
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -1,5 +1,6 @@
|
||||
package com.github.terrakok.cicerone.sample.mvp.animation.profile
|
||||
|
||||
import com.github.terrakok.cicerone.ResultListenerHandler
|
||||
import com.github.terrakok.cicerone.Router
|
||||
import com.github.terrakok.cicerone.sample.R
|
||||
import com.github.terrakok.cicerone.sample.Screens.SelectPhoto
|
||||
@@ -13,6 +14,7 @@ import moxy.MvpPresenter
|
||||
class ProfilePresenter(
|
||||
private val router: Router
|
||||
) : MvpPresenter<ProfileView>() {
|
||||
private var resultListenerHandler: ResultListenerHandler? = null
|
||||
|
||||
companion object {
|
||||
private const val RESULT_KEY = "photo_result"
|
||||
@@ -25,12 +27,17 @@ class ProfilePresenter(
|
||||
}
|
||||
|
||||
fun onPhotoClicked() {
|
||||
router.setResultListener(RESULT_KEY) { data ->
|
||||
resultListenerHandler = router.setResultListener(RESULT_KEY) { data ->
|
||||
viewState!!.showPhoto(data as Int)
|
||||
}
|
||||
router.navigateTo(SelectPhoto(RESULT_KEY))
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
resultListenerHandler?.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
fun onBackPressed() {
|
||||
router.exit()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.github.terrakok.cicerone.sample.mvp.graph
|
||||
|
||||
import com.github.terrakok.cicerone.graph.GraphRouter
|
||||
import moxy.InjectViewState
|
||||
import moxy.MvpPresenter
|
||||
import moxy.MvpView
|
||||
|
||||
@InjectViewState
|
||||
class ForkPresenter(
|
||||
private val graphRouter: GraphRouter
|
||||
): MvpPresenter<MvpView>() {
|
||||
|
||||
fun onTopButtonClick() {
|
||||
graphRouter.currentVertex.edges.firstOrNull()?.id?.let { id ->
|
||||
graphRouter.navigateTo(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun onBottomButtonClick() {
|
||||
graphRouter.currentVertex.edges.lastOrNull()?.id?.let { id ->
|
||||
graphRouter.navigateTo(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun onBackPressed() {
|
||||
graphRouter.exit()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.github.terrakok.cicerone.sample.mvp.graph
|
||||
|
||||
import com.github.terrakok.cicerone.graph.GraphRouter
|
||||
import moxy.InjectViewState
|
||||
import moxy.MvpPresenter
|
||||
import moxy.MvpView
|
||||
|
||||
@InjectViewState
|
||||
class RoadPresenter(
|
||||
private val graphRouter: GraphRouter
|
||||
): MvpPresenter<MvpView>() {
|
||||
|
||||
fun onButtonClick() {
|
||||
graphRouter.currentVertex.edges.firstOrNull()?.id?.let { id ->
|
||||
graphRouter.navigateTo(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun onJumpClick() {
|
||||
graphRouter.currentVertex.jumps.firstOrNull()?.id?.let { id ->
|
||||
graphRouter.jumpTo(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun onBackPressed() {
|
||||
graphRouter.exit()
|
||||
}
|
||||
}
|
||||
+5
@@ -2,6 +2,7 @@ package com.github.terrakok.cicerone.sample.mvp.start
|
||||
|
||||
import com.github.terrakok.cicerone.Router
|
||||
import com.github.terrakok.cicerone.sample.Screens.BottomNavigation
|
||||
import com.github.terrakok.cicerone.sample.Screens.Graph
|
||||
import com.github.terrakok.cicerone.sample.Screens.Main
|
||||
import com.github.terrakok.cicerone.sample.Screens.Profile
|
||||
import moxy.MvpPresenter
|
||||
@@ -23,6 +24,10 @@ class StartActivityPresenter(private val router: Router) : MvpPresenter<StartAct
|
||||
router.navigateTo(Profile())
|
||||
}
|
||||
|
||||
fun onGraphPressed() {
|
||||
router.navigateTo(Graph())
|
||||
}
|
||||
|
||||
fun onBackPressed() {
|
||||
router.exit()
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class ProfileActivity : AppCompatActivity() {
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.container) {
|
||||
|
||||
override fun setupFragmentTransaction(fragmentTransaction: FragmentTransaction, currentFragment: Fragment?, nextFragment: Fragment?) {
|
||||
if (currentFragment is ProfileFragment
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ class BottomNavigationActivity : MvpAppCompatActivity(), BottomNavigationView, R
|
||||
if (newFragment == null) {
|
||||
transaction.add(
|
||||
R.id.ab_container,
|
||||
Tab(tab).createFragment.invoke(fm.fragmentFactory), tab
|
||||
Tab(tab).createFragment(fm.fragmentFactory), tab
|
||||
)
|
||||
}
|
||||
if (currentFragment != null) {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import javax.inject.Inject
|
||||
class TabContainerFragment : Fragment(), RouterProvider, BackButtonListener {
|
||||
|
||||
private val navigator: Navigator by lazy {
|
||||
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager, childFragmentManager.fragmentFactory)
|
||||
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager)
|
||||
}
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.github.terrakok.cicerone.sample.ui.graph
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.github.terrakok.cicerone.graph.GraphRouter
|
||||
import com.github.terrakok.cicerone.sample.SampleApplication
|
||||
import com.github.terrakok.cicerone.sample.databinding.FragmentForkBinding
|
||||
import com.github.terrakok.cicerone.sample.databinding.FragmentRoadBinding
|
||||
import com.github.terrakok.cicerone.sample.mvp.graph.ForkPresenter
|
||||
import com.github.terrakok.cicerone.sample.mvp.graph.RoadPresenter
|
||||
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
|
||||
import moxy.MvpAppCompatFragment
|
||||
import moxy.MvpView
|
||||
import moxy.presenter.InjectPresenter
|
||||
import moxy.presenter.ProvidePresenter
|
||||
import javax.inject.Inject
|
||||
|
||||
class ForkFragment : MvpAppCompatFragment(), MvpView, BackButtonListener {
|
||||
lateinit var binding: FragmentForkBinding
|
||||
|
||||
@Inject
|
||||
lateinit var graphRouter: GraphRouter
|
||||
|
||||
@InjectPresenter
|
||||
lateinit var presenter: ForkPresenter
|
||||
|
||||
@ProvidePresenter
|
||||
fun provideRoadPresenter(): ForkPresenter {
|
||||
return ForkPresenter(graphRouter)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
SampleApplication.INSTANCE.appComponent.inject(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentForkBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
binding.textView.text = arguments?.getString(EXTRA_NUMBER)
|
||||
binding.topButton.setOnClickListener { presenter.onTopButtonClick() }
|
||||
binding.bottomButton.setOnClickListener { presenter.onBottomButtonClick()}
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
presenter.onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_NUMBER = "extra_number"
|
||||
|
||||
fun getNewInstance(number: String) = ForkFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(EXTRA_NUMBER, number)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.github.terrakok.cicerone.sample.ui.graph
|
||||
|
||||
import android.os.Bundle
|
||||
import android.transition.ChangeBounds
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.github.terrakok.cicerone.Command
|
||||
import com.github.terrakok.cicerone.Navigator
|
||||
import com.github.terrakok.cicerone.NavigatorHolder
|
||||
import com.github.terrakok.cicerone.Replace
|
||||
import com.github.terrakok.cicerone.androidx.AppNavigator
|
||||
import com.github.terrakok.cicerone.graph.GraphRouter
|
||||
import com.github.terrakok.cicerone.sample.R
|
||||
import com.github.terrakok.cicerone.sample.SampleApplication
|
||||
import com.github.terrakok.cicerone.sample.Screens.ProfileInfo
|
||||
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
/**
|
||||
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
|
||||
*/
|
||||
class GraphActivity : AppCompatActivity() {
|
||||
|
||||
@Inject
|
||||
@Named("graph")
|
||||
lateinit var navigatorHolder: NavigatorHolder
|
||||
|
||||
@Inject
|
||||
lateinit var graphRouter: GraphRouter
|
||||
|
||||
private val navigator = AppNavigator(this, R.id.container)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
SampleApplication.INSTANCE.appComponent.inject(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_container)
|
||||
}
|
||||
|
||||
override fun onResumeFragments() {
|
||||
super.onResumeFragments()
|
||||
navigatorHolder.setNavigator(navigator)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
navigatorHolder.removeNavigator()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
val fragment = supportFragmentManager.findFragmentById(R.id.container)
|
||||
if (fragment != null && fragment is BackButtonListener
|
||||
&& (fragment as BackButtonListener).onBackPressed()) {
|
||||
return
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.github.terrakok.cicerone.sample.ui.graph
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.github.terrakok.cicerone.graph.GraphRouter
|
||||
import com.github.terrakok.cicerone.sample.SampleApplication
|
||||
import com.github.terrakok.cicerone.sample.databinding.FragmentRoadBinding
|
||||
import com.github.terrakok.cicerone.sample.mvp.graph.RoadPresenter
|
||||
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
|
||||
import moxy.MvpAppCompatFragment
|
||||
import moxy.MvpView
|
||||
import moxy.presenter.InjectPresenter
|
||||
import moxy.presenter.ProvidePresenter
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoadFragment : MvpAppCompatFragment(), MvpView, BackButtonListener {
|
||||
lateinit var binding: FragmentRoadBinding
|
||||
|
||||
@Inject
|
||||
lateinit var graphRouter: GraphRouter
|
||||
|
||||
@InjectPresenter
|
||||
lateinit var presenter: RoadPresenter
|
||||
|
||||
@ProvidePresenter
|
||||
fun provideRoadPresenter(): RoadPresenter {
|
||||
return RoadPresenter(graphRouter)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
SampleApplication.INSTANCE.appComponent.inject(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentRoadBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
binding.textView.text = arguments?.getString(EXTRA_NUMBER)
|
||||
binding.forwardButton.setOnClickListener { presenter.onButtonClick() }
|
||||
binding.jumpButton.setOnClickListener { presenter.onJumpClick() }
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
presenter.onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_NUMBER = "extra_number"
|
||||
|
||||
fun getNewInstance(number: String) = RoadFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(EXTRA_NUMBER, number)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class MainActivity : MvpAppCompatActivity(), ChainHolder {
|
||||
@Inject
|
||||
lateinit var navigatorHolder: NavigatorHolder
|
||||
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container) {
|
||||
|
||||
override fun applyCommands(commands: Array<out Command>) {
|
||||
super.applyCommands(commands)
|
||||
|
||||
@@ -29,7 +29,7 @@ class StartActivity : MvpAppCompatActivity(), StartActivityView {
|
||||
@InjectPresenter
|
||||
lateinit var presenter: StartActivityPresenter
|
||||
|
||||
private val navigator: Navigator = AppNavigator(this, -1, supportFragmentManager, supportFragmentManager.fragmentFactory)
|
||||
private val navigator: Navigator = AppNavigator(this, -1)
|
||||
|
||||
@ProvidePresenter
|
||||
fun createStartActivityPresenter() = StartActivityPresenter(router)
|
||||
@@ -52,6 +52,9 @@ class StartActivity : MvpAppCompatActivity(), StartActivityView {
|
||||
findViewById<View>(R.id.result_and_anim_button).setOnClickListener {
|
||||
presenter.onResultWithAnimationPressed()
|
||||
}
|
||||
findViewById<View>(R.id.graph_button).setOnClickListener {
|
||||
presenter.onGraphPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -43,4 +43,11 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/result_and_anim_nav"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/graph_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="@string/graph_nav"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="16dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="13sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/top_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:minWidth="100dp"
|
||||
android:text="➔"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottom_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:minWidth="100dp"
|
||||
android:text="➔"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="16dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="13sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/forward_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:minWidth="100dp"
|
||||
android:text="➔"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/jump_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:minWidth="100dp"
|
||||
android:text="JUMP"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
@@ -4,6 +4,7 @@
|
||||
<string name="ordinary_nav">Ordinary navigation</string>
|
||||
<string name="multi_nav">Multi navigation</string>
|
||||
<string name="result_and_anim_nav">Resulting and animation sample</string>
|
||||
<string name="graph_nav">Navigation graph</string>
|
||||
|
||||
<string name="tab_android">Android</string>
|
||||
<string name="tab_bug">Bug</string>
|
||||
|
||||
Reference in New Issue
Block a user