112 Commits

Author SHA1 Message Date
terrakok df860d2173 Change version name to "6.0" and update bintray setup. 2020-10-19 15:37:29 +03:00
terrakok dbfc6a4f72 Change "Tckhovrebov" -> "Tskhovrebov" everywhere. 2020-10-19 15:34:14 +03:00
terrakok 69e4632058 Change "all" -> "bin" for gradle wrapper. 2020-10-19 15:33:04 +03:00
Konstantin f38b5685a2 Merge pull request #124 from Javernaut/feature/updated_sample
Sample project reviving
2020-10-17 22:55:10 +03:00
Javernaut b81d6bc8e5 Removing redundant explicit dependency for Kotlin stdlib 2020-10-17 21:54:03 +03:00
Javernaut ab8036a13e Migrating to moxy-community 2020-10-17 21:52:27 +03:00
Javernaut a9688db7f5 Migrating the sample applicationId to 'com.github.terrakok.cicerone.sample' 2020-10-16 19:34:59 +03:00
Javernaut e63f6c2339 The Animation sample was migrated to Kotlin 2020-10-16 18:47:15 +03:00
Javernaut 1de7a50b58 Bottom navigation sample was migrated to Kotlin 2020-10-16 18:33:18 +03:00
Javernaut 0efab8731b Main functionality of the sample was migrated to Kotlin 2020-10-16 18:18:44 +03:00
Javernaut 39d043f5d7 StartActivity was migrated to Kotlin 2020-10-16 17:10:02 +03:00
Javernaut f5397994ab Migrating Dagger code to Kotlin 2020-10-16 17:00:14 +03:00
Javernaut af272bc587 Reviving the sample module 2020-10-16 16:22:44 +03:00
Javernaut 9e6c08d5c5 Build tools updating 2020-10-16 14:45:57 +03:00
Konstantin 9c67451d11 Update README.md 2020-10-16 10:58:22 +03:00
Konstantin 86674c0275 Update README.md 2020-10-14 23:45:46 +03:00
terrakok 70b8fcbf4f Rename version to 6.1 2020-10-14 22:51:54 +03:00
terrakok 8be84acb80 Update readme for version 6.0 2020-10-14 22:41:40 +03:00
terrakok 4872d10d6a Add screen keys and update version to 6.0.3-dev 2020-07-26 00:39:01 +03:00
terrakok ac5efa84ed Use default FragmentFactory and update version to 6.0.2-dev 2020-06-10 22:04:30 +03:00
terrakok 3e982794ec Update version to 6.0.1-dev 2020-06-10 18:07:44 +03:00
terrakok a748473697 Refactor FragmentFactory usage and remove outdated sample. 2020-06-10 17:11:28 +03:00
terrakok 0734a4eb22 Refactor AppNavigator. 2020-05-24 23:21:12 +03:00
terrakok 748f2f657c Setup new publication. 2020-05-24 12:48:08 +03:00
terrakok 88884ac51e Migrate to kotlin. 2020-05-23 16:25:32 +03:00
terrakok 0b35127778 Add new parameter for clear container or not during navigation to new screen. 2020-05-21 23:32:45 +03:00
terrakok 9aeb02cfae Update version to 5.1.1 2020-05-10 17:23:43 +03:00
Konstantin 7c31cdc1bd Merge pull request #112 from adolgiy/hotfix/fragment-factory-npe
Fix NPE when localStackCopy.size() == 0 and FragmentFactory is used
2020-05-10 17:11:14 +03:00
Aleksey Dolgiy 05a8ec8f44 Extract 'forward' to reuse in replace when localStackCopy.size > 0 2020-02-20 10:29:18 +03:00
Aleksey Dolgiy d08e6b1afe Code Review: Use internal replace for fragmentForward(Forward) and add explicit exception with null checks 2020-02-19 20:46:31 +03:00
Aleksey Dolgiy cea829ae88 Fix NPE when localStackCopy.size() == 0 and FragmentFactory is used 2020-02-19 17:37:25 +03:00
Konstantin Tskhovrebov dcf1532103 Merge branch 'develop' 2020-02-11 14:42:17 +03:00
Konstantin Tskhovrebov 046410e132 Update version of Cicerone to "5.1.0". 2020-02-11 14:41:24 +03:00
Konstantin e80b2d5432 Merge pull request #111 from terrakok/handle_command_error
Add errorOnApplyCommand for ability manual error handling.
2020-02-11 14:38:09 +03:00
Konstantin Tskhovrebov 3f1cf714df Add errorOnApplyCommand for ability manual error handling. 2020-02-11 14:36:13 +03:00
Konstantin febae878a4 Merge pull request #99 from asitnkova/feature/add_nullability_annotations
Add androidx nullability annotations
2020-02-11 12:45:10 +03:00
Konstantin 6cc2d3e474 Merge branch 'develop' into feature/add_nullability_annotations 2020-02-11 12:42:04 +03:00
Konstantin dc2f732ec6 Merge pull request #110 from asitnkova/feature/add_new_methods_fragment_1.2.0
Feature/add new methods fragment 1.2.0
2020-02-11 12:38:47 +03:00
asitnkova 5d6880c95f Add support for new FragmentTransaction.replace overload 2020-02-04 22:34:10 +07:00
Alexander e1e2dd8b9b Bump build tools 3.5.0-alpha01 -> 3.5.0, gradle 5.1-milestone-1 -> 5.6.4 2020-02-04 22:32:51 +07:00
Konstantin 4df0f34e34 Merge pull request #101 from Anton111111/develop
Made properties protected to add ability use them in overridden methods.
2019-11-18 12:52:07 +03:00
anton f36f6a1e44 Made properties protected to add ability use them in overridden methods. 2019-09-17 19:42:11 +03:00
Anton Potekhin be63c91abe Made properties protected to add ability use them in overridden methods. 2019-02-07 09:36:05 +03:00
Alexander Sitnikov 8e9bf90ee6 Add nullability annotations 2019-01-26 17:31:29 +07:00
terrakok 0ba13efe29 Merge branch 'develop' 2019-01-25 22:13:32 +03:00
terrakok d5737bb092 Update version of Cicerone to "5.0.0". 2019-01-25 22:13:14 +03:00
terrakok 07869ad6e7 Update gradle plugin. 2019-01-25 22:09:19 +03:00
Konstantin a57b8ecb3c Merge pull request #91 from pihariev/fix/sample_chain_representation
Fix sample chain representation for ordinary navigation.
2018-11-05 22:58:21 +03:00
Konstantin 8e3f41057d Merge pull request #88 from pihariev/refactor/androidx
Refactor library and sample to AndroidX packaging system.
2018-11-05 22:12:00 +03:00
Roman Pihariev 7823dd0658 Fix sample chain representation for ordinary navigation. 2018-11-05 16:10:43 +02:00
Roman Pihariev 6b195590de Refactor library and sample to AndroidX packaging system. 2018-11-02 22:44:01 +02:00
Konstantin Tskhovrebov 95f29e0143 Merge branch 'develop' 2018-10-03 23:13:07 +03:00
Konstantin Tskhovrebov dd4fdb5b83 Update version to 4.0.2. 2018-10-03 23:11:43 +03:00
Konstantin Tskhovrebov 5ea14dc30c Merge branch 'master' into develop 2018-10-03 23:11:16 +03:00
Konstantin 3da51271a1 Merge pull request #82 from qwert2603/patch-1
Fix error in Router.java
2018-10-03 23:05:59 +03:00
Alexander Zhdanov e2a7493549 Fix error in Router.java
I make call:
router.newRootChain(new ScreenOne(), new ScreenTwo());
and get a exception 
        java.lang.ArrayIndexOutOfBoundsException: length=2; index=2
        at ru.terrakok.cicerone.Router.newRootChain(Router.java:91)
        at ...
