219 Commits

Author SHA1 Message Date
terrakok 8b2654ee2b WIP: add default destination. 2020-11-28 17:08:24 +03:00
terrakok 8c8521d2c5 WIP: chain builder as function. 2020-11-28 17:08:24 +03:00
terrakok 14b94927e4 WIP: add jumps. 2020-11-28 17:08:24 +03:00
terrakok 1e40fb29bb WIP: add link between vertex. 2020-11-28 17:08:24 +03:00
terrakok dbc8c3363f WIP: navigation graph. 2020-11-28 17:08:24 +03:00
terrakok c3873c6e9d Bump version to "6.6" 2020-11-26 13:31:05 +03:00
terrakok f44410fcfe Fix problem with loss of result listener.
Issue #130
2020-11-26 13:30:22 +03:00
Konstantin 608a32c3be Update README.md 2020-11-18 22:34:28 +03:00
Konstantin 17428f0189 Update README.md 2020-11-18 22:08:21 +03:00
terrakok 1701bc4f36 Fixed launching Activity via no concrete Intent. 2020-11-18 22:02:18 +03:00
terrakok 9c9c56bc9b Bump version to "6.4" 2020-11-05 17:24:22 +03:00
terrakok 3da8fe52ca Set fragmentReorderingAllowed=TRUE by default. 2020-11-05 17:21:01 +03:00
terrakok a943aab08e Move ResultListener to another file. 2020-11-05 16:15:28 +03:00
Konstantin 86ac421fb8 Merge pull request #128 from aradxxx/feature/java-interop
java interop
2020-11-05 16:06:50 +03:00
aradxxx f7009f53e8 java interop 2020-11-03 05:13:13 +04:00
Konstantin 994c497147 Update README.md 2020-11-02 20:55:34 +03:00
Konstantin bb13ace1c9 Update README.md 2020-11-02 15:29:24 +03:00
Konstantin dbe065275d Update README.md 2020-11-02 15:15:24 +03:00
Konstantin 2ebbc98a99 Update README.md 2020-11-02 15:10:17 +03:00
Konstantin ba7eea60f5 Update README.md 2020-11-02 10:10:17 +03:00
Konstantin 6347fe7d95 Update README.md 2020-11-01 21:30:28 +03:00
Konstantin 2b6e2ee874 Update README.md 2020-11-01 18:34:03 +03:00
terrakok f50a4d8106 Bump version to "6.3" 2020-11-01 17:07:08 +03:00
terrakok 3535f491f5 Refactor Screen class as interface. 2020-11-01 17:07:04 +03:00
Konstantin 1c5b89d2ec Update README.md 2020-11-01 14:54:59 +03:00
Konstantin 127cce1fe4 Update README.md 2020-11-01 14:06:53 +03:00
terrakok 3bea311400 Bump version to "6.2" 2020-10-30 12:31:15 +03:00
terrakok a0b3dfa71e Fix sample app design 2020-10-30 12:27:24 +03:00
Konstantin 0e9ff3f7f5 Merge pull request #126 from MonStar1/fix/newRootChain
fix newRootChain
2020-10-30 12:12:13 +03:00
Andrei Papko 004faf77c4 fix newRootChain 2020-10-28 17:21:57 +03:00
Konstantin 214a6eddf1 Update README.md 2020-10-21 17:32:59 +03:00
terrakok a4c94003ad Bump version to "6.1" 2020-10-21 17:14:18 +03:00
terrakok 4f7c1c1607 Add screenKey to fragment transaction tag. 2020-10-21 17:14:18 +03:00
terrakok 16f8fd833e Refactor sample screens names 2020-10-21 17:14:18 +03:00
terrakok 77c3ecc76e Rename ResultBus to ResultWire 2020-10-21 17:14:18 +03:00
terrakok 062073cb37 Add simple result messaging. 2020-10-21 17:14:18 +03:00
Konstantin b9cddc290e Update README.md 2020-10-19 15:52:39 +03:00
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
terrakok ced094ea55 Merge branch 'develop' 2017-08-21 23:02:21 +03:00
terrakok ecd214fa36 update readme.md for new version 2017-08-21 22:58:35 +03:00
Konstantin dcfe1a32b8 Merge pull request #41 from Jeevuz/small_javadocs_fix
Changed the javadocs of the animation allowing methods to look similar.
2017-08-02 11:02:52 +03:00
Vasili Chyrvon 4120ce878d Changed the javadocs of the animation allowing methods to look similar. 2017-08-02 01:28:34 +03:00
Konstantin c9ca3f76da Merge pull request #37 from Jeevuz/patch-2
Update FragmentNavigator.java
2017-08-01 23:10:45 +03:00
Konstantin 9a7b4421c2 Merge pull request #38 from Jeevuz/patch-3
Update SupportFragmentNavigator.java
2017-08-01 23:09:47 +03:00
Konstantin 81774d2d12 Merge pull request #39 from Popalay/feature/activity-animations
Feature/activity animations
2017-08-01 23:09:04 +03:00
Denis Nikiforov 30f2f736c0 Update docs 2017-07-29 22:12:40 +03:00
Denis Nikiforov 0f59f588b8 Rename setupActivityTransactionAnimation to createStartActivityOptions 2017-07-29 22:09:50 +03:00
Denis Nikiforov 68bc8c809c Create activity transition only if activity intent is not null 2017-07-28 12:34:26 +03:00
Denis Nikiforov 37d5818413 Add support of activity transitions 2017-07-27 16:04:40 +03:00
Vasili Chyrvon 7344926349 Update SupportFragmentNavigator.java
Small change in docs
2017-07-18 01:03:58 +03:00
Vasili Chyrvon 357f8d9670 Update FragmentNavigator.java
Small change in docs
2017-07-18 01:01:59 +03:00
terrakok d8266b02b8 Merge branch 'develop' 2017-07-18 00:51:17 +03:00
terrakok 7337901f19 updated version to 2.0.0 AND fixed java docs 2017-07-18 00:50:45 +03:00
terrakok 2e647aec0e updated README 2017-07-18 00:37:38 +03:00
terrakok e10725955b update README file for new version 2017-07-18 00:35:09 +03:00
terrakok f357e9db3a Merge branch 'feature/fragment_animations' into develop 2017-07-17 23:26:57 +03:00
terrakok 5ed9df5409 added javaDocs for new setupFragmentTransactionAnimation navigator method AND update sample 2017-07-17 23:20:13 +03:00
Konstantin Tskhovrebov b493cf5a7b fix after merge 2017-07-15 13:47:29 +03:00
Konstantin Tskhovrebov 2be8ac0db2 Merge branch 'develop' into feature/fragment_animations 2017-07-15 13:45:15 +03:00
Konstantin Tskhovrebov b317498d95 changed weak references on manual set/remove result listener for Router 2017-07-15 13:44:53 +03:00
Konstantin Tskhovrebov 230d7ba064 added sample for present fragment transaction animation and returning result from screen 2017-07-15 01:37:59 +03:00
Konstantin Tskhovrebov b5c2d20ea3 added next fragment instance to applyFragmentAnimations method 2017-07-15 01:35:19 +03:00
Konstantin Tskhovrebov 1647377fb1 Merge branch 'develop' into feature/fragment_animations 2017-07-14 22:43:43 +03:00
Konstantin f6b8c0bfe1 Merge pull request #36 from terrakok/feature/result_bus
Feature/result_bus
2017-07-12 12:55:18 +03:00
terrakok 5a4eeef49f added javaDocs 2017-07-12 01:16:55 +03:00
terrakok 340e659502 removed ResultData marker 2017-07-12 01:05:10 +03:00
terrakok f88ab56ee9 changed call ordering for exitWithResult method 2017-07-12 01:03:16 +03:00
terrakok 3e26610157 renamed requestCode -> resultCode 2017-07-12 01:01:45 +03:00
terrakok 906baccdfe Added the ability to apply animation to a fragment transaction 2017-07-05 00:36:34 +03:00
terrakok 72d100197a added simple "bus" to Router for listen result data 2017-07-04 23:57:27 +03:00
terrakok 10918fd759 added warning for bad sample code 2017-05-31 01:10:44 +03:00
terrakok c02d5a1ff8 optimized navigator backToRoot implementation 2017-05-31 01:04:04 +03:00
terrakok 2f6f4457b7 Merge branch 'develop' 2017-03-29 01:00:04 +03:00
terrakok d59e20269c updated version to 1.2.1 2017-03-29 00:56:55 +03:00
Konstantin 77c5645eb1 Merge pull request #26 from electrolobzik/develop
added hint for onResumeFragments() method to the readme
2017-03-28 12:55:51 +03:00
Roman Chernyak 8fef3a9e0b Moved comment from code block to the text area. 2017-03-28 12:51:58 +03:00
Roman Chernyak 53dd652942 Fixed code formatting in readme 2017-03-28 12:43:36 +03:00
Roman Chernyak edcf6ea1d0 - Removed extra text in readme
- Fixed BottomNavigationActivity to match readme
2017-03-28 12:31:46 +03:00
Roman Chernyak 25b3050d98 added hint for onResumeFragments() method to the readme and corrected the sample to correspond the readme 2017-03-28 00:10:49 +03:00
Konstantin 1f09d9ddd8 Merge pull request #25 from Jeevuz/throw-if-unknown
Throw if unknown
2017-03-27 22:46:56 +03:00
jeevuz 66ab47b3cd Throw in the unknownScreen() method. 2017-03-27 22:34:48 +03:00
terrakok bec83313c7 Merge branch 'develop' 2017-03-27 13:41:51 +03:00
terrakok d3398ae8e6 updated version to 1.2 2017-03-27 13:41:20 +03:00
terrakok 12e925cbec Merge branch 'develop' 2017-03-27 13:31:16 +03:00
terrakok 3e1d0fba75 updated readme files for new version 2017-03-27 13:30:49 +03:00
Konstantin a8bf9a331f Merge pull request #23 from Jeevuz/javadoc-change
Changed javadoc for unknownScreen()
2017-03-26 01:22:59 +03:00
jeevuz 2862752da9 Changed javadoc for unknownScreen() 2017-03-26 00:23:10 +03:00
terrakok ce90bbf617 updated library versions and actualized sample app 2017-03-25 17:01:16 +03:00
terrakok 237be41c07 completed new AppNavigator 2017-03-25 17:00:36 +03:00
Konstantin 5f1d9eb039 Merge pull request #22 from Jeevuz/app-navigators
Changed to use separate AppNavigators for work with Activities.
2017-03-24 13:30:16 +03:00
Vasili Chyrvon 920868c1f2 Changed to use separate AppNavigators for work with Activities.
Changed some javadocs.
Updated some lib versions.
2017-03-24 12:56:58 +03:00
terrakok ebcbf503cf update gradle plugin 2017-03-10 17:10:50 +03:00
terrakok af338d04b9 added demonstration launching external activity 2017-03-10 16:44:37 +03:00
terrakok 50eafba93a changed sample application for new navigator 2017-03-10 16:36:57 +03:00
terrakok 30c284b5a2 added new fragment navigator constructor for using child fragment manager case 2017-03-10 16:35:05 +03:00
terrakok 771f837e64 created default implementation for completed fragment navigator methods: exit() and showSystemMessage() 2017-03-10 16:19:14 +03:00
terrakok ecb8b1a430 Update README.md 2017-02-27 20:35:10 +04:00
terrakok 2836c63126 added ability to launch new Activities 2017-02-22 19:51:12 +03:00
terrakok de7422144b added method finishChain for Router 2017-01-29 00:11:40 +03:00
terrakok b044d560f2 updated readme.md 2016-11-28 19:16:08 +03:00
terrakok 39d9657c8c Merge branch 'ft_child_navigation' into develop 2016-11-28 00:13:55 +03:00
terrakok 5033949e48 some refactor 2016-11-28 00:13:26 +03:00
terrakok 60beb90e11 fixed container initialization after activity rotation 2016-11-26 13:30:37 +03:00
terrakok 4226595a71 implemented local navigation for each tab 2016-11-26 00:54:15 +03:00
terrakok ed837a0c43 added activity with bottom navigation bar 2016-11-25 01:22:19 +03:00
terrakok 5518c16789 integrated Dagger 2 2016-11-24 23:26:46 +03:00
terrakok 96ae7361f8 updated Moxy and Support library version 2016-11-22 23:15:25 +03:00
terrakok a7e713865a added Android Arsenal badge and fixed jCenter badge 2016-11-22 22:52:02 +03:00
134 changed files with 3267 additions and 1790 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.
+194 -111
View File
@@ -1,172 +1,255 @@
# Cicerone
[![Bintray](https://img.shields.io/bintray/v/terrakok/terramaven/cicerone.svg)](https://bintray.com/terrakok/terramaven/cicerone)
[![License](https://img.shields.io/badge/license-APACHE2-blue.svg)](http://choosealicense.com/licenses/apache-2.0/)
[![jCenter](https://api.bintray.com/packages/terrakok/terramaven/cicerone-kotlin/images/download.svg)](https://bintray.com/terrakok/terramaven/cicerone-kotlin/_latestVersion)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
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.
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Cicerone-green.svg?style=true)](https://android-arsenal.com/details/1/4700)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-250-green.svg)](http://androidweekly.net/issues/issue-250)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-271-green.svg)](http://androidweekly.net/issues/issue-271)
<table>
<tr>
<td>
<img src="https://github.com/terrakok/Cicerone/raw/master/media/navigation.gif" width="256"/>
</td>
<td>
<img src="https://github.com/terrakok/Cicerone/raw/master/media/insta_tabs.gif" width="256"/>
</td>
<td>
<img src="https://github.com/terrakok/Cicerone/raw/master/media/animations.gif" width="256"/>
</td>
</tr>
<tr>
<td>
Power navigation
</td>
<td>
Multibackstack
</td>
<td>
Result listeners
</td>
</tr>
</table>
Cicerone (means - a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
It was designed to be used with the MVP/MVVM/MVI patterns but will work great with any architecture.
## Main advantages
+ is not tied to Fragments
+ not a framework
+ not a framework (very lightweight)
+ short navigation calls (no builders)
+ static typed checks for screen parameters!
+ lifecycle-safe!
+ functionality is simple to extend
+ suitable for Unit Testing
## 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:1.1'
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/blob/master/media/CiceroneDiagram.png" alt="CiceroneDiagram.png" 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_.
```java
@Override
protected void onResume() {
super.onResume();
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(navigator);
**Attention**: Use _onResumeFragments()_ with FragmentActivity ([more info](https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()))
```kotlin
private val navigator = AppNavigator(this, R.id.container)
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/master/media/forward_img.png)
+ Back - Rolls back the last transition
![](https://github.com/terrakok/Cicerone/raw/master/media/back_img.png)
+ BackTo - Rolls back to the needed screen in the screens chain
![](https://github.com/terrakok/Cicerone/raw/master/media/backTo_img.png)
+ Replace - Replaces the current screen
![](https://github.com/terrakok/Cicerone/raw/master/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 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();
}
};
## 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/files/16d/2ee/6e3/16d2ee6e33a0428eb4f0dcab8ce6b294.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 {
fun Main() = FragmentScreen { MainFragment() }
fun AddressSearch() = FragmentScreen { AddressSearchFragment() }
fun Profile(userId: Long) = FragmentScreen("Profile_$userId") { ProfileFragment(userId) }
fun Browser(url: String) = ActivityScreen { Intent(Intent.ACTION_VIEW, Uri.parse(url)) }
}
```
Additional you can use `FragmentFactory` for creating your screens:
```kotlin
fun SomeScreen() = FragmentScreen { factory: FragmentFactory -> ... }
```
## Screen parameters and result listener
```kotlin
//you have to specify screen parameters via new FragmentScreen creation
fun SelectPhoto(resultKey: String) = FragmentScreen {
SelectPhotoFragment.getNewInstance(resultKey)
}
```
```kotlin
//listen result
fun onSelectPhotoClicked() {
router.setResultListener(RESULT_KEY) { data ->
view.showPhoto(data as Bitmap)
}
router.navigateTo(SelectPhoto(RESULT_KEY))
}
//send result
fun onPhotoClick(photo: Bitmap) {
router.sendResult(resultKey, photoRes)
router.exit()
}
```
## Sample
To see how to add, initialize and use the library and predefined navigators see **sample project**
(thank you [@Javernaut](https://github.com/Javernaut) for support new library version and migrate sample project to Kotlin!)
For more complex use case check out the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
## Applications with Cicerone inside
<a href="https://play.google.com/store/apps/details?id=ru.foodfox.client"><img src="https://play-lh.googleusercontent.com/gWYedIqy8QujCQOn0kzEIBEkGLBSpuKvFm-fMcfkWnJ1Oirtv847xAE4OyhAaohdcp5V=s360" width="64" /> Яндекс.Еда — доставка еды/продуктов. Food delivery</a><br>
<a href="https://play.google.com/store/apps/details?id=com.foodient.whisk"><img src="https://play-lh.googleusercontent.com/eKotZjJcZOU2_L9t2l34EEY7aGl5zhvKVuEbF0Kc4MRs_pAC2SJgOnWMkMTFjR_e9EY=s360" width="64" /> Whisk: Recipe Saver, Meal Planner & Grocery List</a><br>
<a href="https://play.google.com/store/apps/details?id=kz.beeline.odp"><img src="https://play-lh.googleusercontent.com/hzgjpQQpy6Z-Byye0aVKSv9P7h8yx58i6pVkQtiM6jB99iWFXjYfKeaPqJ3wm6Rtb38=s360" width="64" /> Мой Beeline (Казахстан)</a><br>
<a href="https://play.google.com/store/apps/details?id=com.mercuryo.app"><img src="https://play-lh.googleusercontent.com/FKulXdc15r5PWX6hTZi2i3iaJjcQHwd9xParp6YPiQ2KiBqza7jwEt_b_tqLwXpyEHg=s360" width="64" /> Mercuryo Bitcoin Cryptowallet</a><br>
<a href="https://play.google.com/store/apps/details?id=com.warefly.checkscan"><img src="https://play-lh.googleusercontent.com/2c2uuiSl2vwGgp-vdI-VArQEMdSSXk1neUK5A-Udc0WANPcvp5kBJFEugrFiXnxUc7k=s360" width="64" /> ЧекСкан - кэшбэк за чеки, цены и акции в магазинах</a><br>
<a href="https://github.com/eduard1abdulmanov123/News"><img src="https://raw.githubusercontent.com/eduard1abdulmanov123/News/dev/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" width="64" /> RSS Reader для Вести.Ru</a><br>
<a href="https://play.google.com/store/apps/details?id=com.epam.connect.android"><img src="https://play-lh.googleusercontent.com/aN7R6BiR7yt7b3oEoBI30pVwzsdzaWe3TWpw8c9igqoOj79Pm2xVh4_C4qwjSKwjVio=s360" width="64" /> EPAM Connect</a><br>
<a href="https://play.google.com/store/apps/details?id=org.consumerreports.ratings"><img src="https://play-lh.googleusercontent.com/dEdOwZOjXAdamytxY1TgY8LS-Hc9FKCcit5HP1RyaKqRAWjDJEyFSQS1XlqQPpeY5UI=s360" width="64" /> Consumer Reports: Product Reviews & Ratings</a><br>
<a href="https://play.google.com/store/apps/details?id=ru.zakaz.android"><img src="https://play-lh.googleusercontent.com/jj18yK2dB2MHZ_QdO21aXyznGXteIF2q4mgxY4ubLhFv9gwZqHVDeu1i2FmanS-0Furm=s360" width="64" /> Zakaz.ru</a><br>
## Participants
+ idea and code - Konstantin Tskhovrebov (@terrakok)
+ architecture advice, documentation and publication - Vasili Chyrvon (@Jeevuz)
## License
```
MIT License
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
Copyright (c) 2017 Konstantin Tskhovrebov (@terrakok)
and Vasili Chyrvon (@Jeevuz)
http://www.apache.org/licenses/LICENSE-2.0
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:
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.
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.
```
-170
View File
@@ -1,170 +0,0 @@
# Cicerone
Cicerone (_"чи-че-ро́-не"_ - устар. гид) - легкая библиотека для простой реализации навигации в андроид приложении.
Разработана для использования в MVP архитектуре (попробуйте [Moxy](https://github.com/Arello-Mobile/Moxy)), но легко встраивается в любые решения.
## Основные преимущества
+ не завязана на Fragment'ы
+ не фреймворк
+ короткие вызовы навигации (никаких билдеров)
+ lifecycle-безопасна!
+ простое расширение функционала
+ приспособлена для Unit тестов
## Как подключить?
Добавьте в build.gradle зависимость:
```groovy
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:1.1'
}
```
И инициализируйте библиотеку, например, так:
```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_
```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)
## Участники
+ идея и реализация - Константин Цховребов (@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.2.2'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// For the library uploading to the Bintray
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.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 @@
#Tue Sep 06 14:31:17 MSK 2016
#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-2.14.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
+25 -7
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,29 +8,31 @@ 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 = '1.1'
libraryVersion = '6.6'
gitUrl = 'https://github.com/terrakok/Cicerone'
allLicenses = ['Apache-2.0']
allLicenses = ['MIT']
}
// 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,150 +0,0 @@
package ru.terrakok.cicerone;
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;
/**
* 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 {
public Router() {
super();
}
/**
* 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));
}
/**
* 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 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,129 +0,0 @@
package ru.terrakok.cicerone.android;
import android.app.Fragment;
import android.app.FragmentManager;
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
public void applyCommand(Command command) {
if (command instanceof Forward) {
Forward forward = (Forward) command;
fragmentManager
.beginTransaction()
.replace(containerId, createFragment(forward.getScreenKey(), forward.getTransitionData()))
.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;
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
fragmentManager
.beginTransaction()
.replace(containerId, createFragment(replace.getScreenKey(), replace.getTransitionData()))
.addToBackStack(replace.getScreenKey())
.commit();
} else {
fragmentManager
.beginTransaction()
.replace(containerId, createFragment(replace.getScreenKey(), replace.getTransitionData()))
.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() {
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
fragmentManager.popBackStack();
}
fragmentManager.executePendingTransactions();
}
/**
* 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();
}
}
@@ -1,129 +0,0 @@
package ru.terrakok.cicerone.android;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
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
public void applyCommand(Command command) {
if (command instanceof Forward) {
Forward forward = (Forward) command;
fragmentManager
.beginTransaction()
.replace(containerId, createFragment(forward.getScreenKey(), forward.getTransitionData()))
.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;
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
fragmentManager
.beginTransaction()
.replace(containerId, createFragment(replace.getScreenKey(), replace.getTransitionData()))
.addToBackStack(replace.getScreenKey())
.commit();
} else {
fragmentManager
.beginTransaction()
.replace(containerId, createFragment(replace.getScreenKey(), replace.getTransitionData()))
.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() {
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
fragmentManager.popBackStack();
}
fragmentManager.executePendingTransactions();
}
/**
* 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();
}
}
@@ -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;
}
}
@@ -0,0 +1,40 @@
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()
private val resultWire = ResultWire()
/**
* Sets data listener with given key
* and returns [ResultListenerHandler] for availability to dispose subscription.
*
* After first call listener will be removed.
*/
fun setResultListener(
key: String,
listener: ResultListener
): ResultListenerHandler {
return resultWire.setResultListener(key, listener)
}
/**
* Sends data to listener with given key.
*/
fun sendResult(key: String, data: Any) {
resultWire.sendResult(key, data)
}
/**
* Sends navigation command array to [CommandBuffer].
*
* @param commands navigation command array to execute
*/
protected fun executeCommands(vararg commands: Command) {
commandBuffer.executeCommands(commands)
}
}
@@ -0,0 +1,30 @@
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]
*/
@JvmStatic
fun create() = create(Router())
/**
* Creates the Cicerone instance with the custom router.
* @param customRouter the custom router extending [BaseRouter]
*/
@JvmStatic
fun <T : BaseRouter> create(customRouter: T) = Cicerone(customRouter)
}
}
@@ -0,0 +1,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,30 @@
package com.github.terrakok.cicerone
/**
* Interface definition for a result callback.
*/
fun interface ResultListener {
fun onResult(data: Any)
}
/**
* Handler for manual delete subscription and avoid leak
*/
fun interface ResultListenerHandler {
fun dispose()
}
internal class ResultWire {
private val listeners = mutableMapOf<String, ResultListener>()
fun setResultListener(key: String, listener: ResultListener): ResultListenerHandler {
listeners[key] = listener
return ResultListenerHandler {
listeners.remove(key)
}
}
fun sendResult(key: String, data: Any) {
listeners.remove(key)?.onResult(data)
}
}
@@ -0,0 +1,103 @@
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
*/
@JvmOverloads
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
*/
@JvmOverloads
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
*/
@JvmOverloads
fun newRootChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
val commands = screens.mapIndexed { index, screen ->
if (index == 0)
Replace(screen)
else
Forward(screen, 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 interface for description application screen.
*/
interface Screen {
val screenKey: String get() = this::class.java.name
}
@@ -0,0 +1,217 @@
package com.github.terrakok.cicerone.androidx
import android.content.ActivityNotFoundException
import android.content.Intent
import androidx.fragment.app.*
import com.github.terrakok.cicerone.*
import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.ADD
import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.REPLACE
/**
* Navigator implementation for launch fragments and activities.
*
* Feature [BackTo] works only for fragments.
*
* Recommendation: most useful for Single-Activity application.
*/
open class AppNavigator @JvmOverloads constructor(
protected val activity: FragmentActivity,
protected val containerId: Int,
protected val fragmentManager: FragmentManager = activity.supportFragmentManager,
protected val fragmentFactory: FragmentFactory = fragmentManager.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()
transaction.setReorderingAllowed(true)
setupFragmentTransaction(
transaction,
fragmentManager.findFragmentById(containerId),
fragment
)
when (type) {
ADD -> transaction.add(containerId, fragment, screen.screenKey)
REPLACE -> transaction.replace(containerId, fragment, screen.screenKey)
}
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)
try {
activity.startActivity(activityIntent, screen.startActivityOptions)
} catch (e: ActivityNotFoundException) {
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,31 @@
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
fun interface Creator<A, R> {
fun create(argument: A): R
}
open class FragmentScreen @JvmOverloads constructor(
private val key: String? = null,
private val fragmentCreator: Creator<FragmentFactory, Fragment>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
}
open class ActivityScreen @JvmOverloads constructor(
private val key: String? = null,
private val intentCreator: Creator<Context, Intent>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
open val startActivityOptions: Bundle? = null
fun createIntent(context: Context) = intentCreator.create(context)
}
@@ -0,0 +1,21 @@
package com.github.terrakok.cicerone.androidx
data class TransactionInfo(
val screenKey: String,
val type: Type
) {
enum class Type(val symbol: Char) {
ADD('+'),
REPLACE('-');
}
override fun toString() = screenKey + type.symbol
companion object {
@JvmStatic
fun fromString(str: String) = TransactionInfo(
str.dropLast(1),
if (str.last() == Type.ADD.symbol) Type.ADD else Type.REPLACE
)
}
}
@@ -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
@@ -0,0 +1,134 @@
package com.github.terrakok.cicerone.graph
import com.github.terrakok.cicerone.*
class GraphRouter(
private val root: Root
) : BaseRouter() {
private val vertexes: Map<String, Vertex>
private val currentPath: MutableList<String>
val currentVertex get() = vertexes.getValue(currentPath.last())
init {
val allVertexes = mutableMapOf<String, Vertex>()
val links = mutableSetOf<VertexLink>()
val jumps = mutableListOf<List<String>>()
validateGraph(root.vertex, allVertexes, links, jumps)
val linkAndJumpIds = links.map { it.id } + jumps.flatten()
linkAndJumpIds.forEach { id ->
require(allVertexes.containsKey(id)) { "Not found vertex for id=$id" }
}
vertexes = allVertexes
currentPath = mutableListOf(root.vertex.id)
jumps.forEach {
require(validateJump(it)) { "Invalid jump path=$it" }
}
if (root.defaultDestination != null) {
require(root.vertex.edges.any { it.id == root.defaultDestination }) { "Not found default destination" }
navigateTo(root.defaultDestination)
}
}
private fun validateGraph(
vertex: Vertex,
allVertexes: MutableMap<String, Vertex>,
links: MutableSet<VertexLink>,
jumps: MutableList<List<String>>
) {
if (allVertexes.containsKey(vertex.id)) error("Graph contains duplicate id ${vertex.id}")
allVertexes[vertex.id] = vertex
links.addAll(vertex.edges.filterIsInstance<VertexLink>())
jumps.addAll(vertex.jumps.map { listOf(it.backTo ?: vertex.id, *it.chain.toTypedArray()) })
vertex.edges.forEach {
if (it !is VertexLink) validateGraph(it, allVertexes, links, jumps)
}
}
private fun validateJump(path: List<String>): Boolean {
if (path.isEmpty()) return false
if (path.size == 1) return true
val v = vertexes.getValue(path[0])
v.edges.firstOrNull { it.id == path[1] } ?: return false
return validateJump(path.subList(1, path.size))
}
fun navigateTo(
vertexId: String,
screenFactory: (vertexId: String) -> Screen? = { null }
) {
val destination = currentVertex.edges.first { it.id == vertexId }
val screen = createScreen(destination.id, screenFactory)
val command =
if (currentVertex.id == Root.ID) Replace(screen)
else Forward(screen, destination.destroyPreviousView)
currentPath.add(destination.id)
executeCommands(command)
}
fun jumpTo(
jumpId: String,
screenFactory: (vertexId: String) -> Screen? = { null }
) {
val jump = currentVertex.jumps.first { it.id == jumpId }
if (jump.backTo == Root.ID && jump.chain.isEmpty()) {
finish()
return
}
val commands = mutableListOf<Command>().apply {
if (jump.backTo != null) {
val id = jump.backTo
val index = currentPath.indexOfFirst { it == id }
if (index == -1) error("Current path doesn't contain vertex $id")
currentPath.subList(index + 1, currentPath.size).clear()
if (jump.backTo == Root.ID) add(BackTo(null))
else add(BackTo(Key(id)))
}
jump.chain.forEachIndexed { index, vertexId ->
val screen = createScreen(vertexId, screenFactory)
currentPath.add(vertexId)
if (index == 0 && jump.backTo == Root.ID) {
add(Replace(screen))
} else {
add(Forward(screen, vertexes.getValue(vertexId).destroyPreviousView))
}
}
}
executeCommands(*commands.toTypedArray())
}
fun exit() {
currentPath.removeLast()
executeCommands(Back())
}
private fun finish() {
currentPath.clear()
currentPath.add(Root.ID)
executeCommands(BackTo(null), Back())
}
private fun createScreen(
vertexId: String,
screenFactory: (vertexId: String) -> Screen?
): Screen {
val screen = screenFactory(vertexId)
?: vertexes.getValue(vertexId).screenFactory(vertexId)
?: error("Unknown screen for vertex $vertexId")
require(screen.screenKey == vertexId) { "Screen key must be equal vertex id!" }
return screen
}
private class Key(
override val screenKey: String
) : Screen
}
@@ -0,0 +1,134 @@
package com.github.terrakok.cicerone.graph
import com.github.terrakok.cicerone.*
open class Vertex internal constructor(
val id: String,
val edges: Set<Vertex>,
val jumps: Set<Jump>,
val destroyPreviousView: Boolean = true,
val screenFactory: (id: String) -> Screen? = { null }
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Vertex
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
}
internal class VertexLink(
id: String,
destroyPreviousView: Boolean = true
): Vertex(id, emptySet(), emptySet(), destroyPreviousView)
class Jump(
val id: String,
val backTo: String?,
val chain: List<String>
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Jump
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
}
//Graph DSL
class GraphInfo(
var edges: MutableSet<Vertex>.() -> Unit = {},
var jumps: MutableSet<Jump>.() -> Unit = {}
)
class Root(
val vertex: Vertex,
val defaultDestination: String?
) {
companion object {
const val ID = "graph-root"
}
}
fun graph(
defaultDestination: String? = null,
setup: GraphInfo.() -> Unit
): Root {
val info = GraphInfo().apply(setup)
val v = Vertex(
Root.ID,
mutableSetOf<Vertex>().apply(info.edges),
mutableSetOf<Jump>().apply(info.jumps)
)
return Root(v, defaultDestination)
}
class VertexInfo(
var edges: MutableSet<Vertex>.() -> Unit = {},
var jumps: MutableSet<Jump>.() -> Unit = {},
var screen: (id: String) -> Screen? = { null }
)
fun MutableSet<Vertex>.dest(
id: String,
destroyPreviousView: Boolean = true,
setup: VertexInfo.() -> Unit = {}
) {
val info = VertexInfo().apply(setup)
add(Vertex(
id,
mutableSetOf<Vertex>().apply(info.edges),
mutableSetOf<Jump>().apply(info.jumps),
destroyPreviousView,
info.screen
))
}
fun MutableSet<Vertex>.edge(
id: String,
destroyPreviousView: Boolean = true
) {
add(VertexLink(id, destroyPreviousView))
}
class JumpInfo(
var backTo: String? = null,
internal var chain: List<String> = emptyList()
) {
fun chain(vararg id: String) {
chain = id.toList()
}
}
fun MutableSet<Jump>.jump(
id: String,
setup: JumpInfo.() -> Unit
) {
val info = JumpInfo().apply(setup)
add(Jump(
id,
info.backTo,
info.chain
))
}
fun MutableSet<Jump>.finish(id: String) {
add(Jump(id, Root.ID, emptyList()))
}
+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,7 +0,0 @@
package android.app;
/***
* Created by laomo on 2016-11-21.
*/
public class Activity {
}
@@ -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,41 +0,0 @@
package android.app;
/***
* Created by laomo on 2016-11-21.
*/
public class FragmentManager {
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 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,7 +0,0 @@
package android.os;
/***
* Created by laomo on 2016-11-21.
*/
public class Bundle {
}
@@ -1,11 +0,0 @@
package android.support.v4.app;
import android.app.Activity;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentActivity extends Activity {
}
@@ -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,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
*/
@@ -0,0 +1,14 @@
package androidx.fragment.app;
import android.app.Activity;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentActivity extends Activity {
public FragmentManager getSupportFragmentManager() {
throw new RuntimeException("Stub!");
}
}
@@ -0,0 +1,9 @@
package androidx.fragment.app;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentFactory {
}
@@ -1,11 +1,12 @@
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
*/
public class FragmentManager {
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
public FragmentTransaction beginTransaction() {
throw new RuntimeException("Stub!");
@@ -19,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!");
}
@@ -35,6 +32,13 @@ public class FragmentManager {
throw new RuntimeException("Stub!");
}
public Fragment findFragmentById(int id) {
throw new RuntimeException("Stub!");
}
public FragmentFactory getFragmentFactory() {
throw new RuntimeException("Stub!");
}
public interface BackStackEntry {
int getId();
@@ -0,0 +1,28 @@
package androidx.fragment.app;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentTransaction {
public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
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

+39 -13
View File
@@ -1,19 +1,20 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
repositories {
jcenter()
}
android {
compileSdkVersion 24
buildToolsVersion '24'
compileSdkVersion 28
defaultConfig {
minSdkVersion 15
targetSdkVersion 24
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0.0"
applicationId "ru.terrakok.cicerone.sample"
applicationId "com.github.terrakok.cicerone.sample"
}
buildTypes {
@@ -25,23 +26,48 @@ android {
debug {
}
}
buildFeatures {
viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
ext {
supportLibraryVersion = "24.2.1"
moxyVersion = "1.0.2"
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"
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-android:$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
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
//Bottom Navigation Bar
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"
}
+5 -2
View File
@@ -1,11 +1,11 @@
<?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
android:name=".SampleApplication"
android:allowBackup="false"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
@@ -19,5 +19,8 @@
</activity>
<activity android:name=".ui.main.MainActivity"/>
<activity android:name=".ui.bottom.BottomNavigationActivity"/>
<activity android:name=".ui.animations.ProfileActivity"/>
<activity android:name=".ui.graph.GraphActivity"/>
</application>
</manifest>
@@ -0,0 +1,75 @@
package com.github.terrakok.cicerone.sample
import com.github.terrakok.cicerone.androidx.FragmentScreen
import com.github.terrakok.cicerone.graph.*
import com.github.terrakok.cicerone.sample.ui.graph.ForkFragment
import com.github.terrakok.cicerone.sample.ui.graph.RoadFragment
private val RoadScreen = { id: String -> FragmentScreen(id) { RoadFragment.getNewInstance(id) } }
private val ForkScreen = { id: String -> FragmentScreen(id) { ForkFragment.getNewInstance(id) } }
fun Graph() = graph("8") {
edges = {
dest("1") {
screen = RoadScreen
edges = {
dest("2") {
screen = ForkScreen
edges = {
dest("3") {
screen = RoadScreen
edges = {
dest("4") {
screen = RoadScreen
edges = {
edge("5")
}
}
}
}
dest("5") {
screen = ForkScreen
edges = {
dest("6") {
screen = RoadScreen
}
dest("7") {
screen = RoadScreen
jumps = {
finish("1")
}
}
}
}
}
}
}
}
dest("8") {
screen = ForkScreen
edges = {
dest("9") {
screen = RoadScreen
edges = {
edge("1")
}
jumps = {
jump("1") {
backTo = Root.ID
chain("1", "2", "3", "4", "5", "7")
}
}
}
dest("10") {
screen = ForkScreen
edges = {
dest("11") {
screen = RoadScreen
}
edge("10")
}
}
}
}
}
}
@@ -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,67 @@
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.graph.GraphActivity
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
object Screens {
fun Sample(number: Int) = FragmentScreen("Sample($number)") {
SampleFragment.getNewInstance(number)
}
fun Start() = ActivityScreen {
Intent(it, StartActivity::class.java)
}
fun Main() = ActivityScreen {
Intent(it, MainActivity::class.java)
}
fun BottomNavigation() = ActivityScreen {
Intent(it, BottomNavigationActivity::class.java)
}
fun Tab(tabName: String) = FragmentScreen {
TabContainerFragment.getNewInstance(tabName)
}
fun Forward(containerName: String, number: Int) = FragmentScreen {
ForwardFragment.getNewInstance(containerName, number)
}
fun Github() = ActivityScreen {
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"))
}
fun Profile() = ActivityScreen {
Intent(it, ProfileActivity::class.java)
}
fun ProfileInfo() = FragmentScreen {
ProfileFragment()
}
fun SelectPhoto(resultKey: String) = FragmentScreen {
SelectPhotoFragment.getNewInstance(resultKey)
}
fun Graph() = ActivityScreen {
Intent(it, GraphActivity::class.java)
}
}
@@ -0,0 +1,49 @@
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.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
import com.github.terrakok.cicerone.sample.ui.graph.ForkFragment
import com.github.terrakok.cicerone.sample.ui.graph.GraphActivity
import com.github.terrakok.cicerone.sample.ui.graph.RoadFragment
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
import dagger.Component
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Singleton
@Component(modules = [
NavigationModule::class,
LocalNavigationModule::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)
fun inject(activity: GraphActivity)
fun inject(fragment: RoadFragment)
fun inject(fragment: ForkFragment)
}
@@ -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,46 @@
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 com.github.terrakok.cicerone.graph.GraphRouter
import com.github.terrakok.cicerone.sample.Graph
import dagger.Module
import dagger.Provides
import javax.inject.Named
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
class NavigationModule {
private val cicerone: Cicerone<Router> = create()
private val graphCicerone: Cicerone<GraphRouter> = create(GraphRouter(Graph()))
@Provides
@Singleton
fun provideRouter(): Router {
return cicerone.router
}
@Provides
@Singleton
fun provideNavigatorHolder(): NavigatorHolder {
return cicerone.getNavigatorHolder()
}
@Provides
@Singleton
fun provideGraphRouter(): GraphRouter {
return graphCicerone.router
}
@Provides
@Singleton
@Named("graph")
fun provideGraphNavigatorHolder(): NavigatorHolder {
return graphCicerone.getNavigatorHolder()
}
}
@@ -0,0 +1,35 @@
package com.github.terrakok.cicerone.sample.mvp.animation.photos
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class SelectPhotoPresenter(
private val resultKey: String,
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) {
router.sendResult(resultKey, 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,44 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import com.github.terrakok.cicerone.ResultListenerHandler
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.Screens.SelectPhoto
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class ProfilePresenter(
private val router: Router
) : MvpPresenter<ProfileView>() {
private var resultListenerHandler: ResultListenerHandler? = null
companion object {
private const val RESULT_KEY = "photo_result"
private const val DEFAULT_PHOTO = R.drawable.ava_1
}
override fun onFirstViewAttach() {
super.onFirstViewAttach()
viewState!!.showPhoto(DEFAULT_PHOTO)
}
fun onPhotoClicked() {
resultListenerHandler = router.setResultListener(RESULT_KEY) { data ->
viewState!!.showPhoto(data as Int)
}
router.navigateTo(SelectPhoto(RESULT_KEY))
}
override fun onDestroy() {
resultListenerHandler?.dispose()
super.onDestroy()
}
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,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.Forward
import com.github.terrakok.cicerone.sample.Screens.Github
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(Forward(container, number + 1))
}
fun onGithubPressed() {
router.navigateTo(Github())
}
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,28 @@
package com.github.terrakok.cicerone.sample.mvp.graph
import com.github.terrakok.cicerone.graph.GraphRouter
import moxy.InjectViewState
import moxy.MvpPresenter
import moxy.MvpView
@InjectViewState
class ForkPresenter(
private val graphRouter: GraphRouter
): MvpPresenter<MvpView>() {
fun onTopButtonClick() {
graphRouter.currentVertex.edges.firstOrNull()?.id?.let { id ->
graphRouter.navigateTo(id)
}
}
fun onBottomButtonClick() {
graphRouter.currentVertex.edges.lastOrNull()?.id?.let { id ->
graphRouter.navigateTo(id)
}
}
fun onBackPressed() {
graphRouter.exit()
}
}
@@ -0,0 +1,28 @@
package com.github.terrakok.cicerone.sample.mvp.graph
import com.github.terrakok.cicerone.graph.GraphRouter
import moxy.InjectViewState
import moxy.MvpPresenter
import moxy.MvpView
@InjectViewState
class RoadPresenter(
private val graphRouter: GraphRouter
): MvpPresenter<MvpView>() {
fun onButtonClick() {
graphRouter.currentVertex.edges.firstOrNull()?.id?.let { id ->
graphRouter.navigateTo(id)
}
}
fun onJumpClick() {
graphRouter.currentVertex.jumps.firstOrNull()?.id?.let { id ->
graphRouter.jumpTo(id)
}
}
fun onBackPressed() {
graphRouter.exit()
}
}
@@ -0,0 +1,79 @@
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.Sample
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(Sample(screenNumber + 1))
}
fun onReplaceCommandClick() {
router.replaceScreen(Sample(screenNumber + 1))
}
fun onNewChainCommandClick() {
router.newChain(
Sample(screenNumber + 1),
Sample(screenNumber + 2),
Sample(screenNumber + 3)
)
}
fun onNewRootChainCommandClick() {
router.newRootChain(
Sample(screenNumber + 1),
Sample(screenNumber + 2),
Sample(screenNumber + 3)
)
}
fun onFinishChainCommandClick() {
router.finishChain()
}
fun onNewRootCommandClick() {
router.newRootScreen(Sample(screenNumber + 1))
}
fun onForwardWithDelayCommandClick() {
future?.cancel(true)
future = executorService.schedule({ //WARNING! Navigation must be only in UI thread.
Handler(Looper.getMainLooper()).post {
router.navigateTo(Sample(screenNumber + 1))
}
}, 5, TimeUnit.SECONDS)
}
fun onBackToCommandClick() {
router.backTo(Sample(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,35 @@
package com.github.terrakok.cicerone.sample.mvp.start
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.BottomNavigation
import com.github.terrakok.cicerone.sample.Screens.Graph
import com.github.terrakok.cicerone.sample.Screens.Main
import com.github.terrakok.cicerone.sample.Screens.Profile
import moxy.MvpPresenter
/**
* Created by terrakok 21.11.16
*/
class StartActivityPresenter(private val router: Router) : MvpPresenter<StartActivityView>() {
fun onOrdinaryPressed() {
router.navigateTo(Main())
}
fun onMultiPressed() {
router.navigateTo(BottomNavigation())
}
fun onResultWithAnimationPressed() {
router.navigateTo(Profile())
}
fun onGraphPressed() {
router.navigateTo(Graph())
}
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.ProfileInfo
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import javax.inject.Inject
/**
* 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(ProfileInfo())))
}
}
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) {
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,112 @@
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.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
@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)
private val resultKey: String
get() = arguments!!.getString(EXTRA_RESULT_KEY)!!
@ProvidePresenter
fun providePresenter() = SelectPhotoPresenter(resultKey, 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"
private const val EXTRA_RESULT_KEY = "extra_result_key"
fun getNewInstance(resultKey: String): SelectPhotoFragment {
return SelectPhotoFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_RESULT_KEY, resultKey)
}
}
}
}
}
@@ -0,0 +1,70 @@
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.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
@ProvidePresenter
fun providePresenter() = ProfilePresenter(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.Tab
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
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,
Tab(tab).createFragment(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.Forward
import com.github.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
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)
}
@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(Forward(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,65 @@
package com.github.terrakok.cicerone.sample.ui.graph
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.terrakok.cicerone.graph.GraphRouter
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentForkBinding
import com.github.terrakok.cicerone.sample.databinding.FragmentRoadBinding
import com.github.terrakok.cicerone.sample.mvp.graph.ForkPresenter
import com.github.terrakok.cicerone.sample.mvp.graph.RoadPresenter
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.MvpView
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
class ForkFragment : MvpAppCompatFragment(), MvpView, BackButtonListener {
lateinit var binding: FragmentForkBinding
@Inject
lateinit var graphRouter: GraphRouter
@InjectPresenter
lateinit var presenter: ForkPresenter
@ProvidePresenter
fun provideRoadPresenter(): ForkPresenter {
return ForkPresenter(graphRouter)
}
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentForkBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.textView.text = arguments?.getString(EXTRA_NUMBER)
binding.topButton.setOnClickListener { presenter.onTopButtonClick() }
binding.bottomButton.setOnClickListener { presenter.onBottomButtonClick()}
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val EXTRA_NUMBER = "extra_number"
fun getNewInstance(number: String) = ForkFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NUMBER, number)
}
}
}
}
@@ -0,0 +1,63 @@
package com.github.terrakok.cicerone.sample.ui.graph
import android.os.Bundle
import android.transition.ChangeBounds
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.github.terrakok.cicerone.Command
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Replace
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.graph.GraphRouter
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.ProfileInfo
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class GraphActivity : AppCompatActivity() {
@Inject
@Named("graph")
lateinit var navigatorHolder: NavigatorHolder
@Inject
lateinit var graphRouter: GraphRouter
private val navigator = AppNavigator(this, R.id.container)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_container)
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentById(R.id.container)
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
super.onBackPressed()
}
}
}
@@ -0,0 +1,63 @@
package com.github.terrakok.cicerone.sample.ui.graph
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.terrakok.cicerone.graph.GraphRouter
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentRoadBinding
import com.github.terrakok.cicerone.sample.mvp.graph.RoadPresenter
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.MvpView
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
class RoadFragment : MvpAppCompatFragment(), MvpView, BackButtonListener {
lateinit var binding: FragmentRoadBinding
@Inject
lateinit var graphRouter: GraphRouter
@InjectPresenter
lateinit var presenter: RoadPresenter
@ProvidePresenter
fun provideRoadPresenter(): RoadPresenter {
return RoadPresenter(graphRouter)
}
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentRoadBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.textView.text = arguments?.getString(EXTRA_NUMBER)
binding.forwardButton.setOnClickListener { presenter.onButtonClick() }
binding.jumpButton.setOnClickListener { presenter.onJumpClick() }
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val EXTRA_NUMBER = "extra_number"
fun getNewInstance(number: String) = RoadFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NUMBER, number)
}
}
}
}
@@ -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.Sample
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) {
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(Sample(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,88 @@
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.newRootChainCommand.setOnClickListener { presenter.onNewRootChainCommandClick() }
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,73 @@
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)
@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()
}
findViewById<View>(R.id.graph_button).setOnClickListener {
presenter.onGraphPressed()
}
}
override fun onResume() {
super.onResume()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
presenter.onBackPressed()
}
}
@@ -1,37 +0,0 @@
package ru.terrakok.cicerone.sample;
import com.arellomobile.mvp.MvpApplication;
import ru.terrakok.cicerone.Cicerone;
import ru.terrakok.cicerone.NavigatorHolder;
import ru.terrakok.cicerone.Router;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
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();
}
}
@@ -1,12 +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";
}
@@ -1,79 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.main;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.Screens;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
@InjectViewState
public class SamplePresenter extends MvpPresenter<SampleView> {
private Router router;
private int screenNumber;
private ScheduledExecutorService executorService;
private ScheduledFuture<?> future;
public SamplePresenter() {
router = SampleApplication.INSTANCE.getRouter();
executorService = Executors.newSingleThreadScheduledExecutor();
}
public void init(int screenNumber) {
this.screenNumber = screenNumber;
}
@Override
protected void onFirstViewAttach() {
super.onFirstViewAttach();
getViewState().setTitle("Screen " + screenNumber);
}
public void onBackCommandClick() {
router.exit();
}
public void onForwardCommandClick() {
router.navigateTo(Screens.SAMPLE_SCREEN + (screenNumber + 1), screenNumber + 1);
}
public void onReplaceCommandClick() {
router.replaceScreen(Screens.SAMPLE_SCREEN + (screenNumber + 1), screenNumber + 1);
}
public void onNewChainCommandClick() {
router.newScreenChain(Screens.SAMPLE_SCREEN + (screenNumber + 1), screenNumber + 1);
}
public void onNewRootCommandClick() {
router.newRootScreen(Screens.SAMPLE_SCREEN + (screenNumber + 1), screenNumber + 1);
}
public void onBackWithMessageCommandClick() {
router.exitWithMessage("Exit from 'Screen " + screenNumber + "'");
}
public void onForwardWithDelayCommandClick() {
if (future != null) future.cancel(true);
future = executorService.schedule(new Runnable() {
@Override
public void run() {
router.navigateTo(Screens.SAMPLE_SCREEN + (screenNumber + 1), screenNumber + 1);
}
}, 5, TimeUnit.SECONDS);
}
public void onBackToCommandClick() {
router.backTo(Screens.SAMPLE_SCREEN + 3);
}
}
@@ -1,15 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.main;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy;
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
@StateStrategyType(AddToEndSingleStrategy.class)
public interface SampleView extends MvpView {
void setTitle(String title);
}

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