Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b2654ee2b | |||
| 8c8521d2c5 | |||
| 14b94927e4 | |||
| 1e40fb29bb | |||
| dbc8c3363f |
+1
-4
@@ -10,7 +10,4 @@ local.properties
|
||||
**/*.iml
|
||||
|
||||
# Mac OS
|
||||
.DS_Store
|
||||
|
||||
# GPG keys
|
||||
*.gpg
|
||||
.DS_Store
|
||||
@@ -1,5 +1,5 @@
|
||||
# Cicerone
|
||||
[](https://repo1.maven.org/maven2/com/github/terrakok/cicerone/)
|
||||
[](https://bintray.com/terrakok/terramaven/cicerone-kotlin/_latestVersion)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
[](https://android-arsenal.com/details/1/4700)
|
||||
@@ -152,12 +152,12 @@ Custom navigator can be useful sometimes:
|
||||
```kotlin
|
||||
private val navigator = object : AppNavigator(this, R.id.container) {
|
||||
override fun setupFragmentTransaction(
|
||||
screen: FragmentScreen,
|
||||
fragmentTransaction: FragmentTransaction,
|
||||
currentFragment: Fragment?,
|
||||
nextFragment: Fragment
|
||||
nextFragment: Fragment?
|
||||
) {
|
||||
//setup your animation
|
||||
super.setupFragmentTransaction(fragmentTransaction, currentFragment, nextFragment)
|
||||
fragmentTransaction.setReorderingAllowed(true)
|
||||
}
|
||||
|
||||
override fun applyCommands(commands: Array<out Command>) {
|
||||
|
||||
+7
-4
@@ -1,21 +1,24 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.21'
|
||||
ext.kotlin_version = '1.4.10'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// For the library uploading to the Bintray
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
|
||||
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
apply plugin: 'com.github.dcendents.android-maven'
|
||||
|
||||
group = publishedGroupId // Maven Group ID for the artifact
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
// This generates POM.xml with proper parameters
|
||||
pom.project {
|
||||
packaging 'jar'
|
||||
groupId publishedGroupId
|
||||
artifactId artifact
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
|
||||
version = libraryVersion // Library version
|
||||
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
|
||||
bintray {
|
||||
// User and ApiKey stored in local.properties
|
||||
user = properties.getProperty("bintrayUser")
|
||||
key = properties.getProperty("bintrayApiKey")
|
||||
configurations = ['archives']
|
||||
// Package info for Bintray
|
||||
pkg {
|
||||
repo = bintrayRepo
|
||||
name = bintrayName
|
||||
licenses = allLicenses
|
||||
vcsUrl = gitUrl
|
||||
publish = true
|
||||
}
|
||||
}
|
||||
|
||||
// Dependency to call only bintrayUpload task
|
||||
bintrayUpload.dependsOn install
|
||||
+42
-4
@@ -1,4 +1,5 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
// This is important even if Android Studio claims it isn't
|
||||
// used. Android can't interpret Java 8 byte code.
|
||||
@@ -12,12 +13,49 @@ dependencies {
|
||||
}
|
||||
|
||||
ext {
|
||||
// This params is for the library uploading to the Bintray
|
||||
bintrayRepo = 'terramaven'
|
||||
bintrayName = 'cicerone-kotlin'
|
||||
publishedGroupId = 'com.github.terrakok'
|
||||
artifact = 'cicerone'
|
||||
libraryVersion = '7.1'
|
||||
libraryVersion = '6.6'
|
||||
gitUrl = 'https://github.com/terrakok/Cicerone'
|
||||
allLicenses = ['MIT']
|
||||
}
|
||||
|
||||
project.archivesBaseName = artifact // to fix that project name different from artifact name
|
||||
// Configuration of the library uploading to the Bintray
|
||||
// Note: Call 'bintrayUpload' task (it will execute 'install' task first)
|
||||
// I try to include as little properties as possible in these files
|
||||
project.archivesBaseName = 'cicerone' // to fix that project name different from artifact name
|
||||
apply from: 'androidmaven.gradle'
|
||||
apply from: 'bintray.gradle'
|
||||
|
||||
//apply from: 'publish-mavencentral.gradle'
|
||||
//for publication use './gradlew publishReleasePublicationToSonatypeRepository'
|
||||
// Tasks for sources and javadocs jars
|
||||
task sourcesJar(type: Jar) {
|
||||
from sourceSets.main.kotlin.srcDirs
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar
|
||||
archives sourcesJar
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
groupId = publishedGroupId
|
||||
artifactId = artifact
|
||||
version = libraryVersion
|
||||
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
properties.each { name, value -> ext[name] = value }
|
||||
|
||||
group = publishedGroupId
|
||||
version = libraryVersion
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
from sourceSets.main.kotlin.srcDirs
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar
|
||||
archives sourcesJar
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
groupId = publishedGroupId
|
||||
artifactId = artifact
|
||||
version = libraryVersion
|
||||
|
||||
artifact("$buildDir/libs/${artifact}-${version}.jar")
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
|
||||
pom {
|
||||
name = artifact
|
||||
description = 'Cicerone is a lightweight library that makes the navigation in an Android app easy.'
|
||||
url = 'https://github.com/terrakok/Cicerone'
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name = "MIT"
|
||||
url = "https://opensource.org/licenses/MIT"
|
||||
}
|
||||
}
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id = 'terrakok'
|
||||
name = 'Konstantin Tskhovrebov'
|
||||
email = 'terrakok@gmail.com'
|
||||
}
|
||||
}
|
||||
|
||||
scm {
|
||||
developerConnection = 'scm:git:ssh://github.com/terrakok/Cicerone.git'
|
||||
url = 'https://github.com/terrakok/Cicerone'
|
||||
}
|
||||
|
||||
// A slightly hacky fix so that your POM will include any transitive dependencies
|
||||
// that your library builds upon
|
||||
withXml {
|
||||
def dependenciesNode = asNode().appendNode('dependencies')
|
||||
|
||||
project.configurations.implementation.allDependencies.each {
|
||||
def dependencyNode = dependenciesNode.appendNode('dependency')
|
||||
dependencyNode.appendNode('groupId', it.group)
|
||||
dependencyNode.appendNode('artifactId', it.name)
|
||||
dependencyNode.appendNode('version', it.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
|
||||
credentials {
|
||||
username = ossrhUsername
|
||||
password = ossrhPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
sign publishing.publications
|
||||
}
|
||||
|
||||
tasks.getByName("publishReleasePublicationToSonatypeRepository").dependsOn("assemble", "sourcesJar", "javadocJar")
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.github.terrakok.cicerone
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
||||
/**
|
||||
* Passes navigation command to an active [Navigator]
|
||||
* or stores it in the pending commands queue to pass it later.
|
||||
@@ -10,7 +7,6 @@ import android.os.Looper
|
||||
internal class CommandBuffer : NavigatorHolder {
|
||||
private var navigator: Navigator? = null
|
||||
private val pendingCommands = mutableListOf<Array<out Command>>()
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
override fun setNavigator(navigator: Navigator) {
|
||||
this.navigator = navigator
|
||||
@@ -28,8 +24,6 @@ internal class CommandBuffer : NavigatorHolder {
|
||||
* @param commands navigation command array
|
||||
*/
|
||||
fun executeCommands(commands: Array<out Command>) {
|
||||
mainHandler.post {
|
||||
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
|
||||
}
|
||||
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,11 @@ open class Router : BaseRouter() {
|
||||
* Open new screen and add it to the screens chain.
|
||||
*
|
||||
* @param screen screen
|
||||
* @param clearContainer if FALSE then new screen shows over previous
|
||||
*/
|
||||
fun navigateTo(screen: Screen) {
|
||||
executeCommands(Forward(screen))
|
||||
@JvmOverloads
|
||||
fun navigateTo(screen: Screen, clearContainer: Boolean = true) {
|
||||
executeCommands(Forward(screen, clearContainer))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,9 +57,11 @@ open class Router : BaseRouter() {
|
||||
* Opens several screens inside single transaction.
|
||||
*
|
||||
* @param screens
|
||||
* @param showOnlyTopScreenView if FALSE then all screen views show together
|
||||
*/
|
||||
fun newChain(vararg screens: Screen) {
|
||||
val commands = screens.map { Forward(it) }
|
||||
@JvmOverloads
|
||||
fun newChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
|
||||
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
|
||||
executeCommands(*commands.toTypedArray())
|
||||
}
|
||||
|
||||
@@ -65,13 +69,15 @@ open class Router : BaseRouter() {
|
||||
* Clear current stack and open several screens inside single transaction.
|
||||
*
|
||||
* @param screens
|
||||
* @param showOnlyTopScreenView if FALSE then all screen views show together
|
||||
*/
|
||||
fun newRootChain(vararg screens: Screen) {
|
||||
@JvmOverloads
|
||||
fun newRootChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
|
||||
val commands = screens.mapIndexed { index, screen ->
|
||||
if (index == 0)
|
||||
Replace(screen)
|
||||
else
|
||||
Forward(screen)
|
||||
Forward(screen, showOnlyTopScreenView)
|
||||
}
|
||||
executeCommands(BackTo(null), *commands.toTypedArray())
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.*
|
||||
import com.github.terrakok.cicerone.*
|
||||
import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.ADD
|
||||
import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.REPLACE
|
||||
|
||||
/**
|
||||
* Navigator implementation for launch fragments and activities.
|
||||
@@ -19,7 +21,7 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
protected val fragmentFactory: FragmentFactory = fragmentManager.fragmentFactory
|
||||
) : Navigator {
|
||||
|
||||
protected val localStackCopy = mutableListOf<String>()
|
||||
protected val localStackCopy = mutableListOf<TransactionInfo>()
|
||||
|
||||
override fun applyCommands(commands: Array<out Command>) {
|
||||
fragmentManager.executePendingTransactions()
|
||||
@@ -39,7 +41,8 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
private fun copyStackToLocal() {
|
||||
localStackCopy.clear()
|
||||
for (i in 0 until fragmentManager.backStackEntryCount) {
|
||||
localStackCopy.add(fragmentManager.getBackStackEntryAt(i).name)
|
||||
val str = fragmentManager.getBackStackEntryAt(i).name
|
||||
localStackCopy.add(TransactionInfo.fromString(str))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,18 +61,19 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
protected open fun forward(command: Forward) {
|
||||
when (val screen = command.screen) {
|
||||
when (val screen = command.screen as AppScreen) {
|
||||
is ActivityScreen -> {
|
||||
checkAndStartActivity(screen)
|
||||
}
|
||||
is FragmentScreen -> {
|
||||
commitNewFragmentScreen(screen, true)
|
||||
val type = if (command.clearContainer) REPLACE else ADD
|
||||
commitNewFragmentScreen(screen, type, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun replace(command: Replace) {
|
||||
when (val screen = command.screen) {
|
||||
when (val screen = command.screen as AppScreen) {
|
||||
is ActivityScreen -> {
|
||||
checkAndStartActivity(screen)
|
||||
activity.finish()
|
||||
@@ -77,10 +81,10 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
is FragmentScreen -> {
|
||||
if (localStackCopy.isNotEmpty()) {
|
||||
fragmentManager.popBackStack()
|
||||
localStackCopy.removeAt(localStackCopy.lastIndex)
|
||||
commitNewFragmentScreen(screen, true)
|
||||
val removed = localStackCopy.removeAt(localStackCopy.lastIndex)
|
||||
commitNewFragmentScreen(screen, removed.type, true)
|
||||
} else {
|
||||
commitNewFragmentScreen(screen, false)
|
||||
commitNewFragmentScreen(screen, REPLACE, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,25 +105,25 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
|
||||
protected open fun commitNewFragmentScreen(
|
||||
screen: FragmentScreen,
|
||||
type: TransactionInfo.Type,
|
||||
addToBackStack: Boolean
|
||||
) {
|
||||
val fragment = screen.createFragment(fragmentFactory)
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
transaction.setReorderingAllowed(true)
|
||||
setupFragmentTransaction(
|
||||
screen,
|
||||
transaction,
|
||||
fragmentManager.findFragmentById(containerId),
|
||||
fragment
|
||||
)
|
||||
if (screen.clearContainer) {
|
||||
transaction.replace(containerId, fragment, screen.screenKey)
|
||||
} else {
|
||||
transaction.add(containerId, fragment, screen.screenKey)
|
||||
when (type) {
|
||||
ADD -> transaction.add(containerId, fragment, screen.screenKey)
|
||||
REPLACE -> transaction.replace(containerId, fragment, screen.screenKey)
|
||||
}
|
||||
if (addToBackStack) {
|
||||
transaction.addToBackStack(screen.screenKey)
|
||||
localStackCopy.add(screen.screenKey)
|
||||
val transactionInfo = TransactionInfo(screen.screenKey, type)
|
||||
transaction.addToBackStack(transactionInfo.toString())
|
||||
localStackCopy.add(transactionInfo)
|
||||
}
|
||||
transaction.commit()
|
||||
}
|
||||
@@ -132,13 +136,13 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
backToRoot()
|
||||
} else {
|
||||
val screenKey = command.screen.screenKey
|
||||
val index = localStackCopy.indexOfFirst { it == screenKey }
|
||||
val index = localStackCopy.indexOfFirst { it.screenKey == screenKey }
|
||||
if (index != -1) {
|
||||
val forRemove = localStackCopy.subList(index, localStackCopy.size)
|
||||
fragmentManager.popBackStack(forRemove.first().toString(), 0)
|
||||
forRemove.clear()
|
||||
} else {
|
||||
backToUnexisting(command.screen)
|
||||
backToUnexisting(command.screen as AppScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,10 +162,9 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
* @param nextFragment next screen fragment
|
||||
*/
|
||||
protected open fun setupFragmentTransaction(
|
||||
screen: FragmentScreen,
|
||||
fragmentTransaction: FragmentTransaction,
|
||||
currentFragment: Fragment?,
|
||||
nextFragment: Fragment
|
||||
nextFragment: Fragment?
|
||||
) {
|
||||
// Do nothing by default
|
||||
}
|
||||
@@ -195,7 +198,7 @@ open class AppNavigator @JvmOverloads constructor(
|
||||
*
|
||||
* @param screen screen
|
||||
*/
|
||||
protected open fun backToUnexisting(screen: Screen) {
|
||||
protected open fun backToUnexisting(screen: AppScreen) {
|
||||
backToRoot()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,40 +7,25 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentFactory
|
||||
import com.github.terrakok.cicerone.Screen
|
||||
|
||||
sealed class AppScreen : Screen
|
||||
|
||||
fun interface Creator<A, R> {
|
||||
fun create(argument: A): R
|
||||
}
|
||||
|
||||
interface FragmentScreen : Screen {
|
||||
val clearContainer: Boolean get() = true
|
||||
fun createFragment(factory: FragmentFactory): Fragment
|
||||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
key: String? = null,
|
||||
clearContainer: Boolean = true,
|
||||
fragmentCreator: Creator<FragmentFactory, Fragment>
|
||||
) = object : FragmentScreen {
|
||||
override val screenKey = key ?: fragmentCreator::class.java.name
|
||||
override val clearContainer = clearContainer
|
||||
override fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
interface ActivityScreen : Screen {
|
||||
val startActivityOptions: Bundle? get() = null
|
||||
fun createIntent(context: Context): Intent
|
||||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
key: String? = null,
|
||||
startActivityOptions: Bundle? = null,
|
||||
intentCreator: Creator<Context, Intent>
|
||||
) = object : ActivityScreen {
|
||||
override val screenKey = key ?: intentCreator::class.java.name
|
||||
override val startActivityOptions = startActivityOptions
|
||||
override fun createIntent(context: Context) = intentCreator.create(context)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.terrakok.cicerone.androidx
|
||||
|
||||
data class TransactionInfo(
|
||||
val screenKey: String,
|
||||
val type: Type
|
||||
) {
|
||||
enum class Type(val symbol: Char) {
|
||||
ADD('+'),
|
||||
REPLACE('-');
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,10 @@ interface Command
|
||||
/**
|
||||
* Opens new screen.
|
||||
*/
|
||||
data class Forward(val screen: Screen) : Command
|
||||
data class Forward(
|
||||
val screen: Screen,
|
||||
val clearContainer: Boolean
|
||||
) : Command
|
||||
|
||||
/**
|
||||
* Replaces the current screen.
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
+6
-6
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -42,10 +42,10 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
androidXVersion = "1.3.0"
|
||||
materialVersion = "1.4.0"
|
||||
moxyVersion = "2.2.2"
|
||||
daggerVersion = "2.38"
|
||||
androidXVersion = "1.2.0"
|
||||
materialVersion = "1.2.1"
|
||||
moxyVersion = "2.2.0"
|
||||
daggerVersion = "2.29.1"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -69,5 +69,5 @@ dependencies {
|
||||
implementation ('com.ashokvarma.android:bottom-navigation-bar:1.3.0') {
|
||||
exclude group: "com.android.support", module: "design"
|
||||
}
|
||||
implementation "androidx.core:core-ktx:1.6.0"
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
}
|
||||
|
||||
@@ -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,9 +10,9 @@ 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.main.SemiTransparentFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
|
||||
|
||||
/**
|
||||
@@ -61,7 +61,7 @@ object Screens {
|
||||
SelectPhotoFragment.getNewInstance(resultKey)
|
||||
}
|
||||
|
||||
fun SemiTransparent() = FragmentScreen(clearContainer = false) {
|
||||
SemiTransparentFragment()
|
||||
fun Graph() = ActivityScreen {
|
||||
Intent(it, GraphActivity::class.java)
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,11 @@ 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.main.SemiTransparentFragment
|
||||
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
@@ -39,5 +41,9 @@ interface AppComponent {
|
||||
|
||||
fun inject(activity: ProfileActivity)
|
||||
|
||||
fun inject(fragment: SemiTransparentFragment)
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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,6 @@ 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
|
||||
import com.github.terrakok.cicerone.sample.Screens.Sample
|
||||
import moxy.InjectViewState
|
||||
import moxy.MvpPresenter
|
||||
@@ -57,10 +56,6 @@ class SamplePresenter(
|
||||
router.finishChain()
|
||||
}
|
||||
|
||||
fun onForwardNccCommandClick() {
|
||||
router.navigateTo(Screens.SemiTransparent())
|
||||
}
|
||||
|
||||
fun onNewRootCommandClick() {
|
||||
router.newRootScreen(Sample(screenNumber + 1))
|
||||
}
|
||||
|
||||
+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()
|
||||
}
|
||||
|
||||
+6
-11
@@ -10,7 +10,6 @@ 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.androidx.FragmentScreen
|
||||
import com.github.terrakok.cicerone.sample.R
|
||||
import com.github.terrakok.cicerone.sample.SampleApplication
|
||||
import com.github.terrakok.cicerone.sample.Screens.ProfileInfo
|
||||
@@ -48,18 +47,14 @@ class ProfileActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private val navigator: Navigator = object : AppNavigator(this, R.id.container) {
|
||||
override fun setupFragmentTransaction(
|
||||
screen: FragmentScreen,
|
||||
fragmentTransaction: FragmentTransaction,
|
||||
currentFragment: Fragment?,
|
||||
nextFragment: Fragment
|
||||
) {
|
||||
|
||||
override fun setupFragmentTransaction(fragmentTransaction: FragmentTransaction, currentFragment: Fragment?, nextFragment: Fragment?) {
|
||||
if (currentFragment is ProfileFragment
|
||||
&& nextFragment is SelectPhotoFragment) {
|
||||
&& nextFragment is SelectPhotoFragment) {
|
||||
setupSharedElementForProfileToSelectPhoto(
|
||||
currentFragment,
|
||||
nextFragment,
|
||||
fragmentTransaction
|
||||
currentFragment,
|
||||
nextFragment,
|
||||
fragmentTransaction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import androidx.fragment.app.Fragment
|
||||
import moxy.MvpAppCompatFragment
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
abstract class BaseFragment : MvpAppCompatFragment(), ChainScreen {
|
||||
abstract class BaseFragment : MvpAppCompatFragment() {
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
|
||||
@@ -5,9 +5,4 @@ import java.lang.ref.WeakReference
|
||||
|
||||
interface ChainHolder {
|
||||
val chain: MutableList<WeakReference<Fragment>>
|
||||
}
|
||||
|
||||
interface ChainScreen {
|
||||
val name: String
|
||||
val creationTime: Long
|
||||
}
|
||||
@@ -75,10 +75,10 @@ class MainActivity : MvpAppCompatActivity(), ChainHolder {
|
||||
}
|
||||
|
||||
private fun printScreensScheme() {
|
||||
val fragments = ArrayList<ChainScreen>()
|
||||
val fragments = ArrayList<SampleFragment>()
|
||||
for (fragmentReference in chain) {
|
||||
val fragment = fragmentReference.get()
|
||||
if (fragment != null && fragment is ChainScreen) {
|
||||
if (fragment != null && fragment is SampleFragment) {
|
||||
fragments.add(fragment)
|
||||
}
|
||||
}
|
||||
@@ -86,9 +86,9 @@ class MainActivity : MvpAppCompatActivity(), ChainHolder {
|
||||
val t = f1.creationTime - f2.creationTime
|
||||
if (t > 0) 1 else if (t < 0) -1 else 0
|
||||
}
|
||||
val keys = ArrayList<String>()
|
||||
val keys = ArrayList<Int>()
|
||||
for (fragment in fragments) {
|
||||
keys.add(fragment.name)
|
||||
keys.add(fragment.number)
|
||||
}
|
||||
screensSchemeTV.text = "Chain: $keys"
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ class SampleFragment : BaseFragment(), SampleView, BackButtonListener {
|
||||
@ProvidePresenter
|
||||
fun createSamplePresenter() = SamplePresenter(router, arguments!!.getInt(EXTRA_NUMBER))
|
||||
|
||||
override val name: String
|
||||
get() = arguments!!.getInt(EXTRA_NUMBER).toString()
|
||||
override val creationTime: Long
|
||||
val number: Int
|
||||
get() = arguments!!.getInt(EXTRA_NUMBER)
|
||||
val creationTime: Long
|
||||
get() = arguments!!.getLong(EXTRA_TIME, 0L)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -59,7 +59,6 @@ class SampleFragment : BaseFragment(), SampleView, BackButtonListener {
|
||||
binding.forwardDelayCommand.setOnClickListener { presenter.onForwardWithDelayCommandClick() }
|
||||
binding.backToCommand.setOnClickListener { presenter.onBackToCommandClick() }
|
||||
binding.finishChainCommand.setOnClickListener { presenter.onFinishChainCommandClick() }
|
||||
binding.forwardNccCommand.setOnClickListener { presenter.onForwardNccCommandClick() }
|
||||
}
|
||||
|
||||
override fun setTitle(title: String) {
|
||||
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package com.github.terrakok.cicerone.sample.ui.main
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
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.databinding.FragmentSampleBinding
|
||||
import com.github.terrakok.cicerone.sample.databinding.FragmentSemitransparentBinding
|
||||
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
|
||||
import moxy.MvpAppCompatFragment
|
||||
import javax.inject.Inject
|
||||
|
||||
class SemiTransparentFragment : BaseFragment(), BackButtonListener {
|
||||
lateinit var binding: FragmentSemitransparentBinding
|
||||
|
||||
@Inject
|
||||
lateinit var router: Router
|
||||
|
||||
override val name: String = "D"
|
||||
override val creationTime: Long = System.currentTimeMillis()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
SampleApplication.INSTANCE.appComponent.inject(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
binding = FragmentSemitransparentBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
binding.background.setOnClickListener { onBackPressed() }
|
||||
binding.backButton.setOnClickListener { onBackPressed() }
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
router.exit()
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -121,21 +121,11 @@
|
||||
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"
|
||||
android:textSize="28sp"/>
|
||||
<TextView
|
||||
android:id="@+id/forward_ncc_command"
|
||||
android:layout_width="0dp"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_rowWeight="1"
|
||||
android:background="@color/nephritis"
|
||||
android:gravity="center"
|
||||
android:text="Forward not clearing container"
|
||||
android:textSize="28sp"/>
|
||||
|
||||
</GridLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/semitransparent"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="40dp"
|
||||
android:background="@color/accent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="This is not a dialog.\n This is a regular screen."
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:minWidth="100dp"
|
||||
android:text="Back"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -4,8 +4,6 @@
|
||||
<color name="primary_dark">#c0392b</color>
|
||||
<color name="accent">#e67e22</color>
|
||||
|
||||
<color name="semitransparent">#99000000</color>
|
||||
|
||||
|
||||
<color name="alizarin">#e74c3c</color>
|
||||
<color name="carrot">#e67e22</color>
|
||||
|
||||
@@ -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