I suppose, iteration bound must be equal to length of array "screens" (not "commands").
2018-10-03 17:56:05 +07:00
Konstantin Tskhovrebov f03ea7f60e Merge branch 'develop' 2018-10-01 22:59:26 +03:00
Konstantin Tskhovrebov 9919f6a73f Update version to 4.0.1. 2018-10-01 22:57:51 +03:00
Konstantin Tskhovrebov 6ff618272d Fix newRootChain command. 2018-10-01 22:49:25 +03:00
Konstantin Tskhovrebov 301f3f3db8 Merge branch 'develop' 2018-09-27 22:15:56 +03:00
Konstantin Tskhovrebov de961bcd24 Update version to 4.0.0. 2018-09-27 22:11:02 +03:00
Konstantin Tskhovrebov bccb7ffdce Add newRootChain method to Router. 2018-09-27 22:10:20 +03:00
Konstantin Tskhovrebov b82196ce8c Update readme and fix java docs. 2018-09-27 22:03:58 +03:00
Konstantin Tskhovrebov 1d733906c8 Add media for readme. 2018-09-27 21:57:08 +03:00
Konstantin Tskhovrebov 39305091b9 Update readme for new version. 2018-09-27 18:03:12 +03:00
Konstantin Tskhovrebov 8cbd987c7c Add media for documentation. 2018-09-27 17:11:52 +03:00
Konstantin Tskhovrebov 6b9b8ba992 Update gradle plugin. 2018-09-27 16:21:56 +03:00
Konstantin Tskhovrebov ef05b43b0d Add java docs for public methods. 2018-09-27 16:21:22 +03:00
Konstantin Tskhovrebov 7e316fa7ef Delete unused code. 2018-09-27 16:20:41 +03:00
Konstantin Tskhovrebov f7f45ad493 Change screen default id to "getCanonicalName". 2018-09-27 16:19:53 +03:00
Konstantin Tskhovrebov 62520744cb Fix "back to root" crash. 2018-09-22 21:31:48 +03:00
Konstantin Tskhovrebov 4a09e0083c Merge branch 'feature/screen_chain' into develop 2018-09-22 21:23:50 +03:00
Konstantin Tskhovrebov 74e9681a48 Add router method for opening screen chain. 2018-09-22 21:23:33 +03:00
Konstantin Tskhovrebov 3ef18015ee Fix library android dependencies. 2018-09-22 21:22:33 +03:00
Konstantin Tskhovrebov 163fc90fcd Merge branch 'feature/result_changes' into develop 2018-09-22 20:45:05 +03:00
Konstantin Tskhovrebov a920505915 Update sample for manual result listening. 2018-09-22 20:43:40 +03:00
Konstantin Tskhovrebov 88427b0271 Change pure android stubs on android source dependency. 2018-09-22 20:43:08 +03:00
Konstantin Tskhovrebov 3b8954b993 Remove result listener from Router. 2018-09-22 20:42:22 +03:00
Konstantin Tskhovrebov d9fd28fb2c Merge branch 'feature/screen_class' into develop 2018-09-20 15:29:10 +03:00
Konstantin Tskhovrebov f2977d599d Update sample for new library version. 2018-09-20 15:27:42 +03:00
Konstantin Tskhovrebov ccc8dd626c Update gradle plugin. 2018-09-20 12:48:51 +03:00
Konstantin Tskhovrebov c9f0b413cb Remove FragmentNavigator and create common AppNavigator. 2018-09-20 12:48:32 +03:00
Konstantin Tskhovrebov 47f5197f90 Add screen class (contains key and creation logic). 2018-09-09 02:51:03 +03:00
Konstantin Tskhovrebov d0b4e5c119 Update gradle plugin. 2018-09-09 01:11:56 +03:00
Konstantin f036217eb0 Merge pull request #69 from Aleksander3007/changed_pop
Changed pop() to removeLast().
2018-07-12 12:24:20 +03:00
aaermakov 29fcd3c834 Changed pop() to removeLast(). 2018-06-19 09:15:11 +03:00
Konstantin Tskhovrebov 92600e69b2 rename setupFragmentTransactionAnimation to setupFragmentTransaction. because it can be used not for only animations 2018-06-06 16:06:30 +03:00
Konstantin Tskhovrebov 9f41ec0d8f remove unused field MainActivity.screenNames 2018-06-06 14:30:25 +03:00
Konstantin Tskhovrebov 56fb9e82e9 fix: switch on main thread for forward command with delay 2018-06-06 14:28:56 +03:00
Konstantin Tskhovrebov 96c1ec0a25 fix invalid sorting for chain view on main activity 2018-06-06 14:27:52 +03:00
Konstantin Tskhovrebov d90f4ab3a1 Merge branch 'feature/remove_system_msg' into develop 2018-06-06 14:26:03 +03:00
Konstantin Tskhovrebov 07a956be3a remove SystemMessage command 2018-06-06 14:09:00 +03:00
Konstantin ad68431834 Update README.md 2018-01-21 14:53:35 +03:00
Konstantin Tskhovrebov 98b1ff265f Merge branch 'develop' 2018-01-08 13:40:26 +03:00
Konstantin Tskhovrebov 9464fae539 updated library version to 3.0.0 2018-01-08 13:24:26 +03:00
Konstantin db0061a416 Merge pull request #58 from Jeevuz/small_refactor_and_docs
Small refactor and docs
2017-12-27 11:31:04 +03:00
Konstantin 8b2b1e0137 Merge pull request #59 from Jeevuz/feature/screen_name_for_unexisting
Add a screen key to the backToUnexisting method.
2017-12-27 11:29:40 +03:00
Vasili Chyrvon 2ac7848bbb Add a screen key to the backToUnexisting method. 2017-12-25 00:41:43 +03:00
Vasili Chyrvon 29bc51db6f Add the licence file 2017-12-25 00:31:00 +03:00
Vasili Chyrvon e2f78e8185 Change readme 2017-12-25 00:27:43 +03:00
Vasili Chyrvon 3eb0302046 Made executeCommands package-private 2017-12-25 00:25:40 +03:00
Vasili Chyrvon 933fdd2715 Add readability improvements and javadocs. 2017-12-25 00:14:23 +03:00
Konstantin Tskhovrebov 143b144cbf updated gradle version 2017-12-23 00:47:17 +03:00
Konstantin Tskhovrebov eeb5d20b8c Merge branch 'feature/commands_batch' into develop 2017-12-23 00:28:56 +03:00
Konstantin Tskhovrebov fe79f30c25 removed blocking navigation (popBackStackImmediate) 2017-12-21 17:18:45 +03:00
Konstantin Tskhovrebov 0ee0df0bcc moved auth comment to file header for library package 2017-12-19 23:42:57 +03:00
Konstantin Tskhovrebov f665c263ee changed navigator logic for apply commands batch 2017-12-19 22:56:01 +03:00
Konstantin 835d6425be Merge pull request #51 from Jeevuz/feature/context_for_activity_intents
Add Context to the activity creation method.
2017-12-01 14:28:14 +03:00
Vasili Chyrvon 851535ab28 Fix signature of the createActivityIntent() method in the sample and readme. 2017-11-27 02:13:34 +03:00
Vasili Chyrvon e4bb13b025 Add Context to the activity creation method. 2017-11-24 14:45:11 +03:00
Konstantin 4e0f36d57a Merge pull request #49 from Jeevuz/check_activity_exists
Added check for the activity existence before opening.
2017-10-24 09:22:05 +03:00
Vasili Chyrvon 752c22c0b9 Added check for the activity existence before opening. 2017-10-23 17:25:23 +03:00
127 changed files with 2222 additions and 3484 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2017 Konstantin Tskhovrebov (@terrakok)
and Vasili Chyrvon (@Jeevuz)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+106 -116
View File
@@ -2,8 +2,6 @@
[![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)
[![Join the chat at https://gitter.im/terrakok/Cicerone](https://img.shields.io/badge/Gitter-Join%20Chat-brightred.svg?style=flat)](https://gitter.im/terrakok/Cicerone)
[![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)
@@ -11,7 +9,7 @@
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
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 pattern (try [Moxy](https://github.com/Arello-Mobile/Moxy)), but will work great with any architecture.
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
@@ -21,163 +19,152 @@ It was designed to be used with the MVP pattern (try [Moxy](https://github.com/A
+ functionality is simple to extend
+ suitable for Unit Testing
## Version 2.+
+ easy screen result subscription
+ predefined navigator ready for setup transition animation
**See the sample application**
## Additional features
+ 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
## How to add
Add the dependency in your build.gradle:
```groovy
```kotlin
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:X.X.X'
implementation("com.github.terrakok:cicerone:X.X.X")
}
```
Initialize the library (for example in your Application class):
```java
public class SampleApplication extends MvpApplication {
public static SampleApplication INSTANCE;
private Cicerone<Router> cicerone;
```kotlin
class App : Application() {
private val cicerone = Cicerone.create()
val router get() = cicerone.router
val navigatorHolder get() = cicerone.getNavigatorHolder()
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
initCicerone();
override fun onCreate() {
super.onCreate()
INSTANCE = this
}
private void initCicerone() {
cicerone = Cicerone.create();
}
public NavigatorHolder getNavigatorHolder() {
return cicerone.getNavigatorHolder();
}
public Router getRouter() {
return cicerone.getRouter();
companion object {
internal lateinit var INSTANCE: App
private set
}
}
```
## How it works?
![](https://habrastorage.org/files/4df/45d/973/4df45d9733fc4ee0a2f0be933de475b1.png)
<img src="https://github.com/terrakok/Cicerone/raw/develop/media/CiceroneDiagram.png" alt="drawing" width="800"/>
Presenter calls navigation method of Router.
```java
public class SamplePresenter extends Presenter<SampleView> {
private Router router;
```kotlin
class SamplePresenter(
private val router: Router
) : Presenter<SampleView>() {
public SamplePresenter() {
router = SampleApplication.INSTANCE.getRouter();
fun onOpenNewScreen() {
router.navigateTo(SomeScreen())
}
public void onBackCommandClick() {
router.exit();
}
public void onForwardCommandClick() {
router.navigateTo("Some screen");
fun onBackPressed() {
router.exit()
}
}
```
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 _"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.
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.
```java
protected void executeCommand(Command command) {
if (navigator != null) {
navigator.applyCommand(command);
} else {
pendingCommands.add(command);
}
```kotlin
fun executeCommands(commands: Array<out Command>) {
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
}
```
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_.
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()))
```java
@Override
protected void onResume() {
super.onResume();
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(navigator);
```kotlin
private val navigator = AppNavigator(this, R.id.container)
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
@Override
protected void onPause() {
super.onPause();
SampleApplication.INSTANCE.getNavigatorHolder().removeNavigator();
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
private Navigator navigator = new Navigator() {
@Override
public void applyCommand(Command command) {
//implement commands logic
}
};
```
## Navigation commands
This commands set will fulfill the needs of the most applications. But if you need something special - just add it!
+ Forward - Opens new screen
![](https://habrastorage.org/files/862/77e/b20/86277eb20b574dae8307ac4f64b0f090.png)
+ Back - Rolls back the last transition
![](https://habrastorage.org/files/059/b63/2d3/059b632d3a7c4515a534b9e5e881c8f0.png)
+ BackTo - Rolls back to the needed screen in the screens chain
![](https://habrastorage.org/files/a45/4f4/c34/a454f4c340764632ad0669014ad5550d.png)
+ Replace - Replaces the current screen
![](https://habrastorage.org/files/4ae/95c/fee/4ae95cfee4c04f038ad17d358ab08d07.png)
+ SystemMessage - Shows system message (Alert, Toast, Snack, etc.)
![](https://habrastorage.org/files/6e7/1a6/4ed/6e71a64edec04079bf33faa7ab39606f.png)
+ Forward - Opens new screen
![](https://github.com/terrakok/Cicerone/raw/develop/media/forward_img.png)
+ Back - Rolls back the last transition
![](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/develop/media/backTo_img.png)
+ Replace - Replaces the current screen
![](https://github.com/terrakok/Cicerone/raw/develop/media/replace_img.png)
## Predefined navigators
The library provides predefined navigators for _Fragments_ to use inside _Activity_.
To use, just provide it with the container and _FragmentManager_ and override few simple methods.
```java
private Navigator navigator = new SupportAppNavigator(this, R.id.container) {
@Override
protected Intent createActivityIntent(String screenKey, Object data) {
return null;
}
@Override
protected Fragment createFragment(String screenKey, Object data) {
switch (screenKey) {
case Screens.PROFILE_SCREEN:
return new ProfileFragment();
case Screens.SELECT_PHOTO_SCREEN:
return SelectPhotoFragment.getNewInstance((int) data);
}
return null;
}
@Override
protected void setupFragmentTransactionAnimation(
Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
//setup animation
}
};
## Predefined navigator
The library provides predefined navigator for _Fragments_ and _Activity_.
To use, just provide it with the container and _FragmentManager_.
```kotlin
private val navigator = AppNavigator(this, R.id.container)
```
## Sample
To see how to add, initialize and use the library and predefined navigators check out the sample.
![](https://habrastorage.org/web/a94/d73/653/a94d736534694d9daa994e0c260fca28.gif)
![](https://habrastorage.org/web/6dd/a19/15c/6dda1915cdcf4f14bed16fcffb3fd938.gif)
![](https://habrastorage.org/web/a63/881/7f8/a638817f8bba49daacc4fa427987fabb.gif)
Custom navigator can be useful sometimes:
```kotlin
private val navigator = object : AppNavigator(this, R.id.container) {
override fun setupFragmentTransaction(
fragmentTransaction: FragmentTransaction,
currentFragment: Fragment?,
nextFragment: Fragment?
) {
super.setupFragmentTransaction(fragmentTransaction, currentFragment, nextFragment)
fragmentTransaction.setReorderingAllowed(true)
}
override fun applyCommands(commands: Array<out Command>) {
hideKeyboard()
super.applyCommands(commands)
}
}
```
## Screens
Describe your screens as you like e.g. create Kotlin `object` with all application screens:
```kotlin
object Screens {
val Main = FragmentScreen("MainFragment") { MainFragment() }
val AddressSearch = FragmentScreen("AddressSearchFragment") { AddressSearchFragment() }
fun Profile(userId: Long) = FragmentScreen("ProfileFragment") { ProfileFragment(userId) }
}
```
Additional you can use `FragmentFactory` for creating your screens:
```kotlin
val SomeScreen = FragmentScreen("SomeScreenId") { factory: FragmentFactory -> ... }
```
## Sample
To see how to add, initialize and use the library and predefined navigators check out
the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
![](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)
@@ -185,7 +172,10 @@ To see how to add, initialize and use the library and predefined navigators chec
## License
```
The MIT License (MIT)
MIT License
Copyright (c) 2017 Konstantin Tskhovrebov (@terrakok)
and Vasili Chyrvon (@Jeevuz)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
-178
View File
@@ -1,178 +0,0 @@
# Cicerone
[![Telegram](https://img.shields.io/badge/Telegram-RUS-blue.svg)](https://t.me/Cicerone_RUS)
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
Cicerone (_"чи-че-ро́-не"_ - устар. гид) - легкая библиотека для простой реализации навигации в андроид приложении.
Разработана для использования в MVP архитектуре (попробуйте [Moxy](https://github.com/Arello-Mobile/Moxy)), но легко встраивается в любые решения.
## Основные преимущества
+ не завязана на Fragment'ы
+ не фреймворк
+ короткие вызовы навигации (никаких билдеров)
+ lifecycle-безопасна!
+ простое расширение функционала
+ приспособлена для Unit тестов
## Как подключить?
Добавьте в build.gradle зависимость:
```groovy
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:X.X.X'
}
```
И инициализируйте библиотеку, например, так:
```java
public class SampleApplication extends MvpApplication {
public static SampleApplication INSTANCE;
private Cicerone<Router> cicerone;
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
initCicerone();
}
private void initCicerone() {
cicerone = Cicerone.create();
}
public NavigatorHolder getNavigatorHolder() {
return cicerone.getNavigatorHolder();
}
public Router getRouter() {
return cicerone.getRouter();
}
}
```
## Как это работает?
![](https://habrastorage.org/files/4df/45d/973/4df45d9733fc4ee0a2f0be933de475b1.png)
Presenter вызывает у Router метод навигации.
```java
public class SamplePresenter extends Presenter<SampleView> {
private Router router;
public SamplePresenter() {
router = SampleApplication.INSTANCE.getRouter();
}
public void onBackCommandClick() {
router.exit();
}
public void onForwardCommandClick() {
router.navigateTo("Some screen");
}
}
```
Router - класс, который реализует вызванный метод навигации набором Command и отправляет их в CommandBuffer.
CommandBuffer проверяет есть ли _"активный"_ Navigator.
Если да, то на нем вызываются необходимые команды для осуществления запрошенного перехода.
Если нет, то команды добавляются в очередь, которая будет применена, как только появится _"активный"_ Navigator.
```java
protected void executeCommand(Command command) {
if (navigator != null) {
navigator.applyCommand(command);
} else {
pendingCommands.add(command);
}
}
```
Navigator - (например) анонимный класс внутри Activity, который реализует команды навигации.
Activity предоставляет свой Navigator для CommandBuffer в методе _onResume_ и очищает в _onPause_
**Внимание**: В FragmentActivity следует использовать _onResumeFragments()_. ([подробнее](https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()))
```java
@Override
protected void onResume() {
super.onResume();
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(navigator);
}
@Override
protected void onPause() {
super.onPause();
SampleApplication.INSTANCE.getNavigatorHolder().removeNavigator();
}
private Navigator navigator = new Navigator() {
@Override
public void applyCommand(Command command) {
//implements commands logic
}
};
```
Навигатор не обязан быть в Activity. Он может быть и внутри Fragment'а, который переключает внутри себя View.
## Команды навигатора
Для большинства задач предоставленных в библиотеке команд должно хватить, но их всегда можно дополнить собственными!
+ Forward - переход на новый экран
![](https://habrastorage.org/files/862/77e/b20/86277eb20b574dae8307ac4f64b0f090.png)
+ Back - возврат на предыдущий экран
![](https://habrastorage.org/files/059/b63/2d3/059b632d3a7c4515a534b9e5e881c8f0.png)
+ BackTo - возврат к конкретному экрану в цепочке, либо на корневой, если указанный экран не найден
![](https://habrastorage.org/files/a45/4f4/c34/a454f4c340764632ad0669014ad5550d.png)
+ Replace - замена текущего экрана на новый
![](https://habrastorage.org/files/4ae/95c/fee/4ae95cfee4c04f038ad17d358ab08d07.png)
+ SystemMessage - показ системного сообщения (Alert, Toast, Snack и тд)
![](https://habrastorage.org/files/6e7/1a6/4ed/6e71a64edec04079bf33faa7ab39606f.png)
## Готовые навигаторы
Библиотека предоставляет готовые навигаторы для _Activity_.
Чтобы их использовать, достаточно указать контейнер и передать _FragmentManager_.
```java
private Navigator navigator = new SupportFragmentNavigator(
getSupportFragmentManager(), R.id.main_container) {
@Override
protected Fragment createFragment(String screenKey, Object data) {
return SampleFragment.getNewInstance((int) data);
}
@Override
protected void showSystemMessage(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
@Override
protected void exit() {
finish();
}
};
```
## Sample
Работу библиотеки, готовых навигаторов и другое можно посмотреть в _sample_ приложении.
![](https://habrastorage.org/files/16d/2ee/6e3/16d2ee6e33a0428eb4f0dcab8ce6b294.gif)
![](https://hsto.org/files/867/638/c33/867638c338704489b3107a6d7cb28c2d.gif)
## Участники
+ идея и реализация - Константин Цховребов (@terrakok)
+ архитектурные советы, документация и публикация - Василий Чирвон (@Jeevuz)
## Лицензия
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+6 -2
View File
@@ -1,14 +1,17 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.4.10'
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
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.7.1'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
}
}
@@ -16,6 +19,7 @@ buildscript {
allprojects {
repositories {
jcenter()
google()
}
}
+2 -1
View File
@@ -12,7 +12,8 @@
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
android.enableJetifier=true
android.useAndroidX=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+2 -2
View File
@@ -1,6 +1,6 @@
#Fri Mar 10 17:09:31 MSK 2017
#Fri Jan 25 22:04:15 MSK 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
+24 -6
View File
@@ -1,4 +1,5 @@
apply plugin: 'java'
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.
@@ -7,15 +8,17 @@ targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
compileOnly project(':stub-android')
compileOnly 'com.google.android:android:4.1.1.4'
implementation "org.jetbrains.kotlin:kotlin-stdlib"
}
ext {
// This params is for the library uploading to the Bintray
bintrayRepo = 'terramaven'
bintrayName = 'cicerone'
publishedGroupId = 'ru.terrakok.cicerone'
bintrayName = 'cicerone-kotlin'
publishedGroupId = 'com.github.terrakok'
artifact = 'cicerone'
libraryVersion = '2.1.0'
libraryVersion = '6.0'
gitUrl = 'https://github.com/terrakok/Cicerone'
allLicenses = ['MIT']
}
@@ -23,13 +26,13 @@ ext {
// 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
project.archivesBaseName = 'cicerone' // to fix that project name different from artifact name
apply from: 'androidmaven.gradle'
apply from: 'bintray.gradle'
// Tasks for sources and javadocs jars
task sourcesJar(type: Jar) {
from sourceSets.main.java.srcDirs
from sourceSets.main.kotlin.srcDirs
classifier = 'sources'
}
@@ -37,7 +40,22 @@ 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,33 +0,0 @@
package ru.terrakok.cicerone;
import ru.terrakok.cicerone.commands.Command;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 12.10.16
*/
/**
* BaseRouter is an abstract class to implement high-level navigation.
* Extend it to add needed transition methods.
*/
public abstract class BaseRouter {
private CommandBuffer commandBuffer;
public BaseRouter() {
this.commandBuffer = new CommandBuffer();
}
CommandBuffer getCommandBuffer() {
return commandBuffer;
}
/**
* Sends navigation command to {@link CommandBuffer}.
*
* @param command navigation command to execute
*/
protected void executeCommand(Command command) {
commandBuffer.executeCommand(command);
}
}
@@ -1,45 +0,0 @@
package ru.terrakok.cicerone;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Cicerone is the holder for other library components.
* To use it, instantiate it using one of the {@link #create()} methods.
* When you need a {@link NavigatorHolder navigation holder} or router, get it here.
*
* @param <T> type of router. You can use the default {@link Router} or pass your own
* {@link BaseRouter} implementation.
*/
public class Cicerone<T extends BaseRouter> {
private T router;
private Cicerone(T router) {
this.router = router;
}
public NavigatorHolder getNavigatorHolder() {
return router.getCommandBuffer();
}
public T getRouter() {
return router;
}
/**
* Creates the Cicerone instance with the default {@link Router router}
*/
public static Cicerone<Router> create() {
return create(new Router());
}
/**
* Creates the Cicerone instance with the custom router.
* @param customRouter the custom router extending {@link BaseRouter}
*/
public static <T extends BaseRouter> Cicerone<T> create(T customRouter) {
return new Cicerone<>(customRouter);
}
}
@@ -1,48 +0,0 @@
package ru.terrakok.cicerone;
import java.util.LinkedList;
import java.util.Queue;
import ru.terrakok.cicerone.commands.Command;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 12.10.16
*/
/**
* Passes navigation command to an active {@link Navigator}
* or stores it in the pending commands queue to pass it later.
*/
class CommandBuffer implements NavigatorHolder {
private Navigator navigator;
private Queue<Command> pendingCommands = new LinkedList<>();
@Override
public void setNavigator(Navigator navigator) {
this.navigator = navigator;
while (!pendingCommands.isEmpty()) {
if (navigator != null) {
executeCommand(pendingCommands.poll());
} else break;
}
}
@Override
public void removeNavigator() {
this.navigator = null;
}
/**
* Passes {@code command} to the {@link Navigator} if it available.
* Else puts it to the pending commands queue to pass it later.
* @param command navigation command
*/
public void executeCommand(Command command) {
if (navigator != null) {
navigator.applyCommand(command);
} else {
pendingCommands.add(command);
}
}
}
@@ -1,22 +0,0 @@
package ru.terrakok.cicerone;
import ru.terrakok.cicerone.commands.Command;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* The low-level navigation interface.
* Navigator is the one who actually performs any transition.
*/
public interface Navigator {
/**
* Performs transition described by the navigation command
*
* @param command the navigation command to apply
*/
void applyCommand(Command command);
}
@@ -1,25 +0,0 @@
package ru.terrakok.cicerone;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Navigator holder interface.
* Use it to connect a {@link Navigator} to the {@link Cicerone}.
*/
public interface NavigatorHolder {
/**
* Set an active Navigator for the Cicerone and start receive commands.
*
* @param navigator new active Navigator
*/
void setNavigator(Navigator navigator);
/**
* Remove the current Navigator and stop receive commands.
*/
void removeNavigator();
}
@@ -1,212 +0,0 @@
package ru.terrakok.cicerone;
import java.util.HashMap;
import ru.terrakok.cicerone.commands.Back;
import ru.terrakok.cicerone.commands.BackTo;
import ru.terrakok.cicerone.commands.Forward;
import ru.terrakok.cicerone.commands.Replace;
import ru.terrakok.cicerone.commands.SystemMessage;
import ru.terrakok.cicerone.result.ResultListener;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 12.10.16
*/
/**
* Router is the class for high-level navigation.
* Use it to perform needed transitions.<br>
* This implementation covers almost all cases needed for the average app.
* Extend it if you need some tricky navigation.
*/
public class Router extends BaseRouter {
private HashMap<Integer, ResultListener> resultListeners = new HashMap<>();
public Router() {
super();
}
/**
* Subscribe to the screen result.<br>
* <b>Note:</b> only one listener can subscribe to a unique resultCode!<br>
* You must call a <b>removeResultListener()</b> to avoid a memory leak.
*
* @param resultCode key for filter results
* @param listener result listener
*/
public void setResultListener(Integer resultCode, ResultListener listener) {
resultListeners.put(resultCode, listener);
}
/**
* Unsubscribe from the screen result.
*
* @param resultCode key for filter results
*/
public void removeResultListener(Integer resultCode) {
resultListeners.remove(resultCode);
}
/**
* Send result data to subscriber.
*
* @param resultCode result data key
* @param result result data
* @return TRUE if listener was notified and FALSE otherwise
*/
protected boolean sendResult(Integer resultCode, Object result) {
ResultListener resultListener = resultListeners.get(resultCode);
if (resultListener != null) {
resultListener.onResult(result);
return true;
}
return false;
}
/**
* Open new screen and add it to the screens chain.
*
* @param screenKey screen key
*/
public void navigateTo(String screenKey) {
navigateTo(screenKey, null);
}
/**
* Open new screen and add it to screens chain.
*
* @param screenKey screen key
* @param data initialisation parameters for the new screen
*/
public void navigateTo(String screenKey, Object data) {
executeCommand(new Forward(screenKey, data));
}
/**
* Clear the current screens chain and start new one
* by opening a new screen right after the root.
*
* @param screenKey screen key
*/
public void newScreenChain(String screenKey) {
newScreenChain(screenKey, null);
}
/**
* Clear the current screens chain and start new one
* by opening a new screen right after the root.
*
* @param screenKey screen key
* @param data initialisation parameters for the new screen
*/
public void newScreenChain(String screenKey, Object data) {
executeCommand(new BackTo(null));
executeCommand(new Forward(screenKey, data));
}
/**
* Clear all screens and open new one as root.
*
* @param screenKey screen key
*/
public void newRootScreen(String screenKey) {
newRootScreen(screenKey, null);
}
/**
* Clear all screens and open new one as root.
*
* @param screenKey screen key
* @param data initialisation parameters for the root
*/
public void newRootScreen(String screenKey, Object data) {
executeCommand(new BackTo(null));
executeCommand(new Replace(screenKey, data));
}
/**
* Replace current screen.
* By replacing the screen, you alters the backstack,
* so by going back you will return to the previous screen
* and not to the replaced one.
*
* @param screenKey screen key
*/
public void replaceScreen(String screenKey) {
replaceScreen(screenKey, null);
}
/**
* Replace current screen.
* By replacing the screen, you alters the backstack,
* so by going back you will return to the previous screen
* and not to the replaced one.
*
* @param screenKey screen key
* @param data initialisation parameters for the new screen
*/
public void replaceScreen(String screenKey, Object data) {
executeCommand(new Replace(screenKey, data));
}
/**
* Return back to the needed screen from the chain.
* Behavior in the case when no needed screens found depends on
* the processing of the {@link BackTo} command in a {@link Navigator} implementation.
*
* @param screenKey screen key
*/
public void backTo(String screenKey) {
executeCommand(new BackTo(screenKey));
}
/**
* Remove all screens from the chain and exit.
* It's mostly used to finish the application or close a supplementary navigation chain.
*/
public void finishChain() {
executeCommand(new BackTo(null));
executeCommand(new Back());
}
/**
* Return to the previous screen in the chain.
* Behavior in the case when the current screen is the root depends on
* the processing of the {@link Back} command in a {@link Navigator} implementation.
*/
public void exit() {
executeCommand(new Back());
}
/**
* Return to the previous screen in the chain and send result data.
*
* @param resultCode result data key
* @param result result data
*/
public void exitWithResult(Integer resultCode, Object result) {
exit();
sendResult(resultCode, result);
}
/**
* Return to the previous screen in the chain and show system message.
*
* @param message message to show
*/
public void exitWithMessage(String message) {
executeCommand(new Back());
executeCommand(new SystemMessage(message));
}
/**
* Show system message.
*
* @param message message to show
*/
public void showSystemMessage(String message) {
executeCommand(new SystemMessage(message));
}
}
@@ -1,102 +0,0 @@
package ru.terrakok.cicerone.android;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import ru.terrakok.cicerone.commands.BackTo;
import ru.terrakok.cicerone.commands.Command;
import ru.terrakok.cicerone.commands.Forward;
import ru.terrakok.cicerone.commands.Replace;
/**
* Extends {@link FragmentNavigator} to allow
* open new or replace current activity.
* <p>
* This navigator DOESN'T provide full featured Activity navigation,
* but can ease Activity start or replace from current navigator.
* </p>
*
* @author Vasili Chyrvon (vasili.chyrvon@gmail.com)
*/
public abstract class AppNavigator extends FragmentNavigator {
private Activity activity;
public AppNavigator(Activity activity, int containerId) {
super(activity.getFragmentManager(), containerId);
this.activity = activity;
}
public AppNavigator(Activity activity, FragmentManager fragmentManager, int containerId) {
super(fragmentManager, containerId);
this.activity = activity;
}
/**
* Override this method to create option for start activity
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param activityIntent activity intent
* @return transition options
*/
protected Bundle createStartActivityOptions(Command command, Intent activityIntent) {
return null;
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
Forward forward = (Forward) command;
Intent activityIntent = createActivityIntent(forward.getScreenKey(), forward.getTransitionData());
// Start activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
return;
}
} else if (command instanceof Replace) {
Replace replace = (Replace) command;
Intent activityIntent = createActivityIntent(replace.getScreenKey(), replace.getTransitionData());
// Replace activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
activity.finish();
return;
}
}
// Use default fragments navigation
super.applyCommand(command);
}
/**
* Creates Intent to start Activity for {@code screenKey}.
* <p>
* <b>Warning:</b> This method does not work with {@link BackTo} command.
* </p>
*
* @param screenKey screen key
* @param data initialization data, can be null
* @return intent to start Activity for the passed screen key
*/
protected abstract Intent createActivityIntent(String screenKey, Object data);
@Override
protected void showSystemMessage(String message) {
// Toast by default
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
}
@Override
protected void exit() {
// Finish by default
activity.finish();
}
}
@@ -1,186 +0,0 @@
package ru.terrakok.cicerone.android;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.commands.Back;
import ru.terrakok.cicerone.commands.BackTo;
import ru.terrakok.cicerone.commands.Command;
import ru.terrakok.cicerone.commands.Forward;
import ru.terrakok.cicerone.commands.Replace;
import ru.terrakok.cicerone.commands.SystemMessage;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* {@link Navigator} implementation based on the fragments.
* <p>
* {@link BackTo} navigation command will return to the root if
* needed screen isn't found in the screens chain.
* To change this behavior override {@link #backToUnexisting()} method.
* </p>
* <p>
* {@link Back} command will call {@link #exit()} method if current screen is the root.
* </p>
*/
public abstract class FragmentNavigator implements Navigator {
private FragmentManager fragmentManager;
private int containerId;
/**
* Creates FragmentNavigator.
*
* @param fragmentManager fragment manager
* @param containerId id of the fragments container layout
*/
public FragmentNavigator(FragmentManager fragmentManager, int containerId) {
this.fragmentManager = fragmentManager;
this.containerId = containerId;
}
/**
* Override this method to setup custom fragment transaction animation.
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param currentFragment current fragment in container
* (for {@link Replace} command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
* @param fragmentTransaction fragment transaction
*/
protected void setupFragmentTransactionAnimation(Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
Forward forward = (Forward) command;
Fragment fragment = createFragment(forward.getScreenKey(), forward.getTransitionData());
if (fragment == null) {
unknownScreen(command);
return;
}
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(forward.getScreenKey())
.commit();
} else if (command instanceof Back) {
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
} else {
exit();
}
} else if (command instanceof Replace) {
Replace replace = (Replace) command;
Fragment fragment = createFragment(replace.getScreenKey(), replace.getTransitionData());
if (fragment == null) {
unknownScreen(command);
return;
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(replace.getScreenKey())
.commit();
} else {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.commit();
}
} else if (command instanceof BackTo) {
String key = ((BackTo) command).getScreenKey();
if (key == null) {
backToRoot();
} else {
boolean hasScreen = false;
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
if (key.equals(fragmentManager.getBackStackEntryAt(i).getName())) {
fragmentManager.popBackStackImmediate(key, 0);
hasScreen = true;
break;
}
}
if (!hasScreen) {
backToUnexisting();
}
}
} else if (command instanceof SystemMessage) {
showSystemMessage(((SystemMessage) command).getMessage());
}
}
private void backToRoot() {
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
/**
* Creates Fragment matching {@code screenKey}.
*
* @param screenKey screen key
* @param data initialization data
* @return instantiated fragment for the passed screen key
*/
protected abstract Fragment createFragment(String screenKey, Object data);
/**
* Shows system message.
*
* @param message message to show
*/
protected abstract void showSystemMessage(String message);
/**
* Called when we try to back from the root.
*/
protected abstract void exit();
/**
* Called when we tried to back to some specific screen, but didn't found it.
*/
protected void backToUnexisting() {
backToRoot();
}
/**
* Called if we can't create a screen.
*/
protected void unknownScreen(Command command) {
throw new RuntimeException("Can't create a screen for passed screenKey.");
}
}
@@ -1,103 +0,0 @@
package ru.terrakok.cicerone.android;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.widget.Toast;
import ru.terrakok.cicerone.commands.BackTo;
import ru.terrakok.cicerone.commands.Command;
import ru.terrakok.cicerone.commands.Forward;
import ru.terrakok.cicerone.commands.Replace;
/**
* Extends {@link SupportFragmentNavigator} to allow
* open new or replace current activity.
* <p>
* This navigator DOESN'T provide full featured Activity navigation,
* but can ease Activity start or replace from current navigator.
* </p>
*
* @author Vasili Chyrvon (vasili.chyrvon@gmail.com)
*/
public abstract class SupportAppNavigator extends SupportFragmentNavigator {
private Activity activity;
public SupportAppNavigator(FragmentActivity activity, int containerId) {
super(activity.getSupportFragmentManager(), containerId);
this.activity = activity;
}
public SupportAppNavigator(FragmentActivity activity, FragmentManager fragmentManager, int containerId) {
super(fragmentManager, containerId);
this.activity = activity;
}
/**
* Override this method to create option for start activity
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param activityIntent activity intent
* @return transition options
*/
protected Bundle createStartActivityOptions(Command command, Intent activityIntent) {
return null;
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
Forward forward = (Forward) command;
Intent activityIntent = createActivityIntent(forward.getScreenKey(), forward.getTransitionData());
// Start activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
return;
}
} else if (command instanceof Replace) {
Replace replace = (Replace) command;
Intent activityIntent = createActivityIntent(replace.getScreenKey(), replace.getTransitionData());
// Replace activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
activity.startActivity(activityIntent, options);
activity.finish();
return;
}
}
// Use default fragments navigation
super.applyCommand(command);
}
/**
* Creates Intent to start Activity for {@code screenKey}.
* <p>
* <b>Warning:</b> This method does not work with {@link BackTo} command.
* </p>
*
* @param screenKey screen key
* @param data initialization data, can be null
* @return intent to start Activity for the passed screen key
*/
protected abstract Intent createActivityIntent(String screenKey, Object data);
@Override
protected void showSystemMessage(String message) {
// Toast by default
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
}
@Override
protected void exit() {
// Finish by default
activity.finish();
}
}
@@ -1,185 +0,0 @@
package ru.terrakok.cicerone.android;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.commands.Back;
import ru.terrakok.cicerone.commands.BackTo;
import ru.terrakok.cicerone.commands.Command;
import ru.terrakok.cicerone.commands.Forward;
import ru.terrakok.cicerone.commands.Replace;
import ru.terrakok.cicerone.commands.SystemMessage;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* {@link Navigator} implementation based on the support fragments.
* <p>
* {@link BackTo} navigation command will return to the root if
* needed screen isn't found in the screens chain.
* To change this behavior override {@link #backToUnexisting()} method.
* </p>
* <p>
* {@link Back} command will call {@link #exit()} method if current screen is the root.
* </p>
*/
public abstract class SupportFragmentNavigator implements Navigator {
private FragmentManager fragmentManager;
private int containerId;
/**
* Creates SupportFragmentNavigator.
*
* @param fragmentManager support fragment manager
* @param containerId id of the fragments container layout
*/
public SupportFragmentNavigator(FragmentManager fragmentManager, int containerId) {
this.fragmentManager = fragmentManager;
this.containerId = containerId;
}
/**
* Override this method to setup custom fragment transaction animation.
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param currentFragment current fragment in container
* (for {@link Replace} command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
* @param fragmentTransaction fragment transaction
*/
protected void setupFragmentTransactionAnimation(Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
}
@Override
public void applyCommand(Command command) {
if (command instanceof Forward) {
Forward forward = (Forward) command;
Fragment fragment = createFragment(forward.getScreenKey(), forward.getTransitionData());
if (fragment == null) {
unknownScreen(command);
return;
}
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(forward.getScreenKey())
.commit();
} else if (command instanceof Back) {
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
} else {
exit();
}
} else if (command instanceof Replace) {
Replace replace = (Replace) command;
Fragment fragment = createFragment(replace.getScreenKey(), replace.getTransitionData());
if (fragment == null) {
unknownScreen(command);
return;
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(replace.getScreenKey())
.commit();
} else {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransactionAnimation(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.commit();
}
} else if (command instanceof BackTo) {
String key = ((BackTo) command).getScreenKey();
if (key == null) {
backToRoot();
} else {
boolean hasScreen = false;
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
if (key.equals(fragmentManager.getBackStackEntryAt(i).getName())) {
fragmentManager.popBackStackImmediate(key, 0);
hasScreen = true;
break;
}
}
if (!hasScreen) {
backToUnexisting();
}
}
} else if (command instanceof SystemMessage) {
showSystemMessage(((SystemMessage) command).getMessage());
}
}
private void backToRoot() {
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
/**
* Creates Fragment matching {@code screenKey}.
*
* @param screenKey screen key
* @param data initialization data
* @return instantiated fragment for the passed screen key
*/
protected abstract Fragment createFragment(String screenKey, Object data);
/**
* Shows system message.
*
* @param message message to show
*/
protected abstract void showSystemMessage(String message);
/**
* Called when we try to back from the root.
*/
protected abstract void exit();
/**
* Called when we tried to back to some specific screen, but didn't found it.
*/
protected void backToUnexisting() {
backToRoot();
}
/**
* Called if we can't create a screen.
*/
protected void unknownScreen(Command command) {
throw new RuntimeException("Can't create a screen for passed screenKey.");
}
}
@@ -1,18 +0,0 @@
package ru.terrakok.cicerone.commands;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Rolls back the last transition from the screens chain.
*/
public class Back implements Command {
/**
* Creates a {@link Back} navigation command.
*/
public Back() {
}
}
@@ -1,30 +0,0 @@
package ru.terrakok.cicerone.commands;
import ru.terrakok.cicerone.Navigator;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Rolls back to the needed screen from the screens chain.
* Behavior in the case when no needed screens found depends on an implementation of the {@link Navigator}.
* But the recommended behavior is to return to the root.
*/
public class BackTo implements Command {
private String screenKey;
/**
* Creates a {@link BackTo} navigation command.
*
* @param screenKey screen key
*/
public BackTo(String screenKey) {
this.screenKey = screenKey;
}
public String getScreenKey() {
return screenKey;
}
}
@@ -1,13 +0,0 @@
package ru.terrakok.cicerone.commands;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Navigation command describes screens transition.
* that can be processed by {@link ru.terrakok.cicerone.Navigator}.
*/
public interface Command {
}
@@ -1,33 +0,0 @@
package ru.terrakok.cicerone.commands;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Opens new screen.
*/
public class Forward implements Command {
private String screenKey;
private Object transitionData;
/**
* Creates a {@link Forward} navigation command.
*
* @param screenKey screen key
* @param transitionData initial data
*/
public Forward(String screenKey, Object transitionData) {
this.screenKey = screenKey;
this.transitionData = transitionData;
}
public String getScreenKey() {
return screenKey;
}
public Object getTransitionData() {
return transitionData;
}
}
@@ -1,33 +0,0 @@
package ru.terrakok.cicerone.commands;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Replaces the current screen.
*/
public class Replace implements Command {
private String screenKey;
private Object transitionData;
/**
* Creates a {@link Replace} navigation command.
*
* @param screenKey screen key
* @param transitionData initial data
*/
public Replace(String screenKey, Object transitionData) {
this.screenKey = screenKey;
this.transitionData = transitionData;
}
public String getScreenKey() {
return screenKey;
}
public Object getTransitionData() {
return transitionData;
}
}
@@ -1,26 +0,0 @@
package ru.terrakok.cicerone.commands;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
/**
* Shows system message.
*/
public class SystemMessage implements Command {
private String message;
/**
* Creates a {@link SystemMessage} command.
*
* @param message message text
*/
public SystemMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
@@ -1,15 +0,0 @@
package ru.terrakok.cicerone.result;
/**
* @author Konstantin Tskhovrebov (aka terrakok) on 04.07.17.
*/
public interface ResultListener {
/**
* Received result from screen.
*
* @param resultData
*/
void onResult(Object resultData);
}
@@ -0,0 +1,19 @@
package com.github.terrakok.cicerone
/**
* BaseRouter is an abstract class to implement high-level navigation.
*
* Extend it to add needed transition methods.
*/
abstract class BaseRouter {
internal val commandBuffer = CommandBuffer()
/**
* Sends navigation command array to [CommandBuffer].
*
* @param commands navigation command array to execute
*/
protected fun executeCommands(vararg commands: Command) {
commandBuffer.executeCommands(commands)
}
}
@@ -0,0 +1,28 @@
package com.github.terrakok.cicerone
/**
* Cicerone is the holder for other library components.
*
* To use it, instantiate it using one of the {@link #create()} methods.
*
* When you need a [NavigatorHolder] or router, get it here.
*
* @param router type of router. You can use the default [Router] or pass your own [BaseRouter] implementation.
*/
class Cicerone<T : BaseRouter> private constructor(val router: T) {
fun getNavigatorHolder(): NavigatorHolder = router.commandBuffer
companion object {
/**
* Creates the Cicerone instance with the default [Router]
*/
fun create() = create(Router())
/**
* Creates the Cicerone instance with the custom router.
* @param customRouter the custom router extending [BaseRouter]
*/
fun <T : BaseRouter> create(customRouter: T) = Cicerone(customRouter)
}
}
@@ -0,0 +1,29 @@
package com.github.terrakok.cicerone
/**
* Passes navigation command to an active [Navigator]
* or stores it in the pending commands queue to pass it later.
*/
internal class CommandBuffer : NavigatorHolder {
private var navigator: Navigator? = null
private val pendingCommands = mutableListOf<Array<out Command>>()
override fun setNavigator(navigator: Navigator) {
this.navigator = navigator
pendingCommands.forEach { navigator.applyCommands(it) }
pendingCommands.clear()
}
override fun removeNavigator() {
navigator = null
}
/**
* Passes `commands` to the [Navigator] if it available.
* Else puts it to the pending commands queue to pass it later.
* @param commands navigation command array
*/
fun executeCommands(commands: Array<out Command>) {
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
}
}
@@ -0,0 +1,14 @@
package com.github.terrakok.cicerone
/**
* The low-level navigation interface.
* Navigator is the one who actually performs any transition.
*/
interface Navigator {
/**
* Performs transition described by the navigation command
*
* @param commands the navigation command array to apply per single transaction
*/
fun applyCommands(commands: Array<out Command>)
}
@@ -0,0 +1,20 @@
package com.github.terrakok.cicerone
/**
* Navigator holder interface.
*
* Use it to connect a [Navigator] to the [Cicerone].
*/
interface NavigatorHolder {
/**
* Set an active Navigator for the Cicerone and start receive commands.
*
* @param navigator new active Navigator
*/
fun setNavigator(navigator: Navigator)
/**
* Remove the current Navigator and stop receive commands.
*/
fun removeNavigator()
}
@@ -0,0 +1,95 @@
package com.github.terrakok.cicerone
/**
* Router is the class for high-level navigation.
*
* Use it to perform needed transitions.
* This implementation covers almost all cases needed for the average app.
* Extend it if you need some tricky navigation.
*/
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, clearContainer: Boolean = true) {
executeCommands(Forward(screen, clearContainer))
}
/**
* Clear all screens and open new one as root.
*
* @param screen screen
*/
fun newRootScreen(screen: Screen) {
executeCommands(BackTo(null), Replace(screen))
}
/**
* Replace current screen.
*
* By replacing the screen, you alters the backstack,
* so by going fragmentBack you will return to the previous screen
* and not to the replaced one.
*
* @param screen screen
*/
fun replaceScreen(screen: Screen) {
executeCommands(Replace(screen))
}
/**
* Return fragmentBack to the needed screen from the chain.
*
* Behavior in the case when no needed screens found depends on
* the processing of the [BackTo] command in a [Navigator] implementation.
*
* @param screen screen
*/
fun backTo(screen: Screen?) {
executeCommands(BackTo(screen))
}
/**
* Opens several screens inside single transaction.
*
* @param screens
* @param showOnlyTopScreenView if FALSE then all screen views show together
*/
fun newChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
executeCommands(*commands.toTypedArray())
}
/**
* 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, showOnlyTopScreenView: Boolean = true) {
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
executeCommands(BackTo(null), *commands.toTypedArray())
}
/**
* Remove all screens from the chain and exit.
*
* It's mostly used to finish the application or close a supplementary navigation chain.
*/
fun finishChain() {
executeCommands(BackTo(null), Back())
}
/**
* Return to the previous screen in the chain.
*
* Behavior in the case when the current screen is the root depends on
* the processing of the [Back] command in a [Navigator] implementation.
*/
fun exit() {
executeCommands(Back())
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone
/**
* Screen is class for description application screen.
*/
abstract class Screen {
open val screenKey: String = this::class.qualifiedName!!
}
@@ -0,0 +1,215 @@
package com.github.terrakok.cicerone.androidx
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.
*
* Feature [BackTo] works only for fragments.
*
* Recommendation: most useful for Single-Activity application.
*/
open class AppNavigator constructor(
protected val activity: FragmentActivity,
protected val containerId: Int,
protected val fragmentManager: FragmentManager = activity.supportFragmentManager,
protected val fragmentFactory: FragmentFactory = FragmentFactory()
) : Navigator {
protected val localStackCopy = mutableListOf<TransactionInfo>()
override fun applyCommands(commands: Array<out Command>) {
fragmentManager.executePendingTransactions()
//copy stack before apply commands
copyStackToLocal()
for (command in commands) {
try {
applyCommand(command)
} catch (e: RuntimeException) {
errorOnApplyCommand(command, e)
}
}
}
private fun copyStackToLocal() {
localStackCopy.clear()
for (i in 0 until fragmentManager.backStackEntryCount) {
val str = fragmentManager.getBackStackEntryAt(i).name
localStackCopy.add(TransactionInfo.fromString(str))
}
}
/**
* Perform transition described by the navigation command
*
* @param command the navigation command to apply
*/
protected open fun applyCommand(command: Command) {
when (command) {
is Forward -> forward(command)
is Replace -> replace(command)
is BackTo -> backTo(command)
is Back -> back()
}
}
protected open fun forward(command: Forward) {
when (val screen = command.screen as AppScreen) {
is ActivityScreen -> {
checkAndStartActivity(screen)
}
is FragmentScreen -> {
val type = if (command.clearContainer) REPLACE else ADD
commitNewFragmentScreen(screen, type, true)
}
}
}
protected open fun replace(command: Replace) {
when (val screen = command.screen as AppScreen) {
is ActivityScreen -> {
checkAndStartActivity(screen)
activity.finish()
}
is FragmentScreen -> {
if (localStackCopy.isNotEmpty()) {
fragmentManager.popBackStack()
val removed = localStackCopy.removeAt(localStackCopy.lastIndex)
commitNewFragmentScreen(screen, removed.type, true)
} else {
commitNewFragmentScreen(screen, REPLACE, false)
}
}
}
}
protected open fun back() {
if (localStackCopy.isNotEmpty()) {
fragmentManager.popBackStack()
localStackCopy.removeAt(localStackCopy.lastIndex)
} else {
activityBack()
}
}
protected open fun activityBack() {
activity.finish()
}
protected open fun commitNewFragmentScreen(
screen: FragmentScreen,
type: TransactionInfo.Type,
addToBackStack: Boolean
) {
val fragment = screen.createFragment(fragmentFactory)
val transaction = fragmentManager.beginTransaction()
setupFragmentTransaction(
transaction,
fragmentManager.findFragmentById(containerId),
fragment
)
when (type) {
ADD -> transaction.add(containerId, fragment)
REPLACE -> transaction.replace(containerId, fragment)
}
if (addToBackStack) {
val transactionInfo = TransactionInfo(screen.screenKey, type)
transaction.addToBackStack(transactionInfo.toString())
localStackCopy.add(transactionInfo)
}
transaction.commit()
}
/**
* Performs [BackTo] command transition
*/
protected open fun backTo(command: BackTo) {
if (command.screen == null) {
backToRoot()
} else {
val screenKey = command.screen.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 as AppScreen)
}
}
}
private fun backToRoot() {
localStackCopy.clear()
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
/**
* Override this method to setup fragment transaction [FragmentTransaction].
* For example: setCustomAnimations(...), addSharedElement(...) or setReorderingAllowed(...)
*
* @param fragmentTransaction fragment transaction
* @param currentFragment current fragment in container
* (for [Replace] command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
*/
protected open fun setupFragmentTransaction(
fragmentTransaction: FragmentTransaction,
currentFragment: Fragment?,
nextFragment: Fragment?
) {
// Do nothing by default
}
private fun checkAndStartActivity(screen: ActivityScreen) {
// Check if we can start activity
val activityIntent = screen.createIntent(activity)
if (activityIntent.resolveActivity(activity.packageManager) != null) {
activity.startActivity(activityIntent, screen.startActivityOptions)
} else {
unexistingActivity(screen, activityIntent)
}
}
/**
* Called when there is no activity to open `screenKey`.
*
* @param screen screen
* @param activityIntent intent passed to start Activity for the `screenKey`
*/
protected open fun unexistingActivity(
screen: ActivityScreen,
activityIntent: Intent
) {
// Do nothing by default
}
/**
* Called when we tried to fragmentBack to some specific screen (via [BackTo] command),
* but didn't found it.
*
* @param screen screen
*/
protected open fun backToUnexisting(screen: AppScreen) {
backToRoot()
}
/**
* Override this method if you want to handle apply command error.
*
* @param command command
* @param error error
*/
protected open fun errorOnApplyCommand(
command: Command,
error: RuntimeException
) {
throw error
}
}
@@ -0,0 +1,22 @@
package com.github.terrakok.cicerone.androidx
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import com.github.terrakok.cicerone.Screen
sealed class AppScreen : Screen()
open class FragmentScreen(
override val screenKey: String,
val createFragment: (FragmentFactory) -> Fragment
) : AppScreen()
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
)
}
}
@@ -0,0 +1,34 @@
package com.github.terrakok.cicerone
/**
* Navigation command describes screens transition.
*
* That can be processed by [com.github.terrakok.cicerone.Navigator]
*/
interface Command
/**
* Opens new screen.
*/
data class Forward(
val screen: Screen,
val clearContainer: Boolean
) : Command
/**
* Replaces the current screen.
*/
data class Replace(val screen: Screen) : Command
/**
* Rolls fragmentBack the last transition from the screens chain.
*/
class Back : Command
/**
* Rolls fragmentBack to the needed screen from the screens chain.
*
* Behavior in the case when no needed screens found depends on an implementation of the [com.github.terrakok.cicerone.Navigator]
* But the recommended behavior is to return to the root.
*/
data class BackTo(val screen: Screen?) : Command
+4
View File
@@ -2,3 +2,7 @@ apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
compileOnly 'com.google.android:android:4.1.1.4'
}
@@ -1,26 +0,0 @@
package android.app;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
/***
* Created by laomo on 2016-11-21.
*/
public class Activity extends Context {
public void startActivity(Intent intent) {
throw new RuntimeException("Stub!");
}
public void startActivity(Intent intent, Bundle options) {
throw new RuntimeException("Stub!");
}
public FragmentManager getFragmentManager() {
throw new RuntimeException("Stub!");
}
public void finish() {
throw new RuntimeException("Stub!");
}
}
@@ -1,36 +0,0 @@
package android.app;
import android.os.Bundle;
/***
* Created by laomo on 2016-11-21.
*/
public class Fragment {
public void onCreate(Bundle savedInstanceState) {
throw new RuntimeException("Stub!");
}
public void onStart() {
throw new RuntimeException("Stub!");
}
public void onDestroyView() {
throw new RuntimeException("Stub!");
}
public void onDestroy() {
throw new RuntimeException("Stub!");
}
public void onSaveInstanceState(Bundle outState) {
throw new RuntimeException("Stub!");
}
final public boolean isRemoving() {
throw new RuntimeException("Stub!");
}
public final Activity getActivity() {
throw new RuntimeException("Stub!");
}
}
@@ -1,46 +0,0 @@
package android.app;
/***
* Created by laomo on 2016-11-21.
*/
public class FragmentManager {
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
public FragmentTransaction beginTransaction() {
throw new RuntimeException("Stub!");
}
public boolean executePendingTransactions() {
throw new RuntimeException("Stub!");
}
public void popBackStack() {
throw new RuntimeException("Stub!");
}
public boolean popBackStackImmediate() {
throw new RuntimeException("Stub!");
}
public boolean popBackStackImmediate(String name, int flags) {
throw new RuntimeException("Stub!");
}
public int getBackStackEntryCount() {
throw new RuntimeException("Stub!");
}
public BackStackEntry getBackStackEntryAt(int index) {
throw new RuntimeException("Stub!");
}
public Fragment findFragmentById(int id) {
throw new RuntimeException("Stub!");
}
public interface BackStackEntry {
int getId();
String getName();
}
}
@@ -1,18 +0,0 @@
package android.app;
/***
* Created by laomo on 2016-11-21.
*/
public class FragmentTransaction {
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction addToBackStack(String name) {
throw new RuntimeException("Stub!");
}
public int commit() {
throw new RuntimeException("Stub!");
}
}
@@ -1,8 +0,0 @@
package android.content;
/**
* @author Konstantin Tskhovrebov (aka terrakok). Date: 10.03.17
*/
public class Context {
}
@@ -1,9 +0,0 @@
package android.content;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 22.02.17
*/
public class Intent {
}
@@ -1,7 +0,0 @@
package android.os;
/***
* Created by laomo on 2016-11-21.
*/
public class Bundle {
}
@@ -1,20 +0,0 @@
package android.support.v4.app;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentTransaction {
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction addToBackStack(String name) {
throw new RuntimeException("Stub!");
}
public int commit() {
throw new RuntimeException("Stub!");
}
}
@@ -1,20 +0,0 @@
package android.widget;
import android.content.Context;
/**
* @author Konstantin Tskhovrebov (aka terrakok). Date: 10.03.17
*/
public class Toast {
public static final int LENGTH_SHORT = 0;
public void show() {
throw new RuntimeException("Stub!");
}
public static Toast makeText(Context context, CharSequence text, int duration) {
throw new RuntimeException("Stub!");
}
}
@@ -1,9 +1,10 @@
package android.support.v4.app;
package androidx.fragment.app;
import android.os.Bundle;
import androidx.fragment.app.FragmentActivity;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@@ -1,9 +1,9 @@
package android.support.v4.app;
package androidx.fragment.app;
import android.app.Activity;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@@ -0,0 +1,9 @@
package androidx.fragment.app;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentFactory {
}
@@ -1,7 +1,7 @@
package android.support.v4.app;
package androidx.fragment.app;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@@ -20,11 +20,7 @@ public class FragmentManager {
throw new RuntimeException("Stub!");
}
public boolean popBackStackImmediate() {
throw new RuntimeException("Stub!");
}
public boolean popBackStackImmediate(String name, int flags) {
public void popBackStack(String name, int flags) {
throw new RuntimeException("Stub!");
}
@@ -0,0 +1,40 @@
package androidx.fragment.app;
import android.os.Bundle;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentTransaction {
public FragmentTransaction add(int containerViewId, Fragment fragment) {
throw new RuntimeException("Stub!");
}
public final FragmentTransaction add(
int containerViewId,
Class<? extends Fragment> fragmentClass,
Bundle args) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
throw new RuntimeException("Stub!");
}
public final FragmentTransaction replace(
int containerViewId,
Class<? extends Fragment> fragmentClass,
Bundle args) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction addToBackStack(String name) {
throw new RuntimeException("Stub!");
}
public int commit() {
throw new RuntimeException("Stub!");
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 754 KiB

+1
View File
@@ -0,0 +1 @@
<mxfile userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:62.0) Gecko/20100101 Firefox/62.0" version="9.1.8" editor="www.draw.io" type="device"><diagram id="7e88c67f-9661-f316-02ff-f99b9ebf4599" name="Page-1">7Vpbc6M2FP41nu4+JMPFYPsxdpLtw26bNjvt7lNHBgHqYkSF8GV/fY9A4iZwnBon0zbxTEY+Erp859O54Ym92uw/MJRGn6iP44ll+PuJfTuxrNnChP9CcCgF07lTCkJG/FJk1oJH8h1LoSGlOfFx1hrIKY05SdtCjyYJ9nhLhhiju/awgMbtVVMUYk3w6KFYl/5OfB6VUsswjLrjR0zCiHd71sj7FjKaJ3LBiWUHxV/ZvUFqMjk+i5BPdw2RfTexV4xSXrY2+xWOBbYKt/K5+4HeauMMJ/yUByy3fGKL4hyrLRcb4weFhjhPKodhxvG+TwdorYYb+h7M6mTAGEw3mLMDDNm3tS7J4kiu7GrkFwreqAH61JZCJLUdVjPXB4aGPPPA+Z2nz1+oE4sHjIm93EWE48cUeaJ3B/QHWcQ3sMCtCc2MM/oNr2hMWfG0bRR/VY/ikxgbkDhujJREATlNeN8MgzpoYl1qVAf7ZeDsQdONYYWlT7bQDEXzgeEMtouZ6oNZG93n4d/B1Hfw3J/26WVurW3XlWg/ytXMsyBW5q/NZ1vdiIYGTKtPAyMowNYU8CvNC6jPArUFkQ4ywvPA6wPZ9eZ4HZxL3X5cLeMFcTVNDUHsg8+QXynjEQ1pguK7WrpsY9zAE+8J/yLE14789lUOAizYodElvn6VT/2JOT9IZ4lyTkFUr/uR0lSOK3cqtvcE0nAcmjMPt+4uRyzEvMEmXR8Mx4iTbXv6c9Cdaqxd0c0GJf4yD4LXJ29EGfkOsyA1peAxgXDhJiZhAjIusB+F47bTJrk57SG520PySniOHuaXILnZpHjN6i9Nxtd9Fya5rZN8+kIk1yMNSXLlBtdMucBfcgxjurqAWDEVTe8QE4CcPU39dambj+tKUIWnP+ccZhm4E9gEvznruxMLd2YjdySyT40O2Rc62fu4Ph+B6nrc+xPakhBx+urmZgxo3dd0lov/vBmZ6mZkQB+jm5GZRtx7hsINbPcTSiAvZbo5EZQ2VsBLvOcXJTc4a9frJbc/W6zPz2EGyP2iEbbKVi8ZCZpHIkHj8ux2dXbPXojdpp5B3niwAuEwzb1iOjR/I3j3oS5MNLTxjAQxocIDLn2URYVy6gRepelWH5lXU/EZjAM76Xs1egzqO4un48PKPzapb40RH5p6oK6sjTh0UQ1S53b/ymmZztvGvfg0RSrTl5ZKCZLSBePP9F3mMYyT940Swbr7GMjKVQfqBoW5O1KckdrvIQSS+vTKMoWu6A3x/eJi95Gtfdlb5LokNUy74/JnJ1pFawxm9NQRnaVXBrjZxLn9F2qnqruNXiGynKejs+mlNKUHEG+aGra37itqajFobSNTWT1V3qtMYt2lhKkSQAgo0MjEXmGrVzHeFu9tpNklNBEWHMWxGAFRBbAAC6NOA9FMsUdQXNh4yRUt0GxsI72AWc6AHyQJQeDU3z4X0c6VNRS9UjhyEBfvWCJgG07+GbPGIJPttLjUZ58XF0p3FR+PcqlTdDuFUo+QL+OsRQqDBDWnitTDpzhLfuBFvIh8AUhQyFu8exeTAF95By/G76/fqPVcanVeopk9YaHpXIhbZg+3OprxcratgMCJfyPe0grPEKMsI56eZQ4DdUI6c/Ttl9NzxZTs5HxGrvBASRH2Km8xbWth1kG3TLLkQzXA+jz2QJSvJipTM20iQBUdGsNSMSA7sl+3y5rW22JolDPWNKggPY0Zejb3P2VGV6HdbOxUZnQIVt3zkYnRtSbzkXmhv6bUvVGjHnvEE4GpTuqZVYYJPimP+R9rum9mnQZHa1E537fGXpmtMTEq3EAtA7M+mLmWqytXiMrYCu+xl0uvVoZOZJMC3buBVu0xi19YwCWAcyRF3LVG3Itwdt04enOpQfGbP1QlQqcda/WVSS7mD/uqJJeweo0SoSjNtIrg5sJSggfMCBxCJGC3zzKW7RJgWZp7LQPqTvtd1fN9a7eCppgytm/trmOdZUPha/2rsHJ4/dM7++5v</diagram></mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

+33 -17
View File
@@ -1,19 +1,20 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
repositories {
jcenter()
}
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 25
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0.0"
applicationId "ru.terrakok.cicerone.sample"
applicationId "com.github.terrakok.cicerone.sample"
}
buildTypes {
@@ -25,33 +26,48 @@ android {
debug {
}
}
buildFeatures {
viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
ext {
supportLibraryVersion = "25.3.0"
moxyVersion = "1.4.6"
daggerVersion = "2.10"
androidXVersion = "1.2.0"
materialVersion = "1.2.1"
moxyVersion = "2.2.0"
daggerVersion = "2.29.1"
}
dependencies {
// Support libraries
compile "com.android.support:appcompat-v7:$supportLibraryVersion"
compile "com.android.support:design:$supportLibraryVersion"
implementation "androidx.appcompat:appcompat:$androidXVersion"
implementation "com.google.android.material:material:$materialVersion"
//MVP Moxy
compile "com.arello-mobile:moxy:$moxyVersion"
compile "com.arello-mobile:moxy-app-compat:$moxyVersion"
provided "com.arello-mobile:moxy-compiler:$moxyVersion"
implementation "com.github.moxy-community:moxy:$moxyVersion"
implementation "com.github.moxy-community:moxy-androidx:$moxyVersion"
kapt "com.github.moxy-community:moxy-compiler:$moxyVersion"
//Cicerone
compile project(':library')
implementation project(':library')
//DI
compile "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
//Bottom Navigation Bar
compile ('com.ashokvarma.android:bottom-navigation-bar:1.3.0') {
implementation ('com.ashokvarma.android:bottom-navigation-bar:1.3.0') {
exclude group: "com.android.support", module: "design"
}
implementation "androidx.core:core-ktx:1.3.2"
}
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="ru.terrakok.cicerone.sample"
package="com.github.terrakok.cicerone.sample"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
@@ -0,0 +1,25 @@
package com.github.terrakok.cicerone.sample
import android.app.Application
import com.github.terrakok.cicerone.sample.dagger.AppComponent
import com.github.terrakok.cicerone.sample.dagger.DaggerAppComponent
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class SampleApplication : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent.builder().build()
}
override fun onCreate() {
super.onCreate()
INSTANCE = this
}
companion object {
lateinit var INSTANCE: SampleApplication
}
}
@@ -0,0 +1,62 @@
package com.github.terrakok.cicerone.sample
import android.content.Intent
import android.net.Uri
import com.github.terrakok.cicerone.androidx.ActivityScreen
import com.github.terrakok.cicerone.androidx.FragmentScreen
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
import com.github.terrakok.cicerone.sample.ui.bottom.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.start.StartActivity
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
object Screens {
fun sampleScreen(number: Int) = FragmentScreen("SampleScreen_$number") {
SampleFragment.getNewInstance(number)
}
fun startScreen() = ActivityScreen("StartScreen") {
Intent(it, StartActivity::class.java)
}
fun mainScreen() = ActivityScreen("MainScreen") {
Intent(it, MainActivity::class.java)
}
fun bottomNavigationScreen() = ActivityScreen("BottomNavigationScreen") {
Intent(it, BottomNavigationActivity::class.java)
}
fun tabScreen(tabName: String) = FragmentScreen("FragmentScreen") {
TabContainerFragment.getNewInstance(tabName)
}
fun forwardScreen(containerName: String, number: Int) = FragmentScreen("ForwardScreen") {
ForwardFragment.getNewInstance(containerName, number)
}
fun githubScreen() = ActivityScreen("GithubScreen") {
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"))
}
fun profileScreen() = ActivityScreen("ProfileScreen") {
Intent(it, ProfileActivity::class.java)
}
fun profileInfoScreen() = FragmentScreen("ProfileInfoScreen") {
ProfileFragment()
}
fun selectPhotoScreen() = FragmentScreen("SelectPhotoScreen") {
SelectPhotoFragment()
}
}
@@ -0,0 +1,42 @@
package com.github.terrakok.cicerone.sample.dagger
import com.github.terrakok.cicerone.sample.dagger.module.LocalNavigationModule
import com.github.terrakok.cicerone.sample.dagger.module.NavigationModule
import com.github.terrakok.cicerone.sample.dagger.module.PhotoSelectionModule
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
import dagger.Component
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Singleton
@Component(modules = [
NavigationModule::class,
LocalNavigationModule::class,
PhotoSelectionModule::class]
)
interface AppComponent {
fun inject(activity: StartActivity)
fun inject(activity: MainActivity)
fun inject(fragment: SampleFragment)
fun inject(activity: BottomNavigationActivity)
fun inject(fragment: TabContainerFragment)
fun inject(fragment: ProfileFragment)
fun inject(fragment: SelectPhotoFragment)
fun inject(activity: ProfileActivity)
}
@@ -0,0 +1,17 @@
package com.github.terrakok.cicerone.sample.dagger.module
import com.github.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
object LocalNavigationModule {
@Provides
@Singleton
fun provideLocalNavigationHolder(): LocalCiceroneHolder = LocalCiceroneHolder()
}
@@ -0,0 +1,29 @@
package com.github.terrakok.cicerone.sample.dagger.module
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 dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
class NavigationModule {
private val cicerone: Cicerone<Router> = create()
@Provides
@Singleton
fun provideRouter(): Router {
return cicerone.router
}
@Provides
@Singleton
fun provideNavigatorHolder(): NavigatorHolder {
return cicerone.getNavigatorHolder()
}
}
@@ -0,0 +1,20 @@
package com.github.terrakok.cicerone.sample.dagger.module
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
object PhotoSelectionModule {
private val photoSelection = PhotoSelection(R.drawable.ava_1)
@Provides
@Singleton
fun providePhotoSelection() = photoSelection
}
@@ -0,0 +1,26 @@
package com.github.terrakok.cicerone.sample.mvp.animation
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 22.09.18.
*/
class PhotoSelection(private var selectedPhoto: Int) {
private var listener: Listener? = null
fun getSelectedPhoto(): Int {
return selectedPhoto
}
fun setSelectedPhoto(selectedPhoto: Int) {
this.selectedPhoto = selectedPhoto
listener?.onChange(selectedPhoto)
}
fun setListener(listener: Listener?) {
this.listener = listener
}
interface Listener {
fun onChange(selectedPhoto: Int)
}
}
@@ -0,0 +1,36 @@
package com.github.terrakok.cicerone.sample.mvp.animation.photos
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class SelectPhotoPresenter(
private val photoSelection: PhotoSelection,
private val router: Router
) : MvpPresenter<SelectPhotoView>() {
override fun onFirstViewAttach() {
super.onFirstViewAttach()
viewState!!.showPhotos(intArrayOf(
R.drawable.ava_1,
R.drawable.ava_2,
R.drawable.ava_3,
R.drawable.ava_4
))
}
fun onPhotoClick(photoRes: Int) {
photoSelection.setSelectedPhoto(photoRes)
router.exit()
}
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.animation.photos
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface SelectPhotoView : MvpView {
fun showPhotos(resourceIds: IntArray)
}
@@ -0,0 +1,47 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.selectPhotoScreen
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class ProfilePresenter(
private val photoSelection: PhotoSelection,
private val router: Router
) : MvpPresenter<ProfileView>() {
override fun onFirstViewAttach() {
super.onFirstViewAttach()
updatePhoto()
}
override fun onDestroy() {
photoSelection.setListener(null)
super.onDestroy()
}
private fun updatePhoto() {
viewState!!.showPhoto(photoSelection.getSelectedPhoto())
}
fun onPhotoClicked() {
router.navigateTo(selectPhotoScreen())
}
fun onBackPressed() {
router.exit()
}
init {
photoSelection.setListener(object : PhotoSelection.Listener {
override fun onChange(selectedPhoto: Int) {
updatePhoto()
}
})
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface ProfileView : MvpView {
fun showPhoto(resId: Int)
}
@@ -0,0 +1,18 @@
package com.github.terrakok.cicerone.sample.mvp.bottom
import com.github.terrakok.cicerone.Router
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by terrakok 25.11.16
*/
@InjectViewState
class BottomNavigationPresenter(
private val router: Router
) : MvpPresenter<BottomNavigationView>() {
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,11 @@
package com.github.terrakok.cicerone.sample.mvp.bottom
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by terrakok 25.11.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface BottomNavigationView : MvpView
@@ -0,0 +1,42 @@
package com.github.terrakok.cicerone.sample.mvp.bottom.forward
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.forwardScreen
import com.github.terrakok.cicerone.sample.Screens.githubScreen
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by terrakok 26.11.16
*/
@InjectViewState
class ForwardPresenter(
private val container: String,
private val router: Router,
private val number: Int
) : MvpPresenter<ForwardView>() {
private fun createChain(number: Int): String {
var chain = "[0]"
for (i in 0 until number) {
chain += "" + (i + 1)
}
return chain
}
fun onForwardPressed() {
router.navigateTo(forwardScreen(container, number + 1))
}
fun onGithubPressed() {
router.navigateTo(githubScreen())
}
fun onBackPressed() {
router.exit()
}
init {
viewState?.setChainText(createChain(number))
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.bottom.forward
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by terrakok 26.11.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface ForwardView : MvpView {
fun setChainText(chainText: String)
}
@@ -0,0 +1,71 @@
package com.github.terrakok.cicerone.sample.mvp.main
import android.os.Handler
import android.os.Looper
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.sampleScreen
import moxy.InjectViewState
import moxy.MvpPresenter
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@InjectViewState
class SamplePresenter(
private val router: Router,
private val screenNumber: Int
) : MvpPresenter<SampleView>() {
private val executorService = Executors.newSingleThreadScheduledExecutor()
private var future: ScheduledFuture<*>? = null
fun onBackCommandClick() {
router.exit()
}
fun onForwardCommandClick() {
router.navigateTo(sampleScreen(screenNumber + 1))
}
fun onReplaceCommandClick() {
router.replaceScreen(sampleScreen(screenNumber + 1))
}
fun onNewChainCommandClick() {
router.newChain(
sampleScreen(screenNumber + 1),
sampleScreen(screenNumber + 2),
sampleScreen(screenNumber + 3)
)
}
fun onFinishChainCommandClick() {
router.finishChain()
}
fun onNewRootCommandClick() {
router.newRootScreen(sampleScreen(screenNumber + 1))
}
fun onForwardWithDelayCommandClick() {
future?.cancel(true)
future = executorService.schedule({ //WARNING! Navigation must be only in UI thread.
Handler(Looper.getMainLooper()).post {
router.navigateTo(sampleScreen(screenNumber + 1))
}
}, 5, TimeUnit.SECONDS)
}
fun onBackToCommandClick() {
router.backTo(sampleScreen(3))
}
init {
viewState?.setTitle("Screen $screenNumber")
}
}
@@ -0,0 +1,14 @@
package com.github.terrakok.cicerone.sample.mvp.main
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface SampleView : MvpView {
fun setTitle(title: String)
}
@@ -0,0 +1,30 @@
package com.github.terrakok.cicerone.sample.mvp.start
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.bottomNavigationScreen
import com.github.terrakok.cicerone.sample.Screens.mainScreen
import com.github.terrakok.cicerone.sample.Screens.profileScreen
import moxy.MvpPresenter
/**
* Created by terrakok 21.11.16
*/
class StartActivityPresenter(private val router: Router) : MvpPresenter<StartActivityView>() {
fun onOrdinaryPressed() {
router.navigateTo(mainScreen())
}
fun onMultiPressed() {
router.navigateTo(bottomNavigationScreen())
}
fun onResultWithAnimationPressed() {
router.navigateTo(profileScreen())
}
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone.sample.mvp.start
import moxy.MvpView
/**
* Created by terrakok 21.11.16
*/
interface StartActivityView : MvpView
@@ -0,0 +1,18 @@
package com.github.terrakok.cicerone.sample.subnavigation
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.Cicerone.Companion.create
import com.github.terrakok.cicerone.Router
import java.util.*
/**
* Created by terrakok 27.11.16
*/
class LocalCiceroneHolder {
private val containers = HashMap<String, Cicerone<Router>>()
fun getCicerone(containerTag: String): Cicerone<Router> =
containers.getOrPut(containerTag) {
create()
}
}
@@ -0,0 +1,89 @@
package com.github.terrakok.cicerone.sample.ui.animations
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.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.profileInfoScreen
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
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class ProfileActivity : AppCompatActivity() {
@Inject
lateinit var navigatorHolder: NavigatorHolder
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_container)
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(profileInfoScreen())))
}
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
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) {
setupSharedElementForProfileToSelectPhoto(
currentFragment,
nextFragment,
fragmentTransaction
)
}
}
}
private fun setupSharedElementForProfileToSelectPhoto(profileFragment: ProfileFragment,
selectPhotoFragment: SelectPhotoFragment,
fragmentTransaction: FragmentTransaction) {
val changeBounds = ChangeBounds()
selectPhotoFragment.sharedElementEnterTransition = changeBounds
selectPhotoFragment.sharedElementReturnTransition = changeBounds
profileFragment.sharedElementEnterTransition = changeBounds
profileFragment.sharedElementReturnTransition = changeBounds
val view = profileFragment.avatarViewForAnimation
fragmentTransaction.addSharedElement(view!!, PHOTO_TRANSITION)
selectPhotoFragment.setAnimationDestinationId((view.tag as Int))
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentById(R.id.container)
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
super.onBackPressed()
}
}
companion object {
const val PHOTO_TRANSITION = "photo_trasition"
}
}
@@ -0,0 +1,104 @@
package com.github.terrakok.cicerone.sample.ui.animations.photos
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentSelectPhotoBinding
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoPresenter
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class SelectPhotoFragment : MvpAppCompatFragment(), SelectPhotoView, BackButtonListener {
private lateinit var binding: FragmentSelectPhotoBinding
private lateinit var allImages: Array<ImageView>
@Inject
lateinit var router: Router
@Inject
lateinit var photoSelection: PhotoSelection
@InjectPresenter
lateinit var presenter: SelectPhotoPresenter
fun setAnimationDestinationId(resId: Int) {
var arguments = arguments
if (arguments == null) arguments = Bundle()
arguments.putInt(ARG_ANIM_DESTINATION, resId)
setArguments(arguments)
}
private val animationDestionationId: Int
get() = arguments!!.getInt(ARG_ANIM_DESTINATION)
@ProvidePresenter
fun providePresenter() = SelectPhotoPresenter(photoSelection, router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSelectPhotoBinding.inflate(inflater, container, false)
allImages = arrayOf(
binding.selectImage1,
binding.selectImage2,
binding.selectImage3,
binding.selectImage4
)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
allImages.forEach {
it.setOnClickListener(clickListener)
}
}
private val clickListener = View.OnClickListener { v ->
allImages.forEach {
it.transitionName = null
}
v.transitionName = ProfileActivity.PHOTO_TRANSITION
presenter.onPhotoClick((v.tag as Int))
}
override fun showPhotos(resourceIds: IntArray) {
if (resourceIds.size >= 4) {
//for shared element animation
val animRes = animationDestionationId
allImages.forEachIndexed {
index, imageView ->
imageView.setImageResource(resourceIds[index])
imageView.tag = resourceIds[index]
imageView.transitionName = if (animRes == resourceIds[index]) ProfileActivity.PHOTO_TRANSITION else null
}
}
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val ARG_ANIM_DESTINATION = "arg_anim_dest"
}
}
@@ -0,0 +1,74 @@
package com.github.terrakok.cicerone.sample.ui.animations.profile
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfilePresenter
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfileView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class ProfileFragment : MvpAppCompatFragment(), ProfileView, BackButtonListener {
private lateinit var avatar: ImageView
@Inject
lateinit var router: Router
@InjectPresenter
lateinit var presenter: ProfilePresenter
@Inject
lateinit var photoSelection: PhotoSelection
@ProvidePresenter
fun providePresenter() = ProfilePresenter(photoSelection, router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_profile, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
avatar = view.findViewById<View>(R.id.avatar_imageView) as ImageView
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
avatar.transitionName = ProfileActivity.PHOTO_TRANSITION
avatar.setOnClickListener { presenter.onPhotoClicked() }
}
override fun showPhoto(resId: Int) {
avatar.setImageResource(resId)
//for shared element animation
avatar.tag = resId
}
val avatarViewForAnimation: View?
get() = avatar
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
}
@@ -0,0 +1,119 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.tabScreen
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import moxy.MvpAppCompatActivity
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by terrakok 25.11.16
*/
class BottomNavigationActivity : MvpAppCompatActivity(), BottomNavigationView, RouterProvider {
private lateinit var bottomNavigationBar: BottomNavigationBar
@Inject
override lateinit var router: Router
@InjectPresenter
lateinit var presenter: BottomNavigationPresenter
@ProvidePresenter
fun createBottomNavigationPresenter() = BottomNavigationPresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bottom)
bottomNavigationBar = findViewById<View>(R.id.ab_bottom_navigation_bar) as BottomNavigationBar
initViews()
if (savedInstanceState == null) {
bottomNavigationBar.selectTab(0, true)
}
}
private fun initViews() {
bottomNavigationBar
.addItem(BottomNavigationItem(R.drawable.ic_android_white_24dp, R.string.tab_android))
.addItem(BottomNavigationItem(R.drawable.ic_bug_report_white_24dp, R.string.tab_bug))
.addItem(BottomNavigationItem(R.drawable.ic_pets_white_24dp, R.string.tab_dog))
.initialise()
bottomNavigationBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabSelected(position: Int) {
when (position) {
0 -> selectTab("ANDROID")
1 -> selectTab("BUG")
2 -> selectTab("DOG")
}
bottomNavigationBar.selectTab(position, false)
}
override fun onTabUnselected(position: Int) {}
override fun onTabReselected(position: Int) {
onTabSelected(position)
}
})
}
private fun selectTab(tab: String) {
val fm = supportFragmentManager
var currentFragment: Fragment? = null
val fragments = fm.fragments
for (f in fragments) {
if (f.isVisible) {
currentFragment = f
break
}
}
val newFragment = fm.findFragmentByTag(tab)
if (currentFragment != null && newFragment != null && currentFragment === newFragment) return
val transaction = fm.beginTransaction()
if (newFragment == null) {
transaction.add(
R.id.ab_container,
tabScreen(tab).createFragment.invoke(fm.fragmentFactory), tab
)
}
if (currentFragment != null) {
transaction.hide(currentFragment)
}
if (newFragment != null) {
transaction.show(newFragment)
}
transaction.commitNow()
}
override fun onBackPressed() {
val fm = supportFragmentManager
var fragment: Fragment? = null
val fragments = fm.fragments
for (f in fragments) {
if (f.isVisible) {
fragment = f
break
}
}
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
presenter.onBackPressed()
}
}
}
@@ -0,0 +1,69 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.terrakok.cicerone.sample.databinding.FragmentForwardBinding
import com.github.terrakok.cicerone.sample.mvp.bottom.forward.ForwardPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.forward.ForwardView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
/**
* Created by terrakok 26.11.16
*/
class ForwardFragment : MvpAppCompatFragment(), ForwardView, BackButtonListener {
private lateinit var binding: FragmentForwardBinding
@InjectPresenter
lateinit var presenter: ForwardPresenter
@ProvidePresenter
fun provideForwardPresenter(): ForwardPresenter {
return ForwardPresenter(
arguments!!.getString(EXTRA_NAME),
(parentFragment as RouterProvider).router,
arguments!!.getInt(EXTRA_NUMBER)
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentForwardBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.toolbar.title = arguments!!.getString(EXTRA_NAME)
binding.toolbar.setNavigationOnClickListener { presenter.onBackPressed() }
binding.forwardButton.setOnClickListener { presenter.onForwardPressed() }
binding.githubButton.setOnClickListener { presenter.onGithubPressed() }
}
override fun setChainText(chainText: String) {
binding.chainText.text = chainText
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val EXTRA_NAME = "extra_name"
private const val EXTRA_NUMBER = "extra_number"
fun getNewInstance(name: String?, number: Int): ForwardFragment {
return ForwardFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NAME, name)
putInt(EXTRA_NUMBER, number)
}
}
}
}
}
@@ -0,0 +1,88 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.forwardScreen
import com.github.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import javax.inject.Inject
/**
* Created by terrakok 25.11.16
*/
class TabContainerFragment : Fragment(), RouterProvider, BackButtonListener {
private val navigator: Navigator by lazy {
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager, childFragmentManager.fragmentFactory)
}
@Inject
lateinit var ciceroneHolder: LocalCiceroneHolder
private val containerName: String
get() = arguments!!.getString(EXTRA_NAME)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
private val cicerone: Cicerone<Router>
get() = ciceroneHolder.getCicerone(containerName)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_tab_container, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (childFragmentManager.findFragmentById(R.id.ftc_container) == null) {
cicerone.router.replaceScreen(forwardScreen(containerName, 0))
}
}
override fun onResume() {
super.onResume()
cicerone.getNavigatorHolder().setNavigator(navigator)
}
override fun onPause() {
cicerone.getNavigatorHolder().removeNavigator()
super.onPause()
}
override val router: Router
get() = cicerone.router
override fun onBackPressed(): Boolean {
val fragment = childFragmentManager.findFragmentById(R.id.ftc_container)
return if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
true
} else {
(activity as RouterProvider?)!!.router.exit()
true
}
}
companion object {
private const val EXTRA_NAME = "tcf_extra_name"
fun getNewInstance(name: String?) =
TabContainerFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NAME, name)
}
}
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone.sample.ui.common
/**
* Created by terrakok 26.11.16
*/
interface BackButtonListener {
fun onBackPressed(): Boolean
}
@@ -0,0 +1,10 @@
package com.github.terrakok.cicerone.sample.ui.common
import com.github.terrakok.cicerone.Router
/**
* Created by terrakok 25.11.16
*/
interface RouterProvider {
val router: Router
}
@@ -0,0 +1,34 @@
package com.github.terrakok.cicerone.sample.ui.main
import android.content.Context
import androidx.fragment.app.Fragment
import moxy.MvpAppCompatFragment
import java.lang.ref.WeakReference
abstract class BaseFragment : MvpAppCompatFragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
val activity = activity
if (activity is ChainHolder) {
(activity as ChainHolder).chain.add(WeakReference<Fragment>(this))
}
}
override fun onDetach() {
val activity = activity
if (activity is ChainHolder) {
val chain = (activity as ChainHolder).chain
val it = chain.iterator()
while (it.hasNext()) {
val fragmentReference = it.next()
val fragment = fragmentReference.get()
if (fragment != null && fragment === this) {
it.remove()
break
}
}
}
super.onDetach()
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone.sample.ui.main
import androidx.fragment.app.Fragment
import java.lang.ref.WeakReference
interface ChainHolder {
val chain: MutableList<WeakReference<Fragment>>
}
@@ -0,0 +1,95 @@
package com.github.terrakok.cicerone.sample.ui.main
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.fragment.app.Fragment
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.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.sampleScreen
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatActivity
import java.lang.ref.WeakReference
import java.util.*
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class MainActivity : MvpAppCompatActivity(), ChainHolder {
private lateinit var screensSchemeTV: TextView
override val chain = ArrayList<WeakReference<Fragment>>()
@Inject
lateinit var navigatorHolder: NavigatorHolder
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
override fun applyCommands(commands: Array<out Command>) {
super.applyCommands(commands)
supportFragmentManager.executePendingTransactions()
printScreensScheme()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
screensSchemeTV = findViewById<View>(R.id.screens_scheme) as TextView
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(sampleScreen(1))))
} else {
printScreensScheme()
}
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentById(R.id.main_container)
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
super.onBackPressed()
}
}
private fun printScreensScheme() {
val fragments = ArrayList<SampleFragment>()
for (fragmentReference in chain) {
val fragment = fragmentReference.get()
if (fragment != null && fragment is SampleFragment) {
fragments.add(fragment)
}
}
fragments.sortWith { f1, f2 ->
val t = f1.creationTime - f2.creationTime
if (t > 0) 1 else if (t < 0) -1 else 0
}
val keys = ArrayList<Int>()
for (fragment in fragments) {
keys.add(fragment.number)
}
screensSchemeTV.text = "Chain: $keys"
}
}
@@ -0,0 +1,87 @@
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 com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentSampleBinding
import com.github.terrakok.cicerone.sample.mvp.main.SamplePresenter
import com.github.terrakok.cicerone.sample.mvp.main.SampleView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class SampleFragment : BaseFragment(), SampleView, BackButtonListener {
lateinit var binding: FragmentSampleBinding
@Inject
lateinit var router: Router
@InjectPresenter
lateinit var presenter: SamplePresenter
@ProvidePresenter
fun createSamplePresenter() = SamplePresenter(router, arguments!!.getInt(EXTRA_NUMBER))
val number: Int
get() = arguments!!.getInt(EXTRA_NUMBER)
val creationTime: Long
get() = arguments!!.getLong(EXTRA_TIME, 0L)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSampleBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.toolbar.setNavigationOnClickListener { presenter.onBackCommandClick() }
binding.backCommand.setOnClickListener { presenter.onBackCommandClick() }
binding.forwardCommand.setOnClickListener { presenter.onForwardCommandClick() }
binding.replaceCommand.setOnClickListener { presenter.onReplaceCommandClick() }
binding.newChainCommand.setOnClickListener { presenter.onNewChainCommandClick() }
binding.newRootCommand.setOnClickListener { presenter.onNewRootCommandClick() }
binding.forwardDelayCommand.setOnClickListener { presenter.onForwardWithDelayCommandClick() }
binding.backToCommand.setOnClickListener { presenter.onBackToCommandClick() }
binding.finishChainCommand.setOnClickListener { presenter.onFinishChainCommandClick() }
}
override fun setTitle(title: String) {
binding.toolbar.title = title
}
override fun onBackPressed(): Boolean {
presenter.onBackCommandClick()
return true
}
companion object {
private const val EXTRA_NUMBER = "extra_number"
private const val EXTRA_TIME = "extra_time"
fun getNewInstance(number: Int): SampleFragment {
return SampleFragment().apply {
arguments = Bundle().apply {
putInt(EXTRA_NUMBER, number)
putLong(EXTRA_TIME, System.currentTimeMillis())
}
}
}
}
}
@@ -0,0 +1,70 @@
package com.github.terrakok.cicerone.sample.ui.start
import android.os.Bundle
import android.view.View
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.mvp.start.StartActivityPresenter
import com.github.terrakok.cicerone.sample.mvp.start.StartActivityView
import moxy.MvpAppCompatActivity
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by terrakok 21.11.16
*/
class StartActivity : MvpAppCompatActivity(), StartActivityView {
@Inject
lateinit var router: Router
@Inject
lateinit var navigatorHolder: NavigatorHolder
@InjectPresenter
lateinit var presenter: StartActivityPresenter
private val navigator: Navigator = AppNavigator(this, -1, supportFragmentManager, supportFragmentManager.fragmentFactory)
@ProvidePresenter
fun createStartActivityPresenter() = StartActivityPresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_start)
initViews()
}
private fun initViews() {
findViewById<View>(R.id.ordinary_nav_button).setOnClickListener {
presenter.onOrdinaryPressed()
}
findViewById<View>(R.id.multi_nav_button).setOnClickListener {
presenter.onMultiPressed()
}
findViewById<View>(R.id.result_and_anim_button).setOnClickListener {
presenter.onResultWithAnimationPressed()
}
}
override fun onResume() {
super.onResume()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
presenter.onBackPressed()
}
}
@@ -1,29 +0,0 @@
package ru.terrakok.cicerone.sample;
import android.app.Application;
import ru.terrakok.cicerone.sample.dagger.AppComponent;
import ru.terrakok.cicerone.sample.dagger.DaggerAppComponent;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class SampleApplication extends Application {
public static SampleApplication INSTANCE;
private AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
}
public AppComponent getAppComponent() {
if (appComponent == null) {
appComponent = DaggerAppComponent.builder().build();
}
return appComponent;
}
}
@@ -1,24 +0,0 @@
package ru.terrakok.cicerone.sample;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class Screens {
public static final String SAMPLE_SCREEN = "sample_screen_";
public static final String START_ACTIVITY_SCREEN = "start activity screen";
public static final String MAIN_ACTIVITY_SCREEN = "main activity screen";
public static final String BOTTOM_NAVIGATION_ACTIVITY_SCREEN = "bna screen";
public static final String ANDROID_SCREEN = "android screen";
public static final String BUG_SCREEN = "bug screen";
public static final String DOG_SCREEN = "dog screen";
public static final String FORWARD_SCREEN = "forward screen";
public static final String GITHUB_SCREEN = "github screen";
public static final String PROFILE_SCREEN = "profile screen";
public static final String SELECT_PHOTO_SCREEN = "select photo screen";
}
@@ -1,43 +0,0 @@
package ru.terrakok.cicerone.sample.dagger;
import javax.inject.Singleton;
import dagger.Component;
import ru.terrakok.cicerone.sample.dagger.module.LocalNavigationModule;
import ru.terrakok.cicerone.sample.dagger.module.NavigationModule;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment;
import ru.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment;
import ru.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity;
import ru.terrakok.cicerone.sample.ui.bottom.TabContainerFragment;
import ru.terrakok.cicerone.sample.ui.main.MainActivity;
import ru.terrakok.cicerone.sample.ui.main.SampleFragment;
import ru.terrakok.cicerone.sample.ui.start.StartActivity;
/**
* Created by terrakok 24.11.16
*/
@Singleton
@Component(modules = {
NavigationModule.class,
LocalNavigationModule.class
})
public interface AppComponent {
void inject(StartActivity activity);
void inject(MainActivity activity);
void inject(SampleFragment fragment);
void inject(BottomNavigationActivity activity);
void inject(TabContainerFragment fragment);
void inject(ProfileFragment fragment);
void inject(SelectPhotoFragment fragment);
void inject(ProfileActivity activity);
}
@@ -1,21 +0,0 @@
package ru.terrakok.cicerone.sample.dagger.module;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import ru.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder;
/**
* Created by terrakok 24.11.16
*/
@Module
public class LocalNavigationModule {
@Provides
@Singleton
LocalCiceroneHolder provideLocalNavigationHolder() {
return new LocalCiceroneHolder();
}
}
@@ -1,34 +0,0 @@
package ru.terrakok.cicerone.sample.dagger.module;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import ru.terrakok.cicerone.Cicerone;
import ru.terrakok.cicerone.NavigatorHolder;
import ru.terrakok.cicerone.Router;
/**
* Created by terrakok 24.11.16
*/
@Module
public class NavigationModule {
private Cicerone<Router> cicerone;
public NavigationModule() {
cicerone = Cicerone.create();
}
@Provides
@Singleton
Router provideRouter() {
return cicerone.getRouter();
}
@Provides
@Singleton
NavigatorHolder provideNavigatorHolder() {
return cicerone.getNavigatorHolder();
}
}
@@ -1,42 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.animation.photos;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
public class SelectPhotoPresenter extends MvpPresenter<SelectPhotoView> {
private Router router;
private final int RESULT_CODE;
public SelectPhotoPresenter(Router router, int resultCode) {
this.router = router;
RESULT_CODE = resultCode;
}
@Override
protected void onFirstViewAttach() {
super.onFirstViewAttach();
getViewState().showPhotos(new int[] {
R.drawable.ava_1,
R.drawable.ava_2,
R.drawable.ava_3,
R.drawable.ava_4
});
}
public void onPhotoClick(int photoRes) {
router.exitWithResult(RESULT_CODE, photoRes);
}
public void onBackPressed() {
router.exit();
}
}

Some files were not shown because too many files have changed in this diff Show More