5 Commits

Author SHA1 Message Date
terrakok 8b6583cc8d Bump version to "6.1" 2020-10-21 17:11:59 +03:00
terrakok d8ee40c0ea Add screenKey to fragment transaction tag. 2020-10-21 16:57:26 +03:00
terrakok b88a6992d8 Refactor sample screens names 2020-10-21 16:36:01 +03:00
terrakok e51503fd5c Rename ResultBus to ResultWire 2020-10-21 16:18:57 +03:00
terrakok 6e24e9973f Add simple result messaging. 2020-10-20 22:01:04 +03:00
37 changed files with 261 additions and 520 deletions
+1 -4
View File
@@ -10,7 +10,4 @@ local.properties
**/*.iml
# Mac OS
.DS_Store
# GPG keys
*.gpg
.DS_Store
+46 -107
View File
@@ -1,57 +1,33 @@
# Cicerone
[![Maven Central](https://img.shields.io/maven-central/v/com.github.terrakok/cicerone)](https://repo1.maven.org/maven2/com/github/terrakok/cicerone/)
[![jCenter](https://api.bintray.com/packages/terrakok/terramaven/cicerone/images/download.svg)](https://bintray.com/terrakok/terramaven/cicerone/_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)
<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>
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
Cicerone (a guide who gives information about antiquities and places of interest to sightseers) is a lightweight library that makes the navigation in an Android app easy.
Cicerone (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 (very lightweight)
+ Short navigation calls (no builders)
+ Static typed checks for screen parameters!
+ Lifecycle-safe!
+ Functionality is simple to extend
+ Suitable for Unit Testing
+ is not tied to Fragments
+ not a framework
+ short navigation calls (no builders)
+ lifecycle-safe!
+ functionality is simple to extend
+ suitable for Unit Testing
## Additional features
+ Opening several screens inside single call (for example: deeplink)
+ Provides `FragmentFactory` if it needed
+ opening several screens inside single call (for example: deeplink)
+ provides `FragmentFactory` if it needed
+ `add` or `replace` strategy for opening next screen (see `router.navigateTo` last parameter)
+ Implementation of parallel navigation (Instagram like)
+ Predefined navigator ready for Single-Activity apps
+ Predefined navigator ready for setup transition animation
+ implementation of parallel navigation (Instagram like)
+ predefined navigator ready for Single-Activity apps
+ predefined navigator ready for setup transition animation
## How to add Cicerone to your application
## How to add
Add the dependency in your build.gradle:
```kotlin
dependencies {
@@ -79,10 +55,10 @@ class App : Application() {
}
```
## How does it work?
<img src="https://github.com/terrakok/Cicerone/blob/master/media/CiceroneDiagram.png" alt="CiceroneDiagram.png" width="800"/>
## How it works?
<img src="https://github.com/terrakok/Cicerone/raw/develop/media/CiceroneDiagram.png" alt="drawing" width="800"/>
The `Presenter` calls the navigation method of `Router`.
Presenter calls navigation method of Router.
```kotlin
class SamplePresenter(
@@ -99,11 +75,11 @@ class SamplePresenter(
}
```
`Router` converts the navigation call to the set of commands and sends them to `CommandBuffer`.
Router converts the navigation call to the set of Commands and sends them to CommandBuffer.
`CommandBuffer` checks whether there are the `_"active"_ Navigator`:
- If yes, it passes the commands to the Navigator. `Navigator` will process them to achive the desired transition.
- If no, then `CommandBuffer` saves the commands in a queue, and will apply them as soon as a new `_"active"_ Navigator` will appear.
CommandBuffer checks whether there are _"active"_ Navigator:
- If yes, it passes the commands to the Navigator. Navigator will process them to achive the desired transition.
- If no, then CommandBuffer saves the commands in a queue, and will apply them as soon as new _"active"_ Navigator will appear.
```kotlin
fun executeCommands(commands: Array<out Command>) {
@@ -111,10 +87,10 @@ fun executeCommands(commands: Array<out Command>) {
}
```
`Navigator` processes the navigation commands. Usually it is an anonymous class inside `Activity`.
`Activity` provides `Navigator` to the `CommandBuffer` in `_onResume_` and removes it in `_onPause_`.
Navigator processes the navigation commands. Usually it is an anonymous class inside the Activity.
Activity provides Navigator to the CommandBuffer in _onResume_ and removes it in _onPause_.
**Attention**: Use `_onResumeFragments()_` with `FragmentActivity` ([more info](https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()))
**Attention**: Use _onResumeFragments()_ with FragmentActivity ([more info](https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()))
```kotlin
private val navigator = AppNavigator(this, R.id.container)
@@ -131,15 +107,15 @@ override fun onPause() {
```
## Navigation commands
These commands will fulfill the needs of the most applications. But if you need something special - just add it!
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/master/media/forward_img.png)
![](https://github.com/terrakok/Cicerone/raw/develop/media/forward_img.png)
+ Back - Rolls back the last transition
![](https://github.com/terrakok/Cicerone/raw/master/media/back_img.png)
![](https://github.com/terrakok/Cicerone/raw/develop/media/back_img.png)
+ BackTo - Rolls back to the needed screen in the screens chain
![](https://github.com/terrakok/Cicerone/raw/master/media/backTo_img.png)
![](https://github.com/terrakok/Cicerone/raw/develop/media/backTo_img.png)
+ Replace - Replaces the current screen
![](https://github.com/terrakok/Cicerone/raw/master/media/replace_img.png)
![](https://github.com/terrakok/Cicerone/raw/develop/media/replace_img.png)
## Predefined navigator
The library provides predefined navigator for _Fragments_ and _Activity_.
@@ -148,16 +124,16 @@ To use, just provide it with the container and _FragmentManager_.
private val navigator = AppNavigator(this, R.id.container)
```
A custom navigator can be useful sometimes:
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>) {
@@ -168,68 +144,31 @@ private val navigator = object : AppNavigator(this, R.id.container) {
```
## Screens
Describe your screens as you like e.g. create a Kotlin `object` with all application screens:
Describe your screens as you like e.g. create Kotlin `object` with all application screens:
```kotlin
object Screens {
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)) }
val Main = FragmentScreen("MainFragment") { MainFragment() }
val AddressSearch = FragmentScreen("AddressSearchFragment") { AddressSearchFragment() }
fun Profile(userId: Long) = FragmentScreen("ProfileFragment") { ProfileFragment(userId) }
}
```
Additional you can use `FragmentFactory` for creating your screens:
```kotlin
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()
}
val SomeScreen = FragmentScreen("SomeScreenId") { factory: FragmentFactory -> ... }
```
## Sample
To see how to add, initialize and use the library and predefined navigators see the **sample project**
(thank you [@Javernaut](https://github.com/Javernaut) for support new library version and migrate sample project to Kotlin!)
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)
For more complex use case check out the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
## Applications that use Cicerone
<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.kms.me"><img src="https://play-lh.googleusercontent.com/IBzu0tlHd_amw2HbjBLOZiCfK-0tn0CnwkMdOd1toP23rdHUV-i7L2ViNKgIg687=s360" width="64" /> Kaspersky Internet Security</a><br>
<a href="https://play.google.com/store/apps/details?id=com.deliveryclub"><img src="https://play-lh.googleusercontent.com/m6-gFunvj7aQD5fdv8EdJZBN5M4REIobTaPZPYS0K5Td7CNYnazN7fOKiPwwaY3hJw=s360" width="64" /> Delivery Club Доставка еды и продуктов</a><br>
<a href="https://play.google.com/store/apps/details?id=ru.hh.android"><img src="https://play-lh.googleusercontent.com/YpAV7Q-ZJhI5tzFk_wEX-7-x2BydtnCtFTVUrmq0zAO6jLCLA4nNcfem3p_Pyowg9w=s360" width="64" /> Поиск работы на hh. Вакансии рядом с домом</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>
![](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)
## Participants
+ Idea and code - Konstantin Tskhovrebov (@terrakok)
+ Architecture advice, documentation and publication - Vasili Chyrvon (@Jeevuz)
+ idea and code - Konstantin Tskhovrebov (@terrakok)
+ architecture advice, documentation and publication - Vasili Chyrvon (@Jeevuz)
## License
```
+7 -4
View File
@@ -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
View File
@@ -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
+14
View File
@@ -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
}
}
}
+24
View File
@@ -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
View File
@@ -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.1'
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
}
}
}
-94
View File
@@ -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")
@@ -10,16 +10,12 @@ abstract class BaseRouter {
private val resultWire = ResultWire()
/**
* Sets data listener with given key
* and returns [ResultListenerHandler] for availability to dispose subscription.
* Sets data listener with given key.
*
* After first call listener will be removed.
*/
fun setResultListener(
key: String,
listener: ResultListener
): ResultListenerHandler {
return resultWire.setResultListener(key, listener)
fun setResultListener(key: String, listener: (data: Any) -> Unit) {
resultWire.setResultListener(key, listener)
}
/**
@@ -36,5 +32,6 @@ abstract class BaseRouter {
*/
protected fun executeCommands(vararg commands: Command) {
commandBuffer.executeCommands(commands)
resultWire.flush()
}
}
@@ -17,14 +17,12 @@ class Cicerone<T : BaseRouter> private constructor(val router: T) {
/**
* Creates the Cicerone instance with the default [Router]
*/
@JvmStatic
fun create() = create(Router())
/**
* Creates the Cicerone instance with the custom router.
* @param customRouter the custom router extending [BaseRouter]
*/
@JvmStatic
fun <T : BaseRouter> create(customRouter: T) = Cicerone(customRouter)
}
}
@@ -1,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)
}
}
@@ -1,30 +1,23 @@
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()
}
import java.lang.ref.WeakReference
internal class ResultWire {
private val listeners = mutableMapOf<String, ResultListener>()
private val listeners = mutableMapOf<String, WeakReference<(Any) -> Unit>>()
fun setResultListener(key: String, listener: ResultListener): ResultListenerHandler {
listeners[key] = listener
return ResultListenerHandler {
listeners.remove(key)
}
fun setResultListener(key: String, listener: (data: Any) -> Unit) {
listeners[key] = WeakReference(listener)
}
fun sendResult(key: String, data: Any) {
listeners.remove(key)?.onResult(data)
listeners.remove(key)?.get()?.let { listener ->
listener(data)
}
}
fun flush() {
listeners.entries
.filter { it.value.get() == null }
.forEach { listeners.remove(it.key) }
}
}
@@ -12,9 +12,10 @@ 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))
fun navigateTo(screen: Screen, clearContainer: Boolean = true) {
executeCommands(Forward(screen, clearContainer))
}
/**
@@ -55,9 +56,10 @@ 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) }
fun newChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
executeCommands(*commands.toTypedArray())
}
@@ -65,14 +67,10 @@ 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) {
val commands = screens.mapIndexed { index, screen ->
if (index == 0)
Replace(screen)
else
Forward(screen)
}
fun newRootChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
executeCommands(BackTo(null), *commands.toTypedArray())
}
@@ -1,8 +1,8 @@
package com.github.terrakok.cicerone
/**
* Screen is interface for description application screen.
* Screen is class for description application screen.
*/
interface Screen {
val screenKey: String get() = this::class.java.name
abstract class Screen {
open val screenKey: String = this::class.qualifiedName!!
}
@@ -1,9 +1,10 @@
package com.github.terrakok.cicerone.androidx
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.
@@ -12,14 +13,14 @@ import com.github.terrakok.cicerone.*
*
* Recommendation: most useful for Single-Activity application.
*/
open class AppNavigator @JvmOverloads constructor(
open class AppNavigator constructor(
protected val activity: FragmentActivity,
protected val containerId: Int,
protected val fragmentManager: FragmentManager = activity.supportFragmentManager,
protected val fragmentFactory: FragmentFactory = fragmentManager.fragmentFactory
protected val fragmentFactory: FragmentFactory = FragmentFactory()
) : Navigator {
protected val localStackCopy = mutableListOf<String>()
protected val localStackCopy = mutableListOf<TransactionInfo>()
override fun applyCommands(commands: Array<out Command>) {
fragmentManager.executePendingTransactions()
@@ -39,7 +40,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 +60,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 +80,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 +104,24 @@ 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 +134,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 +160,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
}
@@ -169,9 +170,9 @@ open class AppNavigator @JvmOverloads constructor(
private fun checkAndStartActivity(screen: ActivityScreen) {
// Check if we can start activity
val activityIntent = screen.createIntent(activity)
try {
if (activityIntent.resolveActivity(activity.packageManager) != null) {
activity.startActivity(activityIntent, screen.startActivityOptions)
} catch (e: ActivityNotFoundException) {
} else {
unexistingActivity(screen, activityIntent)
}
}
@@ -195,7 +196,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,16 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import com.github.terrakok.cicerone.Screen
fun interface Creator<A, R> {
fun create(argument: A): R
}
sealed class AppScreen : Screen()
interface FragmentScreen : Screen {
val clearContainer: Boolean get() = true
fun createFragment(factory: FragmentFactory): Fragment
open class FragmentScreen(
override val screenKey: String,
val createFragment: (FragmentFactory) -> Fragment
) : AppScreen()
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)
}
}
}
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(
override val screenKey: String,
val createIntent: (context: Context) -> Intent
) : AppScreen() {
open val startActivityOptions: Bundle? = null
}
@@ -0,0 +1,20 @@
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 {
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.
@@ -36,10 +36,6 @@ public class FragmentManager {
throw new RuntimeException("Stub!");
}
public FragmentFactory getFragmentFactory() {
throw new RuntimeException("Stub!");
}
public interface BackStackEntry {
int getId();
@@ -6,10 +6,6 @@ package androidx.fragment.app;
*/
public class FragmentTransaction {
public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
throw new RuntimeException("Stub!");
}
+6 -6
View File
@@ -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"
}
@@ -12,7 +12,6 @@ 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.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
/**
@@ -25,43 +24,39 @@ object Screens {
SampleFragment.getNewInstance(number)
}
fun Start() = ActivityScreen {
fun Start() = ActivityScreen("Start") {
Intent(it, StartActivity::class.java)
}
fun Main() = ActivityScreen {
fun Main() = ActivityScreen("Main") {
Intent(it, MainActivity::class.java)
}
fun BottomNavigation() = ActivityScreen {
fun BottomNavigation() = ActivityScreen("BottomNavigation") {
Intent(it, BottomNavigationActivity::class.java)
}
fun Tab(tabName: String) = FragmentScreen {
fun Tab(tabName: String) = FragmentScreen("Tab") {
TabContainerFragment.getNewInstance(tabName)
}
fun Forward(containerName: String, number: Int) = FragmentScreen {
fun Forward(containerName: String, number: Int) = FragmentScreen("Forward") {
ForwardFragment.getNewInstance(containerName, number)
}
fun Github() = ActivityScreen {
fun Github() = ActivityScreen("Github") {
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"))
}
fun Profile() = ActivityScreen {
fun Profile() = ActivityScreen("Profile") {
Intent(it, ProfileActivity::class.java)
}
fun ProfileInfo() = FragmentScreen {
fun ProfileInfo() = FragmentScreen("ProfileInfo") {
ProfileFragment()
}
fun SelectPhoto(resultKey: String) = FragmentScreen {
fun SelectPhoto(resultKey: String) = FragmentScreen("SelectPhoto") {
SelectPhotoFragment.getNewInstance(resultKey)
}
fun SemiTransparent() = FragmentScreen(clearContainer = false) {
SemiTransparentFragment()
}
}
@@ -9,7 +9,6 @@ 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.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
@@ -38,6 +37,4 @@ interface AppComponent {
fun inject(fragment: SelectPhotoFragment)
fun inject(activity: ProfileActivity)
fun inject(fragment: SemiTransparentFragment)
}
@@ -1,6 +1,5 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import com.github.terrakok.cicerone.ResultListenerHandler
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.Screens.SelectPhoto
@@ -14,7 +13,6 @@ import moxy.MvpPresenter
class ProfilePresenter(
private val router: Router
) : MvpPresenter<ProfileView>() {
private var resultListenerHandler: ResultListenerHandler? = null
companion object {
private const val RESULT_KEY = "photo_result"
@@ -27,17 +25,12 @@ class ProfilePresenter(
}
fun onPhotoClicked() {
resultListenerHandler = router.setResultListener(RESULT_KEY) { data ->
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()
}
@@ -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
@@ -45,22 +44,10 @@ class SamplePresenter(
)
}
fun onNewRootChainCommandClick() {
router.newRootChain(
Sample(screenNumber + 1),
Sample(screenNumber + 2),
Sample(screenNumber + 3)
)
}
fun onFinishChainCommandClick() {
router.finishChain()
}
fun onForwardNccCommandClick() {
router.navigateTo(Screens.SemiTransparent())
}
fun onNewRootCommandClick() {
router.newRootScreen(Sample(screenNumber + 1))
}
@@ -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
@@ -47,19 +46,15 @@ class ProfileActivity : AppCompatActivity() {
super.onPause()
}
private val navigator: Navigator = object : AppNavigator(this, R.id.container) {
override fun setupFragmentTransaction(
screen: FragmentScreen,
fragmentTransaction: FragmentTransaction,
currentFragment: Fragment?,
nextFragment: Fragment
) {
private val navigator: Navigator = object : AppNavigator(this, R.id.container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
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
)
}
}
@@ -87,7 +87,7 @@ class BottomNavigationActivity : MvpAppCompatActivity(), BottomNavigationView, R
if (newFragment == null) {
transaction.add(
R.id.ab_container,
Tab(tab).createFragment(fm.fragmentFactory), tab
Tab(tab).createFragment.invoke(fm.fragmentFactory), tab
)
}
if (currentFragment != null) {
@@ -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)
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager, childFragmentManager.fragmentFactory)
}
@Inject
@@ -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
}
@@ -31,7 +31,7 @@ class MainActivity : MvpAppCompatActivity(), ChainHolder {
@Inject
lateinit var navigatorHolder: NavigatorHolder
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container) {
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
override fun applyCommands(commands: Array<out Command>) {
super.applyCommands(commands)
@@ -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?) {
@@ -54,12 +54,10 @@ 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() }
binding.finishChainCommand.setOnClickListener { presenter.onFinishChainCommandClick() }
binding.forwardNccCommand.setOnClickListener { presenter.onForwardNccCommandClick() }
}
override fun setTitle(title: String) {
@@ -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
}
}
@@ -29,7 +29,7 @@ class StartActivity : MvpAppCompatActivity(), StartActivityView {
@InjectPresenter
lateinit var presenter: StartActivityPresenter
private val navigator: Navigator = AppNavigator(this, -1)
private val navigator: Navigator = AppNavigator(this, -1, supportFragmentManager, supportFragmentManager.fragmentFactory)
@ProvidePresenter
fun createStartActivityPresenter() = StartActivityPresenter(router)
@@ -66,18 +66,6 @@
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"
@@ -125,17 +113,6 @@
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>
-3
View File
@@ -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>
@@ -13,5 +11,4 @@
<color name="greensea">#16a085</color>
<color name="nephritis">#27ae60</color>
<color name="belizehole">#2980b9</color>
<color name="purple">#9329B9</color>
</resources>