37 Commits

Author SHA1 Message Date
terrakok 8b2654ee2b WIP: add default destination. 2020-11-28 17:08:24 +03:00
terrakok 8c8521d2c5 WIP: chain builder as function. 2020-11-28 17:08:24 +03:00
terrakok 14b94927e4 WIP: add jumps. 2020-11-28 17:08:24 +03:00
terrakok 1e40fb29bb WIP: add link between vertex. 2020-11-28 17:08:24 +03:00
terrakok dbc8c3363f WIP: navigation graph. 2020-11-28 17:08:24 +03:00
terrakok c3873c6e9d Bump version to "6.6" 2020-11-26 13:31:05 +03:00
terrakok f44410fcfe Fix problem with loss of result listener.
Issue #130
2020-11-26 13:30:22 +03:00
Konstantin 608a32c3be Update README.md 2020-11-18 22:34:28 +03:00
Konstantin 17428f0189 Update README.md 2020-11-18 22:08:21 +03:00
terrakok 1701bc4f36 Fixed launching Activity via no concrete Intent. 2020-11-18 22:02:18 +03:00
terrakok 9c9c56bc9b Bump version to "6.4" 2020-11-05 17:24:22 +03:00
terrakok 3da8fe52ca Set fragmentReorderingAllowed=TRUE by default. 2020-11-05 17:21:01 +03:00
terrakok a943aab08e Move ResultListener to another file. 2020-11-05 16:15:28 +03:00
Konstantin 86ac421fb8 Merge pull request #128 from aradxxx/feature/java-interop
java interop
2020-11-05 16:06:50 +03:00
aradxxx f7009f53e8 java interop 2020-11-03 05:13:13 +04:00
Konstantin 994c497147 Update README.md 2020-11-02 20:55:34 +03:00
Konstantin bb13ace1c9 Update README.md 2020-11-02 15:29:24 +03:00
Konstantin dbe065275d Update README.md 2020-11-02 15:15:24 +03:00
Konstantin 2ebbc98a99 Update README.md 2020-11-02 15:10:17 +03:00
Konstantin ba7eea60f5 Update README.md 2020-11-02 10:10:17 +03:00
Konstantin 6347fe7d95 Update README.md 2020-11-01 21:30:28 +03:00
Konstantin 2b6e2ee874 Update README.md 2020-11-01 18:34:03 +03:00
terrakok f50a4d8106 Bump version to "6.3" 2020-11-01 17:07:08 +03:00
terrakok 3535f491f5 Refactor Screen class as interface. 2020-11-01 17:07:04 +03:00
Konstantin 1c5b89d2ec Update README.md 2020-11-01 14:54:59 +03:00
Konstantin 127cce1fe4 Update README.md 2020-11-01 14:06:53 +03:00
terrakok 3bea311400 Bump version to "6.2" 2020-10-30 12:31:15 +03:00
terrakok a0b3dfa71e Fix sample app design 2020-10-30 12:27:24 +03:00
Konstantin 0e9ff3f7f5 Merge pull request #126 from MonStar1/fix/newRootChain
fix newRootChain
2020-10-30 12:12:13 +03:00
Andrei Papko 004faf77c4 fix newRootChain 2020-10-28 17:21:57 +03:00
Konstantin 214a6eddf1 Update README.md 2020-10-21 17:32:59 +03:00
terrakok a4c94003ad Bump version to "6.1" 2020-10-21 17:14:18 +03:00
terrakok 4f7c1c1607 Add screenKey to fragment transaction tag. 2020-10-21 17:14:18 +03:00
terrakok 16f8fd833e Refactor sample screens names 2020-10-21 17:14:18 +03:00
terrakok 77c3ecc76e Rename ResultBus to ResultWire 2020-10-21 17:14:18 +03:00
terrakok 062073cb37 Add simple result messaging. 2020-10-21 17:14:18 +03:00
Konstantin b9cddc290e Update README.md 2020-10-19 15:52:39 +03:00
45 changed files with 1000 additions and 178 deletions
+76 -18
View File
@@ -1,20 +1,44 @@
# Cicerone
[![jCenter](https://api.bintray.com/packages/terrakok/terramaven/cicerone/images/download.svg)](https://bintray.com/terrakok/terramaven/cicerone/_latestVersion)
[![jCenter](https://api.bintray.com/packages/terrakok/terramaven/cicerone-kotlin/images/download.svg)](https://bintray.com/terrakok/terramaven/cicerone-kotlin/_latestVersion)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Cicerone-green.svg?style=true)](https://android-arsenal.com/details/1/4700)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-250-green.svg)](http://androidweekly.net/issues/issue-250)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-271-green.svg)](http://androidweekly.net/issues/issue-271)
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
<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
@@ -56,7 +80,7 @@ class App : Application() {
```
## How it works?
<img src="https://github.com/terrakok/Cicerone/raw/develop/media/CiceroneDiagram.png" alt="drawing" width="800"/>
<img src="https://github.com/terrakok/Cicerone/blob/master/media/CiceroneDiagram.png" alt="CiceroneDiagram.png" width="800"/>
Presenter calls navigation method of Router.
@@ -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
![](https://github.com/terrakok/Cicerone/raw/develop/media/forward_img.png)
![](https://github.com/terrakok/Cicerone/raw/master/media/forward_img.png)
+ Back - Rolls back the last transition
![](https://github.com/terrakok/Cicerone/raw/develop/media/back_img.png)
![](https://github.com/terrakok/Cicerone/raw/master/media/back_img.png)
+ BackTo - Rolls back to the needed screen in the screens chain
![](https://github.com/terrakok/Cicerone/raw/develop/media/backTo_img.png)
![](https://github.com/terrakok/Cicerone/raw/master/media/backTo_img.png)
+ Replace - Replaces the current screen
![](https://github.com/terrakok/Cicerone/raw/develop/media/replace_img.png)
![](https://github.com/terrakok/Cicerone/raw/master/media/replace_img.png)
## Predefined navigator
The library provides predefined navigator for _Fragments_ and _Activity_.
@@ -147,24 +171,58 @@ 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 {
SelectPhotoFragment.getNewInstance(resultKey)
}
```
```kotlin
//listen result
fun onSelectPhotoClicked() {
router.setResultListener(RESULT_KEY) { data ->
view.showPhoto(data as Bitmap)
}
router.navigateTo(SelectPhoto(RESULT_KEY))
}
//send result
fun onPhotoClick(photo: Bitmap) {
router.sendResult(resultKey, photoRes)
router.exit()
}
```
## Sample
To see how to add, initialize and use the library and predefined navigators check out
the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
To see how to add, initialize and use the library and predefined navigators see **sample project**
(thank you [@Javernaut](https://github.com/Javernaut) for support new library version and migrate sample project to Kotlin!)
![](https://github.com/terrakok/Cicerone/raw/develop/media/navigation.gif)
![](https://github.com/terrakok/Cicerone/raw/develop/media/insta_tabs.gif)
![](https://github.com/terrakok/Cicerone/raw/develop/media/animations.gif)
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)
+1 -1
View File
@@ -18,7 +18,7 @@ ext {
bintrayName = 'cicerone-kotlin'
publishedGroupId = 'com.github.terrakok'
artifact = 'cicerone'
libraryVersion = '6.0'
libraryVersion = '6.6'
gitUrl = 'https://github.com/terrakok/Cicerone'
allLicenses = ['MIT']
}
@@ -7,6 +7,27 @@ package com.github.terrakok.cicerone
*/
abstract class BaseRouter {
internal val commandBuffer = CommandBuffer()
private val resultWire = ResultWire()
/**
* 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: ResultListener
): ResultListenerHandler {
return resultWire.setResultListener(key, listener)
}
/**
* Sends data to listener with given key.
*/
fun sendResult(key: String, data: Any) {
resultWire.sendResult(key, data)
}
/**
* Sends navigation command array to [CommandBuffer].
@@ -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)
}
}
@@ -0,0 +1,30 @@
package com.github.terrakok.cicerone
/**
* 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, ResultListener>()
fun setResultListener(key: String, listener: ResultListener): ResultListenerHandler {
listeners[key] = listener
return ResultListenerHandler {
listeners.remove(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,8 +71,14 @@ 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.map { Forward(it, showOnlyTopScreenView) }
val commands = screens.mapIndexed { index, screen ->
if (index == 0)
Replace(screen)
else
Forward(screen, showOnlyTopScreenView)
}
executeCommands(BackTo(null), *commands.toTypedArray())
}
@@ -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,14 +110,15 @@ open class AppNavigator constructor(
) {
val fragment = screen.createFragment(fragmentFactory)
val transaction = fragmentManager.beginTransaction()
transaction.setReorderingAllowed(true)
setupFragmentTransaction(
transaction,
fragmentManager.findFragmentById(containerId),
fragment
)
when (type) {
ADD -> transaction.add(containerId, fragment)
REPLACE -> transaction.replace(containerId, fragment)
ADD -> transaction.add(containerId, fragment, screen.screenKey)
REPLACE -> transaction.replace(containerId, fragment, screen.screenKey)
}
if (addToBackStack) {
val transactionInfo = TransactionInfo(screen.screenKey, type)
@@ -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();
@@ -1,32 +1,20 @@
package androidx.fragment.app;
import android.os.Bundle;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentTransaction {
public FragmentTransaction add(int containerViewId, Fragment fragment) {
public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
throw new RuntimeException("Stub!");
}
public final FragmentTransaction add(
int containerViewId,
Class<? extends Fragment> fragmentClass,
Bundle args) {
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
throw new RuntimeException("Stub!");
}
public final FragmentTransaction replace(
int containerViewId,
Class<? extends Fragment> fragmentClass,
Bundle args) {
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
throw new RuntimeException("Stub!");
}
+1
View File
@@ -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
@@ -20,43 +21,47 @@ import com.github.terrakok.cicerone.sample.ui.start.StartActivity
*/
object Screens {
fun sampleScreen(number: Int) = FragmentScreen("SampleScreen_$number") {
fun Sample(number: Int) = FragmentScreen("Sample($number)") {
SampleFragment.getNewInstance(number)
}
fun startScreen() = ActivityScreen("StartScreen") {
fun Start() = ActivityScreen {
Intent(it, StartActivity::class.java)
}
fun mainScreen() = ActivityScreen("MainScreen") {
fun Main() = ActivityScreen {
Intent(it, MainActivity::class.java)
}
fun bottomNavigationScreen() = ActivityScreen("BottomNavigationScreen") {
fun BottomNavigation() = ActivityScreen {
Intent(it, BottomNavigationActivity::class.java)
}
fun tabScreen(tabName: String) = FragmentScreen("FragmentScreen") {
fun Tab(tabName: String) = FragmentScreen {
TabContainerFragment.getNewInstance(tabName)
}
fun forwardScreen(containerName: String, number: Int) = FragmentScreen("ForwardScreen") {
fun Forward(containerName: String, number: Int) = FragmentScreen {
ForwardFragment.getNewInstance(containerName, number)
}
fun githubScreen() = ActivityScreen("GithubScreen") {
fun Github() = ActivityScreen {
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"))
}
fun profileScreen() = ActivityScreen("ProfileScreen") {
fun Profile() = ActivityScreen {
Intent(it, ProfileActivity::class.java)
}
fun profileInfoScreen() = FragmentScreen("ProfileInfoScreen") {
fun ProfileInfo() = FragmentScreen {
ProfileFragment()
}
fun selectPhotoScreen() = FragmentScreen("SelectPhotoScreen") {
SelectPhotoFragment()
fun SelectPhoto(resultKey: String) = FragmentScreen {
SelectPhotoFragment.getNewInstance(resultKey)
}
fun Graph() = ActivityScreen {
Intent(it, GraphActivity::class.java)
}
}
@@ -2,12 +2,14 @@ package com.github.terrakok.cicerone.sample.dagger
import com.github.terrakok.cicerone.sample.dagger.module.LocalNavigationModule
import com.github.terrakok.cicerone.sample.dagger.module.NavigationModule
import com.github.terrakok.cicerone.sample.dagger.module.PhotoSelectionModule
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
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.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
@@ -20,8 +22,7 @@ import javax.inject.Singleton
@Singleton
@Component(modules = [
NavigationModule::class,
LocalNavigationModule::class,
PhotoSelectionModule::class]
LocalNavigationModule::class]
)
interface AppComponent {
fun inject(activity: StartActivity)
@@ -39,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)
}
@@ -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()
}
}
@@ -1,20 +0,0 @@
package com.github.terrakok.cicerone.sample.dagger.module
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
object PhotoSelectionModule {
private val photoSelection = PhotoSelection(R.drawable.ava_1)
@Provides
@Singleton
fun providePhotoSelection() = photoSelection
}
@@ -1,26 +0,0 @@
package com.github.terrakok.cicerone.sample.mvp.animation
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 22.09.18.
*/
class PhotoSelection(private var selectedPhoto: Int) {
private var listener: Listener? = null
fun getSelectedPhoto(): Int {
return selectedPhoto
}
fun setSelectedPhoto(selectedPhoto: Int) {
this.selectedPhoto = selectedPhoto
listener?.onChange(selectedPhoto)
}
fun setListener(listener: Listener?) {
this.listener = listener
}
interface Listener {
fun onChange(selectedPhoto: Int)
}
}
@@ -2,7 +2,6 @@ package com.github.terrakok.cicerone.sample.mvp.animation.photos
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import moxy.InjectViewState
import moxy.MvpPresenter
@@ -11,7 +10,7 @@ import moxy.MvpPresenter
*/
@InjectViewState
class SelectPhotoPresenter(
private val photoSelection: PhotoSelection,
private val resultKey: String,
private val router: Router
) : MvpPresenter<SelectPhotoView>() {
@@ -26,7 +25,7 @@ class SelectPhotoPresenter(
}
fun onPhotoClick(photoRes: Int) {
photoSelection.setSelectedPhoto(photoRes)
router.sendResult(resultKey, photoRes)
router.exit()
}
@@ -1,8 +1,9 @@
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.Screens.selectPhotoScreen
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.Screens.SelectPhoto
import moxy.InjectViewState
import moxy.MvpPresenter
@@ -11,37 +12,33 @@ import moxy.MvpPresenter
*/
@InjectViewState
class ProfilePresenter(
private val photoSelection: PhotoSelection,
private val router: Router
) : MvpPresenter<ProfileView>() {
private var resultListenerHandler: ResultListenerHandler? = null
companion object {
private const val RESULT_KEY = "photo_result"
private const val DEFAULT_PHOTO = R.drawable.ava_1
}
override fun onFirstViewAttach() {
super.onFirstViewAttach()
updatePhoto()
}
override fun onDestroy() {
photoSelection.setListener(null)
super.onDestroy()
}
private fun updatePhoto() {
viewState!!.showPhoto(photoSelection.getSelectedPhoto())
viewState!!.showPhoto(DEFAULT_PHOTO)
}
fun onPhotoClicked() {
router.navigateTo(selectPhotoScreen())
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()
}
init {
photoSelection.setListener(object : PhotoSelection.Listener {
override fun onChange(selectedPhoto: Int) {
updatePhoto()
}
})
}
}
@@ -1,8 +1,8 @@
package com.github.terrakok.cicerone.sample.mvp.bottom.forward
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.forwardScreen
import com.github.terrakok.cicerone.sample.Screens.githubScreen
import com.github.terrakok.cicerone.sample.Screens.Forward
import com.github.terrakok.cicerone.sample.Screens.Github
import moxy.InjectViewState
import moxy.MvpPresenter
@@ -25,11 +25,11 @@ class ForwardPresenter(
}
fun onForwardPressed() {
router.navigateTo(forwardScreen(container, number + 1))
router.navigateTo(Forward(container, number + 1))
}
fun onGithubPressed() {
router.navigateTo(githubScreen())
router.navigateTo(Github())
}
fun onBackPressed() {
@@ -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()
}
}
@@ -3,7 +3,7 @@ package com.github.terrakok.cicerone.sample.mvp.main
import android.os.Handler
import android.os.Looper
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.sampleScreen
import com.github.terrakok.cicerone.sample.Screens.Sample
import moxy.InjectViewState
import moxy.MvpPresenter
import java.util.concurrent.Executors
@@ -29,18 +29,26 @@ class SamplePresenter(
}
fun onForwardCommandClick() {
router.navigateTo(sampleScreen(screenNumber + 1))
router.navigateTo(Sample(screenNumber + 1))
}
fun onReplaceCommandClick() {
router.replaceScreen(sampleScreen(screenNumber + 1))
router.replaceScreen(Sample(screenNumber + 1))
}
fun onNewChainCommandClick() {
router.newChain(
sampleScreen(screenNumber + 1),
sampleScreen(screenNumber + 2),
sampleScreen(screenNumber + 3)
Sample(screenNumber + 1),
Sample(screenNumber + 2),
Sample(screenNumber + 3)
)
}
fun onNewRootChainCommandClick() {
router.newRootChain(
Sample(screenNumber + 1),
Sample(screenNumber + 2),
Sample(screenNumber + 3)
)
}
@@ -49,20 +57,20 @@ class SamplePresenter(
}
fun onNewRootCommandClick() {
router.newRootScreen(sampleScreen(screenNumber + 1))
router.newRootScreen(Sample(screenNumber + 1))
}
fun onForwardWithDelayCommandClick() {
future?.cancel(true)
future = executorService.schedule({ //WARNING! Navigation must be only in UI thread.
Handler(Looper.getMainLooper()).post {
router.navigateTo(sampleScreen(screenNumber + 1))
router.navigateTo(Sample(screenNumber + 1))
}
}, 5, TimeUnit.SECONDS)
}
fun onBackToCommandClick() {
router.backTo(sampleScreen(3))
router.backTo(Sample(3))
}
init {
@@ -1,9 +1,10 @@
package com.github.terrakok.cicerone.sample.mvp.start
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.bottomNavigationScreen
import com.github.terrakok.cicerone.sample.Screens.mainScreen
import com.github.terrakok.cicerone.sample.Screens.profileScreen
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
/**
@@ -12,15 +13,19 @@ import moxy.MvpPresenter
class StartActivityPresenter(private val router: Router) : MvpPresenter<StartActivityView>() {
fun onOrdinaryPressed() {
router.navigateTo(mainScreen())
router.navigateTo(Main())
}
fun onMultiPressed() {
router.navigateTo(bottomNavigationScreen())
router.navigateTo(BottomNavigation())
}
fun onResultWithAnimationPressed() {
router.navigateTo(profileScreen())
router.navigateTo(Profile())
}
fun onGraphPressed() {
router.navigateTo(Graph())
}
fun onBackPressed() {
@@ -12,7 +12,7 @@ import com.github.terrakok.cicerone.Replace
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.profileInfoScreen
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
@@ -32,7 +32,7 @@ class ProfileActivity : AppCompatActivity() {
setContentView(R.layout.activity_container)
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(profileInfoScreen())))
navigator.applyCommands(arrayOf<Command>(Replace(ProfileInfo())))
}
}
@@ -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
@@ -8,7 +8,6 @@ import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentSelectPhotoBinding
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoPresenter
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
@@ -29,9 +28,6 @@ class SelectPhotoFragment : MvpAppCompatFragment(), SelectPhotoView, BackButtonL
@Inject
lateinit var router: Router
@Inject
lateinit var photoSelection: PhotoSelection
@InjectPresenter
lateinit var presenter: SelectPhotoPresenter
@@ -45,8 +41,11 @@ class SelectPhotoFragment : MvpAppCompatFragment(), SelectPhotoView, BackButtonL
private val animationDestionationId: Int
get() = arguments!!.getInt(ARG_ANIM_DESTINATION)
private val resultKey: String
get() = arguments!!.getString(EXTRA_RESULT_KEY)!!
@ProvidePresenter
fun providePresenter() = SelectPhotoPresenter(photoSelection, router)
fun providePresenter() = SelectPhotoPresenter(resultKey, router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
@@ -100,5 +99,14 @@ class SelectPhotoFragment : MvpAppCompatFragment(), SelectPhotoView, BackButtonL
companion object {
private const val ARG_ANIM_DESTINATION = "arg_anim_dest"
private const val EXTRA_RESULT_KEY = "extra_result_key"
fun getNewInstance(resultKey: String): SelectPhotoFragment {
return SelectPhotoFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_RESULT_KEY, resultKey)
}
}
}
}
}
@@ -8,7 +8,6 @@ import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfilePresenter
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfileView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
@@ -31,11 +30,8 @@ class ProfileFragment : MvpAppCompatFragment(), ProfileView, BackButtonListener
@InjectPresenter
lateinit var presenter: ProfilePresenter
@Inject
lateinit var photoSelection: PhotoSelection
@ProvidePresenter
fun providePresenter() = ProfilePresenter(photoSelection, router)
fun providePresenter() = ProfilePresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
@@ -8,7 +8,7 @@ import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.tabScreen
import com.github.terrakok.cicerone.sample.Screens.Tab
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
@@ -87,7 +87,7 @@ class BottomNavigationActivity : MvpAppCompatActivity(), BottomNavigationView, R
if (newFragment == null) {
transaction.add(
R.id.ab_container,
tabScreen(tab).createFragment.invoke(fm.fragmentFactory), tab
Tab(tab).createFragment(fm.fragmentFactory), tab
)
}
if (currentFragment != null) {
@@ -11,7 +11,7 @@ import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.forwardScreen
import com.github.terrakok.cicerone.sample.Screens.Forward
import com.github.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
@@ -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
@@ -47,7 +47,7 @@ class TabContainerFragment : Fragment(), RouterProvider, BackButtonListener {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (childFragmentManager.findFragmentById(R.id.ftc_container) == null) {
cicerone.router.replaceScreen(forwardScreen(containerName, 0))
cicerone.router.replaceScreen(Forward(containerName, 0))
}
}
@@ -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)
}
}
}
}
@@ -11,7 +11,7 @@ import com.github.terrakok.cicerone.Replace
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.sampleScreen
import com.github.terrakok.cicerone.sample.Screens.Sample
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatActivity
import java.lang.ref.WeakReference
@@ -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)
@@ -48,7 +48,7 @@ class MainActivity : MvpAppCompatActivity(), ChainHolder {
screensSchemeTV = findViewById<View>(R.id.screens_scheme) as TextView
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(sampleScreen(1))))
navigator.applyCommands(arrayOf<Command>(Replace(Sample(1))))
} else {
printScreensScheme()
}
@@ -54,6 +54,7 @@ class SampleFragment : BaseFragment(), SampleView, BackButtonListener {
binding.forwardCommand.setOnClickListener { presenter.onForwardCommandClick() }
binding.replaceCommand.setOnClickListener { presenter.onReplaceCommandClick() }
binding.newChainCommand.setOnClickListener { presenter.onNewChainCommandClick() }
binding.newRootChainCommand.setOnClickListener { presenter.onNewRootChainCommandClick() }
binding.newRootCommand.setOnClickListener { presenter.onNewRootCommandClick() }
binding.forwardDelayCommand.setOnClickListener { presenter.onForwardWithDelayCommandClick() }
binding.backToCommand.setOnClickListener { presenter.onBackToCommandClick() }
@@ -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>
@@ -66,6 +66,18 @@
android:text="New chain"
android:textSize="28sp"/>
<TextView
android:id="@+id/new_root_chain_command"
android:layout_width="0dp"
android:layout_columnWeight="1"
android:layout_margin="4dp"
android:layout_height="0dp"
android:layout_rowWeight="1"
android:background="@color/purple"
android:gravity="center"
android:text="New root chain"
android:textSize="28sp"/>
<TextView
android:id="@+id/new_root_command"
android:layout_width="0dp"
@@ -109,6 +121,7 @@
android:layout_margin="4dp"
android:layout_height="0dp"
android:layout_rowWeight="1"
android:layout_columnSpan="2"
android:background="@color/greensea"
android:gravity="center"
android:text="Finish chain"
+1
View File
@@ -11,4 +11,5 @@
<color name="greensea">#16a085</color>
<color name="nephritis">#27ae60</color>
<color name="belizehole">#2980b9</color>
<color name="purple">#9329B9</color>
</resources>
+1
View File
@@ -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>