194 Commits

Author SHA1 Message Date
terrakok df860d2173 Change version name to "6.0" and update bintray setup. 2020-10-19 15:37:29 +03:00
terrakok dbfc6a4f72 Change "Tckhovrebov" -> "Tskhovrebov" everywhere. 2020-10-19 15:34:14 +03:00
terrakok 69e4632058 Change "all" -> "bin" for gradle wrapper. 2020-10-19 15:33:04 +03:00
Konstantin f38b5685a2 Merge pull request #124 from Javernaut/feature/updated_sample
Sample project reviving
2020-10-17 22:55:10 +03:00
Javernaut b81d6bc8e5 Removing redundant explicit dependency for Kotlin stdlib 2020-10-17 21:54:03 +03:00
Javernaut ab8036a13e Migrating to moxy-community 2020-10-17 21:52:27 +03:00
Javernaut a9688db7f5 Migrating the sample applicationId to 'com.github.terrakok.cicerone.sample' 2020-10-16 19:34:59 +03:00
Javernaut e63f6c2339 The Animation sample was migrated to Kotlin 2020-10-16 18:47:15 +03:00
Javernaut 1de7a50b58 Bottom navigation sample was migrated to Kotlin 2020-10-16 18:33:18 +03:00
Javernaut 0efab8731b Main functionality of the sample was migrated to Kotlin 2020-10-16 18:18:44 +03:00
Javernaut 39d043f5d7 StartActivity was migrated to Kotlin 2020-10-16 17:10:02 +03:00
Javernaut f5397994ab Migrating Dagger code to Kotlin 2020-10-16 17:00:14 +03:00
Javernaut af272bc587 Reviving the sample module 2020-10-16 16:22:44 +03:00
Javernaut 9e6c08d5c5 Build tools updating 2020-10-16 14:45:57 +03:00
Konstantin 9c67451d11 Update README.md 2020-10-16 10:58:22 +03:00
Konstantin 86674c0275 Update README.md 2020-10-14 23:45:46 +03:00
terrakok 70b8fcbf4f Rename version to 6.1 2020-10-14 22:51:54 +03:00
terrakok 8be84acb80 Update readme for version 6.0 2020-10-14 22:41:40 +03:00
terrakok 4872d10d6a Add screen keys and update version to 6.0.3-dev 2020-07-26 00:39:01 +03:00
terrakok ac5efa84ed Use default FragmentFactory and update version to 6.0.2-dev 2020-06-10 22:04:30 +03:00
terrakok 3e982794ec Update version to 6.0.1-dev 2020-06-10 18:07:44 +03:00
terrakok a748473697 Refactor FragmentFactory usage and remove outdated sample. 2020-06-10 17:11:28 +03:00
terrakok 0734a4eb22 Refactor AppNavigator. 2020-05-24 23:21:12 +03:00
terrakok 748f2f657c Setup new publication. 2020-05-24 12:48:08 +03:00
terrakok 88884ac51e Migrate to kotlin. 2020-05-23 16:25:32 +03:00
terrakok 0b35127778 Add new parameter for clear container or not during navigation to new screen. 2020-05-21 23:32:45 +03:00
terrakok 9aeb02cfae Update version to 5.1.1 2020-05-10 17:23:43 +03:00
Konstantin 7c31cdc1bd Merge pull request #112 from adolgiy/hotfix/fragment-factory-npe
Fix NPE when localStackCopy.size() == 0 and FragmentFactory is used
2020-05-10 17:11:14 +03:00
Aleksey Dolgiy 05a8ec8f44 Extract 'forward' to reuse in replace when localStackCopy.size > 0 2020-02-20 10:29:18 +03:00
Aleksey Dolgiy d08e6b1afe Code Review: Use internal replace for fragmentForward(Forward) and add explicit exception with null checks 2020-02-19 20:46:31 +03:00
Aleksey Dolgiy cea829ae88 Fix NPE when localStackCopy.size() == 0 and FragmentFactory is used 2020-02-19 17:37:25 +03:00
Konstantin Tskhovrebov dcf1532103 Merge branch 'develop' 2020-02-11 14:42:17 +03:00
Konstantin Tskhovrebov 046410e132 Update version of Cicerone to "5.1.0". 2020-02-11 14:41:24 +03:00
Konstantin e80b2d5432 Merge pull request #111 from terrakok/handle_command_error
Add errorOnApplyCommand for ability manual error handling.
2020-02-11 14:38:09 +03:00
Konstantin Tskhovrebov 3f1cf714df Add errorOnApplyCommand for ability manual error handling. 2020-02-11 14:36:13 +03:00
Konstantin febae878a4 Merge pull request #99 from asitnkova/feature/add_nullability_annotations
Add androidx nullability annotations
2020-02-11 12:45:10 +03:00
Konstantin 6cc2d3e474 Merge branch 'develop' into feature/add_nullability_annotations 2020-02-11 12:42:04 +03:00
Konstantin dc2f732ec6 Merge pull request #110 from asitnkova/feature/add_new_methods_fragment_1.2.0
Feature/add new methods fragment 1.2.0
2020-02-11 12:38:47 +03:00
asitnkova 5d6880c95f Add support for new FragmentTransaction.replace overload 2020-02-04 22:34:10 +07:00
Alexander e1e2dd8b9b Bump build tools 3.5.0-alpha01 -> 3.5.0, gradle 5.1-milestone-1 -> 5.6.4 2020-02-04 22:32:51 +07:00
Konstantin 4df0f34e34 Merge pull request #101 from Anton111111/develop
Made properties protected to add ability use them in overridden methods.
2019-11-18 12:52:07 +03:00
anton f36f6a1e44 Made properties protected to add ability use them in overridden methods. 2019-09-17 19:42:11 +03:00
Anton Potekhin be63c91abe Made properties protected to add ability use them in overridden methods. 2019-02-07 09:36:05 +03:00
Alexander Sitnikov 8e9bf90ee6 Add nullability annotations 2019-01-26 17:31:29 +07:00
terrakok 0ba13efe29 Merge branch 'develop' 2019-01-25 22:13:32 +03:00
terrakok d5737bb092 Update version of Cicerone to "5.0.0". 2019-01-25 22:13:14 +03:00
terrakok 07869ad6e7 Update gradle plugin. 2019-01-25 22:09:19 +03:00
Konstantin a57b8ecb3c Merge pull request #91 from pihariev/fix/sample_chain_representation
Fix sample chain representation for ordinary navigation.
2018-11-05 22:58:21 +03:00
Konstantin 8e3f41057d Merge pull request #88 from pihariev/refactor/androidx
Refactor library and sample to AndroidX packaging system.
2018-11-05 22:12:00 +03:00
Roman Pihariev 7823dd0658 Fix sample chain representation for ordinary navigation. 2018-11-05 16:10:43 +02:00
Roman Pihariev 6b195590de Refactor library and sample to AndroidX packaging system. 2018-11-02 22:44:01 +02:00
Konstantin Tskhovrebov 95f29e0143 Merge branch 'develop' 2018-10-03 23:13:07 +03:00
Konstantin Tskhovrebov dd4fdb5b83 Update version to 4.0.2. 2018-10-03 23:11:43 +03:00
Konstantin Tskhovrebov 5ea14dc30c Merge branch 'master' into develop 2018-10-03 23:11:16 +03:00
Konstantin 3da51271a1 Merge pull request #82 from qwert2603/patch-1
Fix error in Router.java
2018-10-03 23:05:59 +03:00
Alexander Zhdanov e2a7493549 Fix error in Router.java
I make call:
router.newRootChain(new ScreenOne(), new ScreenTwo());
and get a exception 
        java.lang.ArrayIndexOutOfBoundsException: length=2; index=2
        at ru.terrakok.cicerone.Router.newRootChain(Router.java:91)
        at ...
I suppose, iteration bound must be equal to length of array "screens" (not "commands").
2018-10-03 17:56:05 +07:00
Konstantin Tskhovrebov f03ea7f60e Merge branch 'develop' 2018-10-01 22:59:26 +03:00
Konstantin Tskhovrebov 9919f6a73f Update version to 4.0.1. 2018-10-01 22:57:51 +03:00
Konstantin Tskhovrebov 6ff618272d Fix newRootChain command. 2018-10-01 22:49:25 +03:00
Konstantin Tskhovrebov 301f3f3db8 Merge branch 'develop' 2018-09-27 22:15:56 +03:00
Konstantin Tskhovrebov de961bcd24 Update version to 4.0.0. 2018-09-27 22:11:02 +03:00
Konstantin Tskhovrebov bccb7ffdce Add newRootChain method to Router. 2018-09-27 22:10:20 +03:00
Konstantin Tskhovrebov b82196ce8c Update readme and fix java docs. 2018-09-27 22:03:58 +03:00
Konstantin Tskhovrebov 1d733906c8 Add media for readme. 2018-09-27 21:57:08 +03:00
Konstantin Tskhovrebov 39305091b9 Update readme for new version. 2018-09-27 18:03:12 +03:00
Konstantin Tskhovrebov 8cbd987c7c Add media for documentation. 2018-09-27 17:11:52 +03:00
Konstantin Tskhovrebov 6b9b8ba992 Update gradle plugin. 2018-09-27 16:21:56 +03:00
Konstantin Tskhovrebov ef05b43b0d Add java docs for public methods. 2018-09-27 16:21:22 +03:00
Konstantin Tskhovrebov 7e316fa7ef Delete unused code. 2018-09-27 16:20:41 +03:00
Konstantin Tskhovrebov f7f45ad493 Change screen default id to "getCanonicalName". 2018-09-27 16:19:53 +03:00
Konstantin Tskhovrebov 62520744cb Fix "back to root" crash. 2018-09-22 21:31:48 +03:00
Konstantin Tskhovrebov 4a09e0083c Merge branch 'feature/screen_chain' into develop 2018-09-22 21:23:50 +03:00
Konstantin Tskhovrebov 74e9681a48 Add router method for opening screen chain. 2018-09-22 21:23:33 +03:00
Konstantin Tskhovrebov 3ef18015ee Fix library android dependencies. 2018-09-22 21:22:33 +03:00
Konstantin Tskhovrebov 163fc90fcd Merge branch 'feature/result_changes' into develop 2018-09-22 20:45:05 +03:00
Konstantin Tskhovrebov a920505915 Update sample for manual result listening. 2018-09-22 20:43:40 +03:00
Konstantin Tskhovrebov 88427b0271 Change pure android stubs on android source dependency. 2018-09-22 20:43:08 +03:00
Konstantin Tskhovrebov 3b8954b993 Remove result listener from Router. 2018-09-22 20:42:22 +03:00
Konstantin Tskhovrebov d9fd28fb2c Merge branch 'feature/screen_class' into develop 2018-09-20 15:29:10 +03:00
Konstantin Tskhovrebov f2977d599d Update sample for new library version. 2018-09-20 15:27:42 +03:00
Konstantin Tskhovrebov ccc8dd626c Update gradle plugin. 2018-09-20 12:48:51 +03:00
Konstantin Tskhovrebov c9f0b413cb Remove FragmentNavigator and create common AppNavigator. 2018-09-20 12:48:32 +03:00
Konstantin Tskhovrebov 47f5197f90 Add screen class (contains key and creation logic). 2018-09-09 02:51:03 +03:00
Konstantin Tskhovrebov d0b4e5c119 Update gradle plugin. 2018-09-09 01:11:56 +03:00
Konstantin f036217eb0 Merge pull request #69 from Aleksander3007/changed_pop
Changed pop() to removeLast().
2018-07-12 12:24:20 +03:00
aaermakov 29fcd3c834 Changed pop() to removeLast(). 2018-06-19 09:15:11 +03:00
Konstantin Tskhovrebov 92600e69b2 rename setupFragmentTransactionAnimation to setupFragmentTransaction. because it can be used not for only animations 2018-06-06 16:06:30 +03:00
Konstantin Tskhovrebov 9f41ec0d8f remove unused field MainActivity.screenNames 2018-06-06 14:30:25 +03:00
Konstantin Tskhovrebov 56fb9e82e9 fix: switch on main thread for forward command with delay 2018-06-06 14:28:56 +03:00
Konstantin Tskhovrebov 96c1ec0a25 fix invalid sorting for chain view on main activity 2018-06-06 14:27:52 +03:00
Konstantin Tskhovrebov d90f4ab3a1 Merge branch 'feature/remove_system_msg' into develop 2018-06-06 14:26:03 +03:00
Konstantin Tskhovrebov 07a956be3a remove SystemMessage command 2018-06-06 14:09:00 +03:00
Konstantin ad68431834 Update README.md 2018-01-21 14:53:35 +03:00
Konstantin Tskhovrebov 98b1ff265f Merge branch 'develop' 2018-01-08 13:40:26 +03:00
Konstantin Tskhovrebov 9464fae539 updated library version to 3.0.0 2018-01-08 13:24:26 +03:00
Konstantin db0061a416 Merge pull request #58 from Jeevuz/small_refactor_and_docs
Small refactor and docs
2017-12-27 11:31:04 +03:00
Konstantin 8b2b1e0137 Merge pull request #59 from Jeevuz/feature/screen_name_for_unexisting
Add a screen key to the backToUnexisting method.
2017-12-27 11:29:40 +03:00
Vasili Chyrvon 2ac7848bbb Add a screen key to the backToUnexisting method. 2017-12-25 00:41:43 +03:00
Vasili Chyrvon 29bc51db6f Add the licence file 2017-12-25 00:31:00 +03:00
Vasili Chyrvon e2f78e8185 Change readme 2017-12-25 00:27:43 +03:00
Vasili Chyrvon 3eb0302046 Made executeCommands package-private 2017-12-25 00:25:40 +03:00
Vasili Chyrvon 933fdd2715 Add readability improvements and javadocs. 2017-12-25 00:14:23 +03:00
Konstantin Tskhovrebov 143b144cbf updated gradle version 2017-12-23 00:47:17 +03:00
Konstantin Tskhovrebov eeb5d20b8c Merge branch 'feature/commands_batch' into develop 2017-12-23 00:28:56 +03:00
Konstantin Tskhovrebov fe79f30c25 removed blocking navigation (popBackStackImmediate) 2017-12-21 17:18:45 +03:00
Konstantin Tskhovrebov 0ee0df0bcc moved auth comment to file header for library package 2017-12-19 23:42:57 +03:00
Konstantin Tskhovrebov f665c263ee changed navigator logic for apply commands batch 2017-12-19 22:56:01 +03:00
Konstantin 835d6425be Merge pull request #51 from Jeevuz/feature/context_for_activity_intents
Add Context to the activity creation method.
2017-12-01 14:28:14 +03:00
Vasili Chyrvon 851535ab28 Fix signature of the createActivityIntent() method in the sample and readme. 2017-11-27 02:13:34 +03:00
Vasili Chyrvon e4bb13b025 Add Context to the activity creation method. 2017-11-24 14:45:11 +03:00
Konstantin 4e0f36d57a Merge pull request #49 from Jeevuz/check_activity_exists
Added check for the activity existence before opening.
2017-10-24 09:22:05 +03:00
Vasili Chyrvon 752c22c0b9 Added check for the activity existence before opening. 2017-10-23 17:25:23 +03:00
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
terrakok 427592e309 Merge branch 'develop' 2016-11-22 18:52:45 +03:00
terrakok b3e32c154f Merge pull request #13 from Jeevuz/develop
Upgrade to version 1.1
2016-11-22 19:51:08 +04:00
Vasili Chyrvon 69feb03db6 Upgrade to version 1.1 2016-11-22 18:39:31 +03:00
terrakok 1505ba87af Merge pull request #12 from Jeevuz/develop
Bages and new How to add part
2016-11-22 19:14:35 +04:00
Vasili Chyrvon 3dfb107a21 Changed russian readme 2016-11-22 18:10:38 +03:00
Vasili Chyrvon 458d9b053e Added badges 2016-11-22 18:04:58 +03:00
Vasili Chyrvon 93cecdee1d Published on jcenter. 2016-11-22 17:25:05 +03:00
terrakok 457c70e026 Merge pull request #10 from laomo/develop
Add app stub and remove android dependency
2016-11-22 12:38:39 +04:00
laomo e88b08d8ce Add app stub and remove android dependency 2016-11-22 16:04:49 +08:00
terrakok 4e896d3631 added the startup screen to show the transitions between Activities 2016-11-21 23:28:04 +03:00
terrakok a5404b0939 Merge pull request #7 from Jeevuz/patch-1
Update README.md
2016-11-20 08:12:40 +04:00
Vasili Chyrvon 4d096351cc Update README.md 2016-11-20 01:35:19 +03:00
116 changed files with 2487 additions and 1550 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.
+146 -123
View File
@@ -1,174 +1,197 @@
# Cicerone
[![jCenter](https://api.bintray.com/packages/terrakok/terramaven/cicerone/images/download.svg)](https://bintray.com/terrakok/terramaven/cicerone/_latestVersion)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Cicerone (a guide who gives information to sightseers) is a lightweight library that makes the navigation in an Android app easy.
It designed for using with MVP architecture (try [Moxy](https://github.com/Arello-Mobile/Moxy)), but it fits to work in other ways.
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Cicerone-green.svg?style=true)](https://android-arsenal.com/details/1/4700)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-250-green.svg)](http://androidweekly.net/issues/issue-250)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-271-green.svg)](http://androidweekly.net/issues/issue-271)
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
Cicerone (a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
It was designed to be used with the MVP/MVVM/MVI patterns but will work great with any architecture.
## Main advantages
+ not tied to Fragments
+ not framework
+ is not tied to Fragments
+ not a framework
+ short navigation calls (no builders)
+ lifecycle-safely!
+ functional is simple to extent
+ adapted for Unit Testing
+ lifecycle-safe!
+ functionality is simple to extend
+ suitable for Unit Testing
## How to connect?
Add the following lines to build.gradle:
```groovy
repositories {
maven {
url 'https://dl.bintray.com/terrakok/terramaven/'
}
}
## 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:
```kotlin
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:1.0'
implementation("com.github.terrakok:cicerone:X.X.X")
}
```
And initialise library for example with application:
```java
public class SampleApplication extends MvpApplication {
public static SampleApplication INSTANCE;
private Cicerone<Router> cicerone;
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
Initialize the library (for example in your Application class):
```kotlin
class App : Application() {
private val cicerone = Cicerone.create()
val router get() = cicerone.router
val navigatorHolder get() = cicerone.getNavigatorHolder()
initCicerone();
override fun onCreate() {
super.onCreate()
INSTANCE = this
}
private void initCicerone() {
cicerone = Cicerone.create();
}
public NavigatorHolder getNavigatorHolder() {
return cicerone.getNavigatorHolder();
}
public Router getRouter() {
return cicerone.getRouter();
companion object {
internal lateinit var INSTANCE: App
private set
}
}
```
## How it works?
![](https://habrastorage.org/files/4df/45d/973/4df45d9733fc4ee0a2f0be933de475b1.png)
<img src="https://github.com/terrakok/Cicerone/raw/develop/media/CiceroneDiagram.png" alt="drawing" width="800"/>
Presenter calls navigation method of Router.
```java
public class SamplePresenter extends Presenter<SampleView> {
private Router router;
```kotlin
class SamplePresenter(
private val router: Router
) : Presenter<SampleView>() {
public SamplePresenter() {
router = SampleApplication.INSTANCE.getRouter();
fun onOpenNewScreen() {
router.navigateTo(SomeScreen())
}
public void onBackCommandClick() {
router.exit();
}
public void onForwardCommandClick() {
router.navigateTo("Some screen");
fun onBackPressed() {
router.exit()
}
}
```
Router converts the navigation calls to Comand sets and sends them to CommandBuffer.
Command Buffer checks whether there _"active"_ Navigator.
If yes, it caused the necessary commands for the requested transfer.
If not, the command added to the queue, which will be applied as soon as _"active"_ Navigator.
Router converts the navigation call to the set of Commands and sends them to CommandBuffer.
```java
protected void executeCommand(Command command) {
if (navigator != null) {
navigator.applyCommand(command);
} else {
pendingCommands.add(command);
}
CommandBuffer checks whether there are _"active"_ Navigator:
- If yes, it passes the commands to the Navigator. Navigator will process them to achive the desired transition.
- If no, then CommandBuffer saves the commands in a queue, and will apply them as soon as new _"active"_ Navigator will appear.
```kotlin
fun executeCommands(commands: Array<out Command>) {
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
}
```
Navigator - implements the navigation commands, e.g. anonymous class inside the Activity.
Activity provides Navigator for CommandBuffer in _onResume_ and remove 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) {
//implements commands logic
}
};
```
## Navigation commands
These command set will fulfill needs of the most application. But if you require more - supply your own!
+ Forward - Opens new screen
![](https://habrastorage.org/files/862/77e/b20/86277eb20b574dae8307ac4f64b0f090.png)
+ Back - Rolls back the last transition from the screens chain
![](https://habrastorage.org/files/059/b63/2d3/059b632d3a7c4515a534b9e5e881c8f0.png)
+ BackTo - Rolls back to the needed screen from 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)
This commands set will fulfill the needs of the most applications. But if you need something special - just add it!
+ Forward - Opens new screen
![](https://github.com/terrakok/Cicerone/raw/develop/media/forward_img.png)
+ Back - Rolls back the last transition
![](https://github.com/terrakok/Cicerone/raw/develop/media/back_img.png)
+ BackTo - Rolls back to the needed screen in the screens chain
![](https://github.com/terrakok/Cicerone/raw/develop/media/backTo_img.png)
+ Replace - Replaces the current screen
![](https://github.com/terrakok/Cicerone/raw/develop/media/replace_img.png)
## Ready navigators
The library provides ready navigators for _Activity_.
To use them, just give the container and pass _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();
}
};
## 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
Library usage examples, ready navigators and more can be found in the sample application.
![](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 {
val Main = FragmentScreen("MainFragment") { MainFragment() }
val AddressSearch = FragmentScreen("AddressSearchFragment") { AddressSearchFragment() }
fun Profile(userId: Long) = FragmentScreen("ProfileFragment") { ProfileFragment(userId) }
}
```
Additional you can use `FragmentFactory` for creating your screens:
```kotlin
val SomeScreen = FragmentScreen("SomeScreenId") { factory: FragmentFactory -> ... }
```
## Sample
To see how to add, initialize and use the library and predefined navigators check out
the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
![](https://github.com/terrakok/Cicerone/raw/develop/media/navigation.gif)
![](https://github.com/terrakok/Cicerone/raw/develop/media/insta_tabs.gif)
![](https://github.com/terrakok/Cicerone/raw/develop/media/animations.gif)
## Participants
+ idea and realization - Konstantin Tckhovrebov (@terrakok)
+ architectural advice, documentation and publication - Vasily Chirvon (@Jeevuz)
+ 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.
```
-176
View File
@@ -1,176 +0,0 @@
# Cicerone
Cicerone (_"чи-че-ро́-не"_ - устар. гид) - легкая библиотека для простой реализации навигации в андроид приложении.
Разработана для использования в MVP архитектуре (попробуйте [Moxy](https://github.com/Arello-Mobile/Moxy)), но легко встраивается в любые решения.
## Основные преимущества
+ не завязана на Fragment'ы
+ не фреймворк
+ короткие вызовы навигации (никаких билдеров)
+ lifecycle-безопасна!
+ простое расширение функционала
+ приспособлена для Unit тестов
## Как подключить?
Добавьте в build.gradle следующие строки:
```groovy
repositories {
maven {
url 'https://dl.bintray.com/terrakok/terramaven/'
}
}
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:1.0'
}
```
И инициализируйте библиотеку, например, так:
```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
+26 -9
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.
@@ -6,31 +7,32 @@ sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
compileOnly 'com.google.android:android:4.0.1.2'
compileOnly project(':stub-appcompat')
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.0'
libraryVersion = '6.0'
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'
}
@@ -38,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,19 @@
package com.github.terrakok.cicerone
/**
* BaseRouter is an abstract class to implement high-level navigation.
*
* Extend it to add needed transition methods.
*/
abstract class BaseRouter {
internal val commandBuffer = CommandBuffer()
/**
* Sends navigation command array to [CommandBuffer].
*
* @param commands navigation command array to execute
*/
protected fun executeCommands(vararg commands: Command) {
commandBuffer.executeCommands(commands)
}
}
@@ -0,0 +1,28 @@
package com.github.terrakok.cicerone
/**
* Cicerone is the holder for other library components.
*
* To use it, instantiate it using one of the {@link #create()} methods.
*
* When you need a [NavigatorHolder] or router, get it here.
*
* @param router type of router. You can use the default [Router] or pass your own [BaseRouter] implementation.
*/
class Cicerone<T : BaseRouter> private constructor(val router: T) {
fun getNavigatorHolder(): NavigatorHolder = router.commandBuffer
companion object {
/**
* Creates the Cicerone instance with the default [Router]
*/
fun create() = create(Router())
/**
* Creates the Cicerone instance with the custom router.
* @param customRouter the custom router extending [BaseRouter]
*/
fun <T : BaseRouter> create(customRouter: T) = Cicerone(customRouter)
}
}
@@ -0,0 +1,29 @@
package com.github.terrakok.cicerone
/**
* Passes navigation command to an active [Navigator]
* or stores it in the pending commands queue to pass it later.
*/
internal class CommandBuffer : NavigatorHolder {
private var navigator: Navigator? = null
private val pendingCommands = mutableListOf<Array<out Command>>()
override fun setNavigator(navigator: Navigator) {
this.navigator = navigator
pendingCommands.forEach { navigator.applyCommands(it) }
pendingCommands.clear()
}
override fun removeNavigator() {
navigator = null
}
/**
* Passes `commands` to the [Navigator] if it available.
* Else puts it to the pending commands queue to pass it later.
* @param commands navigation command array
*/
fun executeCommands(commands: Array<out Command>) {
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
}
}
@@ -0,0 +1,14 @@
package com.github.terrakok.cicerone
/**
* The low-level navigation interface.
* Navigator is the one who actually performs any transition.
*/
interface Navigator {
/**
* Performs transition described by the navigation command
*
* @param commands the navigation command array to apply per single transaction
*/
fun applyCommands(commands: Array<out Command>)
}
@@ -0,0 +1,20 @@
package com.github.terrakok.cicerone
/**
* Navigator holder interface.
*
* Use it to connect a [Navigator] to the [Cicerone].
*/
interface NavigatorHolder {
/**
* Set an active Navigator for the Cicerone and start receive commands.
*
* @param navigator new active Navigator
*/
fun setNavigator(navigator: Navigator)
/**
* Remove the current Navigator and stop receive commands.
*/
fun removeNavigator()
}
@@ -0,0 +1,95 @@
package com.github.terrakok.cicerone
/**
* Router is the class for high-level navigation.
*
* Use it to perform needed transitions.
* This implementation covers almost all cases needed for the average app.
* Extend it if you need some tricky navigation.
*/
open class Router : BaseRouter() {
/**
* Open new screen and add it to the screens chain.
*
* @param screen screen
* @param clearContainer if FALSE then new screen shows over previous
*/
fun navigateTo(screen: Screen, clearContainer: Boolean = true) {
executeCommands(Forward(screen, clearContainer))
}
/**
* Clear all screens and open new one as root.
*
* @param screen screen
*/
fun newRootScreen(screen: Screen) {
executeCommands(BackTo(null), Replace(screen))
}
/**
* Replace current screen.
*
* By replacing the screen, you alters the backstack,
* so by going fragmentBack you will return to the previous screen
* and not to the replaced one.
*
* @param screen screen
*/
fun replaceScreen(screen: Screen) {
executeCommands(Replace(screen))
}
/**
* Return fragmentBack to the needed screen from the chain.
*
* Behavior in the case when no needed screens found depends on
* the processing of the [BackTo] command in a [Navigator] implementation.
*
* @param screen screen
*/
fun backTo(screen: Screen?) {
executeCommands(BackTo(screen))
}
/**
* Opens several screens inside single transaction.
*
* @param screens
* @param showOnlyTopScreenView if FALSE then all screen views show together
*/
fun newChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
executeCommands(*commands.toTypedArray())
}
/**
* Clear current stack and open several screens inside single transaction.
*
* @param screens
* @param showOnlyTopScreenView if FALSE then all screen views show together
*/
fun newRootChain(vararg screens: Screen, showOnlyTopScreenView: Boolean = true) {
val commands = screens.map { Forward(it, showOnlyTopScreenView) }
executeCommands(BackTo(null), *commands.toTypedArray())
}
/**
* Remove all screens from the chain and exit.
*
* It's mostly used to finish the application or close a supplementary navigation chain.
*/
fun finishChain() {
executeCommands(BackTo(null), Back())
}
/**
* Return to the previous screen in the chain.
*
* Behavior in the case when the current screen is the root depends on
* the processing of the [Back] command in a [Navigator] implementation.
*/
fun exit() {
executeCommands(Back())
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone
/**
* Screen is class for description application screen.
*/
abstract class Screen {
open val screenKey: String = this::class.qualifiedName!!
}
@@ -0,0 +1,215 @@
package com.github.terrakok.cicerone.androidx
import android.content.Intent
import androidx.fragment.app.*
import com.github.terrakok.cicerone.*
import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.ADD
import com.github.terrakok.cicerone.androidx.TransactionInfo.Type.REPLACE
/**
* Navigator implementation for launch fragments and activities.
*
* Feature [BackTo] works only for fragments.
*
* Recommendation: most useful for Single-Activity application.
*/
open class AppNavigator constructor(
protected val activity: FragmentActivity,
protected val containerId: Int,
protected val fragmentManager: FragmentManager = activity.supportFragmentManager,
protected val fragmentFactory: FragmentFactory = FragmentFactory()
) : Navigator {
protected val localStackCopy = mutableListOf<TransactionInfo>()
override fun applyCommands(commands: Array<out Command>) {
fragmentManager.executePendingTransactions()
//copy stack before apply commands
copyStackToLocal()
for (command in commands) {
try {
applyCommand(command)
} catch (e: RuntimeException) {
errorOnApplyCommand(command, e)
}
}
}
private fun copyStackToLocal() {
localStackCopy.clear()
for (i in 0 until fragmentManager.backStackEntryCount) {
val str = fragmentManager.getBackStackEntryAt(i).name
localStackCopy.add(TransactionInfo.fromString(str))
}
}
/**
* Perform transition described by the navigation command
*
* @param command the navigation command to apply
*/
protected open fun applyCommand(command: Command) {
when (command) {
is Forward -> forward(command)
is Replace -> replace(command)
is BackTo -> backTo(command)
is Back -> back()
}
}
protected open fun forward(command: Forward) {
when (val screen = command.screen as AppScreen) {
is ActivityScreen -> {
checkAndStartActivity(screen)
}
is FragmentScreen -> {
val type = if (command.clearContainer) REPLACE else ADD
commitNewFragmentScreen(screen, type, true)
}
}
}
protected open fun replace(command: Replace) {
when (val screen = command.screen as AppScreen) {
is ActivityScreen -> {
checkAndStartActivity(screen)
activity.finish()
}
is FragmentScreen -> {
if (localStackCopy.isNotEmpty()) {
fragmentManager.popBackStack()
val removed = localStackCopy.removeAt(localStackCopy.lastIndex)
commitNewFragmentScreen(screen, removed.type, true)
} else {
commitNewFragmentScreen(screen, REPLACE, false)
}
}
}
}
protected open fun back() {
if (localStackCopy.isNotEmpty()) {
fragmentManager.popBackStack()
localStackCopy.removeAt(localStackCopy.lastIndex)
} else {
activityBack()
}
}
protected open fun activityBack() {
activity.finish()
}
protected open fun commitNewFragmentScreen(
screen: FragmentScreen,
type: TransactionInfo.Type,
addToBackStack: Boolean
) {
val fragment = screen.createFragment(fragmentFactory)
val transaction = fragmentManager.beginTransaction()
setupFragmentTransaction(
transaction,
fragmentManager.findFragmentById(containerId),
fragment
)
when (type) {
ADD -> transaction.add(containerId, fragment)
REPLACE -> transaction.replace(containerId, fragment)
}
if (addToBackStack) {
val transactionInfo = TransactionInfo(screen.screenKey, type)
transaction.addToBackStack(transactionInfo.toString())
localStackCopy.add(transactionInfo)
}
transaction.commit()
}
/**
* Performs [BackTo] command transition
*/
protected open fun backTo(command: BackTo) {
if (command.screen == null) {
backToRoot()
} else {
val screenKey = command.screen.screenKey
val index = localStackCopy.indexOfFirst { it.screenKey == screenKey }
if (index != -1) {
val forRemove = localStackCopy.subList(index, localStackCopy.size)
fragmentManager.popBackStack(forRemove.first().toString(), 0)
forRemove.clear()
} else {
backToUnexisting(command.screen as AppScreen)
}
}
}
private fun backToRoot() {
localStackCopy.clear()
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
/**
* Override this method to setup fragment transaction [FragmentTransaction].
* For example: setCustomAnimations(...), addSharedElement(...) or setReorderingAllowed(...)
*
* @param fragmentTransaction fragment transaction
* @param currentFragment current fragment in container
* (for [Replace] command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
*/
protected open fun setupFragmentTransaction(
fragmentTransaction: FragmentTransaction,
currentFragment: Fragment?,
nextFragment: Fragment?
) {
// Do nothing by default
}
private fun checkAndStartActivity(screen: ActivityScreen) {
// Check if we can start activity
val activityIntent = screen.createIntent(activity)
if (activityIntent.resolveActivity(activity.packageManager) != null) {
activity.startActivity(activityIntent, screen.startActivityOptions)
} else {
unexistingActivity(screen, activityIntent)
}
}
/**
* Called when there is no activity to open `screenKey`.
*
* @param screen screen
* @param activityIntent intent passed to start Activity for the `screenKey`
*/
protected open fun unexistingActivity(
screen: ActivityScreen,
activityIntent: Intent
) {
// Do nothing by default
}
/**
* Called when we tried to fragmentBack to some specific screen (via [BackTo] command),
* but didn't found it.
*
* @param screen screen
*/
protected open fun backToUnexisting(screen: AppScreen) {
backToRoot()
}
/**
* Override this method if you want to handle apply command error.
*
* @param command command
* @param error error
*/
protected open fun errorOnApplyCommand(
command: Command,
error: RuntimeException
) {
throw error
}
}
@@ -0,0 +1,22 @@
package com.github.terrakok.cicerone.androidx
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import com.github.terrakok.cicerone.Screen
sealed class AppScreen : Screen()
open class FragmentScreen(
override val screenKey: String,
val createFragment: (FragmentFactory) -> Fragment
) : AppScreen()
open class ActivityScreen(
override val screenKey: String,
val createIntent: (context: Context) -> Intent
) : AppScreen() {
open val startActivityOptions: Bundle? = null
}
@@ -0,0 +1,20 @@
package com.github.terrakok.cicerone.androidx
data class TransactionInfo(
val screenKey: String,
val type: Type
) {
enum class Type(val symbol: Char) {
ADD('+'),
REPLACE('-');
}
override fun toString() = screenKey + type.symbol
companion object {
fun fromString(str: String) = TransactionInfo(
str.dropLast(1),
if (str.last() == Type.ADD.symbol) Type.ADD else Type.REPLACE
)
}
}
@@ -0,0 +1,34 @@
package com.github.terrakok.cicerone
/**
* Navigation command describes screens transition.
*
* That can be processed by [com.github.terrakok.cicerone.Navigator]
*/
interface Command
/**
* Opens new screen.
*/
data class Forward(
val screen: Screen,
val clearContainer: Boolean
) : Command
/**
* Replaces the current screen.
*/
data class Replace(val screen: Screen) : Command
/**
* Rolls fragmentBack the last transition from the screens chain.
*/
class Back : Command
/**
* Rolls fragmentBack to the needed screen from the screens chain.
*
* Behavior in the case when no needed screens found depends on an implementation of the [com.github.terrakok.cicerone.Navigator]
* But the recommended behavior is to return to the root.
*/
data class BackTo(val screen: Screen?) : Command
@@ -4,5 +4,5 @@ sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
compileOnly 'com.google.android:android:4.0.1.2'
compileOnly 'com.google.android:android:4.1.1.4'
}
@@ -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,9 @@ public class FragmentManager {
throw new RuntimeException("Stub!");
}
public Fragment findFragmentById(int id) {
throw new RuntimeException("Stub!");
}
public interface BackStackEntry {
int getId();
@@ -0,0 +1,40 @@
package androidx.fragment.app;
import android.os.Bundle;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentTransaction {
public FragmentTransaction add(int containerViewId, Fragment fragment) {
throw new RuntimeException("Stub!");
}
public final FragmentTransaction add(
int containerViewId,
Class<? extends Fragment> fragmentClass,
Bundle args) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
throw new RuntimeException("Stub!");
}
public final FragmentTransaction replace(
int containerViewId,
Class<? extends Fragment> fragmentClass,
Bundle args) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction addToBackStack(String name) {
throw new RuntimeException("Stub!");
}
public int commit() {
throw new RuntimeException("Stub!");
}
}
@@ -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!");
}
}
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"
}
+9 -5
View File
@@ -1,21 +1,25 @@
<?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:theme="@style/AppTheme"
android:label="@string/app_name">
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".ui.main.MainActivity">
android:name=".ui.start.StartActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ui.main.MainActivity"/>
<activity android:name=".ui.bottom.BottomNavigationActivity"/>
<activity android:name=".ui.animations.ProfileActivity"/>
</application>
</manifest>
@@ -0,0 +1,25 @@
package com.github.terrakok.cicerone.sample
import android.app.Application
import com.github.terrakok.cicerone.sample.dagger.AppComponent
import com.github.terrakok.cicerone.sample.dagger.DaggerAppComponent
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class SampleApplication : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent.builder().build()
}
override fun onCreate() {
super.onCreate()
INSTANCE = this
}
companion object {
lateinit var INSTANCE: SampleApplication
}
}
@@ -0,0 +1,62 @@
package com.github.terrakok.cicerone.sample
import android.content.Intent
import android.net.Uri
import com.github.terrakok.cicerone.androidx.ActivityScreen
import com.github.terrakok.cicerone.androidx.FragmentScreen
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
import com.github.terrakok.cicerone.sample.ui.bottom.ForwardFragment
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
object Screens {
fun sampleScreen(number: Int) = FragmentScreen("SampleScreen_$number") {
SampleFragment.getNewInstance(number)
}
fun startScreen() = ActivityScreen("StartScreen") {
Intent(it, StartActivity::class.java)
}
fun mainScreen() = ActivityScreen("MainScreen") {
Intent(it, MainActivity::class.java)
}
fun bottomNavigationScreen() = ActivityScreen("BottomNavigationScreen") {
Intent(it, BottomNavigationActivity::class.java)
}
fun tabScreen(tabName: String) = FragmentScreen("FragmentScreen") {
TabContainerFragment.getNewInstance(tabName)
}
fun forwardScreen(containerName: String, number: Int) = FragmentScreen("ForwardScreen") {
ForwardFragment.getNewInstance(containerName, number)
}
fun githubScreen() = ActivityScreen("GithubScreen") {
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"))
}
fun profileScreen() = ActivityScreen("ProfileScreen") {
Intent(it, ProfileActivity::class.java)
}
fun profileInfoScreen() = FragmentScreen("ProfileInfoScreen") {
ProfileFragment()
}
fun selectPhotoScreen() = FragmentScreen("SelectPhotoScreen") {
SelectPhotoFragment()
}
}
@@ -0,0 +1,42 @@
package com.github.terrakok.cicerone.sample.dagger
import com.github.terrakok.cicerone.sample.dagger.module.LocalNavigationModule
import com.github.terrakok.cicerone.sample.dagger.module.NavigationModule
import com.github.terrakok.cicerone.sample.dagger.module.PhotoSelectionModule
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
import dagger.Component
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Singleton
@Component(modules = [
NavigationModule::class,
LocalNavigationModule::class,
PhotoSelectionModule::class]
)
interface AppComponent {
fun inject(activity: StartActivity)
fun inject(activity: MainActivity)
fun inject(fragment: SampleFragment)
fun inject(activity: BottomNavigationActivity)
fun inject(fragment: TabContainerFragment)
fun inject(fragment: ProfileFragment)
fun inject(fragment: SelectPhotoFragment)
fun inject(activity: ProfileActivity)
}
@@ -0,0 +1,17 @@
package com.github.terrakok.cicerone.sample.dagger.module
import com.github.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
object LocalNavigationModule {
@Provides
@Singleton
fun provideLocalNavigationHolder(): LocalCiceroneHolder = LocalCiceroneHolder()
}
@@ -0,0 +1,29 @@
package com.github.terrakok.cicerone.sample.dagger.module
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.Cicerone.Companion.create
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Router
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
class NavigationModule {
private val cicerone: Cicerone<Router> = create()
@Provides
@Singleton
fun provideRouter(): Router {
return cicerone.router
}
@Provides
@Singleton
fun provideNavigatorHolder(): NavigatorHolder {
return cicerone.getNavigatorHolder()
}
}
@@ -0,0 +1,20 @@
package com.github.terrakok.cicerone.sample.dagger.module
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Module
object PhotoSelectionModule {
private val photoSelection = PhotoSelection(R.drawable.ava_1)
@Provides
@Singleton
fun providePhotoSelection() = photoSelection
}
@@ -0,0 +1,26 @@
package com.github.terrakok.cicerone.sample.mvp.animation
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 22.09.18.
*/
class PhotoSelection(private var selectedPhoto: Int) {
private var listener: Listener? = null
fun getSelectedPhoto(): Int {
return selectedPhoto
}
fun setSelectedPhoto(selectedPhoto: Int) {
this.selectedPhoto = selectedPhoto
listener?.onChange(selectedPhoto)
}
fun setListener(listener: Listener?) {
this.listener = listener
}
interface Listener {
fun onChange(selectedPhoto: Int)
}
}
@@ -0,0 +1,36 @@
package com.github.terrakok.cicerone.sample.mvp.animation.photos
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class SelectPhotoPresenter(
private val photoSelection: PhotoSelection,
private val router: Router
) : MvpPresenter<SelectPhotoView>() {
override fun onFirstViewAttach() {
super.onFirstViewAttach()
viewState!!.showPhotos(intArrayOf(
R.drawable.ava_1,
R.drawable.ava_2,
R.drawable.ava_3,
R.drawable.ava_4
))
}
fun onPhotoClick(photoRes: Int) {
photoSelection.setSelectedPhoto(photoRes)
router.exit()
}
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.animation.photos
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface SelectPhotoView : MvpView {
fun showPhotos(resourceIds: IntArray)
}
@@ -0,0 +1,47 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.selectPhotoScreen
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class ProfilePresenter(
private val photoSelection: PhotoSelection,
private val router: Router
) : MvpPresenter<ProfileView>() {
override fun onFirstViewAttach() {
super.onFirstViewAttach()
updatePhoto()
}
override fun onDestroy() {
photoSelection.setListener(null)
super.onDestroy()
}
private fun updatePhoto() {
viewState!!.showPhoto(photoSelection.getSelectedPhoto())
}
fun onPhotoClicked() {
router.navigateTo(selectPhotoScreen())
}
fun onBackPressed() {
router.exit()
}
init {
photoSelection.setListener(object : PhotoSelection.Listener {
override fun onChange(selectedPhoto: Int) {
updatePhoto()
}
})
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface ProfileView : MvpView {
fun showPhoto(resId: Int)
}
@@ -0,0 +1,18 @@
package com.github.terrakok.cicerone.sample.mvp.bottom
import com.github.terrakok.cicerone.Router
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by terrakok 25.11.16
*/
@InjectViewState
class BottomNavigationPresenter(
private val router: Router
) : MvpPresenter<BottomNavigationView>() {
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,11 @@
package com.github.terrakok.cicerone.sample.mvp.bottom
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by terrakok 25.11.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface BottomNavigationView : MvpView
@@ -0,0 +1,42 @@
package com.github.terrakok.cicerone.sample.mvp.bottom.forward
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.forwardScreen
import com.github.terrakok.cicerone.sample.Screens.githubScreen
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by terrakok 26.11.16
*/
@InjectViewState
class ForwardPresenter(
private val container: String,
private val router: Router,
private val number: Int
) : MvpPresenter<ForwardView>() {
private fun createChain(number: Int): String {
var chain = "[0]"
for (i in 0 until number) {
chain += "" + (i + 1)
}
return chain
}
fun onForwardPressed() {
router.navigateTo(forwardScreen(container, number + 1))
}
fun onGithubPressed() {
router.navigateTo(githubScreen())
}
fun onBackPressed() {
router.exit()
}
init {
viewState?.setChainText(createChain(number))
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.bottom.forward
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by terrakok 26.11.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface ForwardView : MvpView {
fun setChainText(chainText: String)
}
@@ -0,0 +1,71 @@
package com.github.terrakok.cicerone.sample.mvp.main
import android.os.Handler
import android.os.Looper
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.sampleScreen
import moxy.InjectViewState
import moxy.MvpPresenter
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@InjectViewState
class SamplePresenter(
private val router: Router,
private val screenNumber: Int
) : MvpPresenter<SampleView>() {
private val executorService = Executors.newSingleThreadScheduledExecutor()
private var future: ScheduledFuture<*>? = null
fun onBackCommandClick() {
router.exit()
}
fun onForwardCommandClick() {
router.navigateTo(sampleScreen(screenNumber + 1))
}
fun onReplaceCommandClick() {
router.replaceScreen(sampleScreen(screenNumber + 1))
}
fun onNewChainCommandClick() {
router.newChain(
sampleScreen(screenNumber + 1),
sampleScreen(screenNumber + 2),
sampleScreen(screenNumber + 3)
)
}
fun onFinishChainCommandClick() {
router.finishChain()
}
fun onNewRootCommandClick() {
router.newRootScreen(sampleScreen(screenNumber + 1))
}
fun onForwardWithDelayCommandClick() {
future?.cancel(true)
future = executorService.schedule({ //WARNING! Navigation must be only in UI thread.
Handler(Looper.getMainLooper()).post {
router.navigateTo(sampleScreen(screenNumber + 1))
}
}, 5, TimeUnit.SECONDS)
}
fun onBackToCommandClick() {
router.backTo(sampleScreen(3))
}
init {
viewState?.setTitle("Screen $screenNumber")
}
}
@@ -0,0 +1,14 @@
package com.github.terrakok.cicerone.sample.mvp.main
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface SampleView : MvpView {
fun setTitle(title: String)
}
@@ -0,0 +1,30 @@
package com.github.terrakok.cicerone.sample.mvp.start
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.bottomNavigationScreen
import com.github.terrakok.cicerone.sample.Screens.mainScreen
import com.github.terrakok.cicerone.sample.Screens.profileScreen
import moxy.MvpPresenter
/**
* Created by terrakok 21.11.16
*/
class StartActivityPresenter(private val router: Router) : MvpPresenter<StartActivityView>() {
fun onOrdinaryPressed() {
router.navigateTo(mainScreen())
}
fun onMultiPressed() {
router.navigateTo(bottomNavigationScreen())
}
fun onResultWithAnimationPressed() {
router.navigateTo(profileScreen())
}
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone.sample.mvp.start
import moxy.MvpView
/**
* Created by terrakok 21.11.16
*/
interface StartActivityView : MvpView
@@ -0,0 +1,18 @@
package com.github.terrakok.cicerone.sample.subnavigation
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.Cicerone.Companion.create
import com.github.terrakok.cicerone.Router
import java.util.*
/**
* Created by terrakok 27.11.16
*/
class LocalCiceroneHolder {
private val containers = HashMap<String, Cicerone<Router>>()
fun getCicerone(containerTag: String): Cicerone<Router> =
containers.getOrPut(containerTag) {
create()
}
}
@@ -0,0 +1,89 @@
package com.github.terrakok.cicerone.sample.ui.animations
import android.os.Bundle
import android.transition.ChangeBounds
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.github.terrakok.cicerone.Command
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Replace
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.profileInfoScreen
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class ProfileActivity : AppCompatActivity() {
@Inject
lateinit var navigatorHolder: NavigatorHolder
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_container)
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(profileInfoScreen())))
}
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
private val navigator: Navigator = object : AppNavigator(this, R.id.container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
override fun setupFragmentTransaction(fragmentTransaction: FragmentTransaction, currentFragment: Fragment?, nextFragment: Fragment?) {
if (currentFragment is ProfileFragment
&& nextFragment is SelectPhotoFragment) {
setupSharedElementForProfileToSelectPhoto(
currentFragment,
nextFragment,
fragmentTransaction
)
}
}
}
private fun setupSharedElementForProfileToSelectPhoto(profileFragment: ProfileFragment,
selectPhotoFragment: SelectPhotoFragment,
fragmentTransaction: FragmentTransaction) {
val changeBounds = ChangeBounds()
selectPhotoFragment.sharedElementEnterTransition = changeBounds
selectPhotoFragment.sharedElementReturnTransition = changeBounds
profileFragment.sharedElementEnterTransition = changeBounds
profileFragment.sharedElementReturnTransition = changeBounds
val view = profileFragment.avatarViewForAnimation
fragmentTransaction.addSharedElement(view!!, PHOTO_TRANSITION)
selectPhotoFragment.setAnimationDestinationId((view.tag as Int))
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentById(R.id.container)
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
super.onBackPressed()
}
}
companion object {
const val PHOTO_TRANSITION = "photo_trasition"
}
}
@@ -0,0 +1,104 @@
package com.github.terrakok.cicerone.sample.ui.animations.photos
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentSelectPhotoBinding
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoPresenter
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class SelectPhotoFragment : MvpAppCompatFragment(), SelectPhotoView, BackButtonListener {
private lateinit var binding: FragmentSelectPhotoBinding
private lateinit var allImages: Array<ImageView>
@Inject
lateinit var router: Router
@Inject
lateinit var photoSelection: PhotoSelection
@InjectPresenter
lateinit var presenter: SelectPhotoPresenter
fun setAnimationDestinationId(resId: Int) {
var arguments = arguments
if (arguments == null) arguments = Bundle()
arguments.putInt(ARG_ANIM_DESTINATION, resId)
setArguments(arguments)
}
private val animationDestionationId: Int
get() = arguments!!.getInt(ARG_ANIM_DESTINATION)
@ProvidePresenter
fun providePresenter() = SelectPhotoPresenter(photoSelection, router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSelectPhotoBinding.inflate(inflater, container, false)
allImages = arrayOf(
binding.selectImage1,
binding.selectImage2,
binding.selectImage3,
binding.selectImage4
)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
allImages.forEach {
it.setOnClickListener(clickListener)
}
}
private val clickListener = View.OnClickListener { v ->
allImages.forEach {
it.transitionName = null
}
v.transitionName = ProfileActivity.PHOTO_TRANSITION
presenter.onPhotoClick((v.tag as Int))
}
override fun showPhotos(resourceIds: IntArray) {
if (resourceIds.size >= 4) {
//for shared element animation
val animRes = animationDestionationId
allImages.forEachIndexed {
index, imageView ->
imageView.setImageResource(resourceIds[index])
imageView.tag = resourceIds[index]
imageView.transitionName = if (animRes == resourceIds[index]) ProfileActivity.PHOTO_TRANSITION else null
}
}
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val ARG_ANIM_DESTINATION = "arg_anim_dest"
}
}
@@ -0,0 +1,74 @@
package com.github.terrakok.cicerone.sample.ui.animations.profile
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.mvp.animation.PhotoSelection
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfilePresenter
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfileView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class ProfileFragment : MvpAppCompatFragment(), ProfileView, BackButtonListener {
private lateinit var avatar: ImageView
@Inject
lateinit var router: Router
@InjectPresenter
lateinit var presenter: ProfilePresenter
@Inject
lateinit var photoSelection: PhotoSelection
@ProvidePresenter
fun providePresenter() = ProfilePresenter(photoSelection, router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_profile, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
avatar = view.findViewById<View>(R.id.avatar_imageView) as ImageView
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
avatar.transitionName = ProfileActivity.PHOTO_TRANSITION
avatar.setOnClickListener { presenter.onPhotoClicked() }
}
override fun showPhoto(resId: Int) {
avatar.setImageResource(resId)
//for shared element animation
avatar.tag = resId
}
val avatarViewForAnimation: View?
get() = avatar
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
}
@@ -0,0 +1,119 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.tabScreen
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import moxy.MvpAppCompatActivity
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by terrakok 25.11.16
*/
class BottomNavigationActivity : MvpAppCompatActivity(), BottomNavigationView, RouterProvider {
private lateinit var bottomNavigationBar: BottomNavigationBar
@Inject
override lateinit var router: Router
@InjectPresenter
lateinit var presenter: BottomNavigationPresenter
@ProvidePresenter
fun createBottomNavigationPresenter() = BottomNavigationPresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bottom)
bottomNavigationBar = findViewById<View>(R.id.ab_bottom_navigation_bar) as BottomNavigationBar
initViews()
if (savedInstanceState == null) {
bottomNavigationBar.selectTab(0, true)
}
}
private fun initViews() {
bottomNavigationBar
.addItem(BottomNavigationItem(R.drawable.ic_android_white_24dp, R.string.tab_android))
.addItem(BottomNavigationItem(R.drawable.ic_bug_report_white_24dp, R.string.tab_bug))
.addItem(BottomNavigationItem(R.drawable.ic_pets_white_24dp, R.string.tab_dog))
.initialise()
bottomNavigationBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabSelected(position: Int) {
when (position) {
0 -> selectTab("ANDROID")
1 -> selectTab("BUG")
2 -> selectTab("DOG")
}
bottomNavigationBar.selectTab(position, false)
}
override fun onTabUnselected(position: Int) {}
override fun onTabReselected(position: Int) {
onTabSelected(position)
}
})
}
private fun selectTab(tab: String) {
val fm = supportFragmentManager
var currentFragment: Fragment? = null
val fragments = fm.fragments
for (f in fragments) {
if (f.isVisible) {
currentFragment = f
break
}
}
val newFragment = fm.findFragmentByTag(tab)
if (currentFragment != null && newFragment != null && currentFragment === newFragment) return
val transaction = fm.beginTransaction()
if (newFragment == null) {
transaction.add(
R.id.ab_container,
tabScreen(tab).createFragment.invoke(fm.fragmentFactory), tab
)
}
if (currentFragment != null) {
transaction.hide(currentFragment)
}
if (newFragment != null) {
transaction.show(newFragment)
}
transaction.commitNow()
}
override fun onBackPressed() {
val fm = supportFragmentManager
var fragment: Fragment? = null
val fragments = fm.fragments
for (f in fragments) {
if (f.isVisible) {
fragment = f
break
}
}
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
presenter.onBackPressed()
}
}
}
@@ -0,0 +1,69 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.terrakok.cicerone.sample.databinding.FragmentForwardBinding
import com.github.terrakok.cicerone.sample.mvp.bottom.forward.ForwardPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.forward.ForwardView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
/**
* Created by terrakok 26.11.16
*/
class ForwardFragment : MvpAppCompatFragment(), ForwardView, BackButtonListener {
private lateinit var binding: FragmentForwardBinding
@InjectPresenter
lateinit var presenter: ForwardPresenter
@ProvidePresenter
fun provideForwardPresenter(): ForwardPresenter {
return ForwardPresenter(
arguments!!.getString(EXTRA_NAME),
(parentFragment as RouterProvider).router,
arguments!!.getInt(EXTRA_NUMBER)
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentForwardBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.toolbar.title = arguments!!.getString(EXTRA_NAME)
binding.toolbar.setNavigationOnClickListener { presenter.onBackPressed() }
binding.forwardButton.setOnClickListener { presenter.onForwardPressed() }
binding.githubButton.setOnClickListener { presenter.onGithubPressed() }
}
override fun setChainText(chainText: String) {
binding.chainText.text = chainText
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val EXTRA_NAME = "extra_name"
private const val EXTRA_NUMBER = "extra_number"
fun getNewInstance(name: String?, number: Int): ForwardFragment {
return ForwardFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NAME, name)
putInt(EXTRA_NUMBER, number)
}
}
}
}
}
@@ -0,0 +1,88 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.forwardScreen
import com.github.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import javax.inject.Inject
/**
* Created by terrakok 25.11.16
*/
class TabContainerFragment : Fragment(), RouterProvider, BackButtonListener {
private val navigator: Navigator by lazy {
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager, childFragmentManager.fragmentFactory)
}
@Inject
lateinit var ciceroneHolder: LocalCiceroneHolder
private val containerName: String
get() = arguments!!.getString(EXTRA_NAME)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
private val cicerone: Cicerone<Router>
get() = ciceroneHolder.getCicerone(containerName)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_tab_container, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (childFragmentManager.findFragmentById(R.id.ftc_container) == null) {
cicerone.router.replaceScreen(forwardScreen(containerName, 0))
}
}
override fun onResume() {
super.onResume()
cicerone.getNavigatorHolder().setNavigator(navigator)
}
override fun onPause() {
cicerone.getNavigatorHolder().removeNavigator()
super.onPause()
}
override val router: Router
get() = cicerone.router
override fun onBackPressed(): Boolean {
val fragment = childFragmentManager.findFragmentById(R.id.ftc_container)
return if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
true
} else {
(activity as RouterProvider?)!!.router.exit()
true
}
}
companion object {
private const val EXTRA_NAME = "tcf_extra_name"
fun getNewInstance(name: String?) =
TabContainerFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NAME, name)
}
}
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone.sample.ui.common
/**
* Created by terrakok 26.11.16
*/
interface BackButtonListener {
fun onBackPressed(): Boolean
}
@@ -0,0 +1,10 @@
package com.github.terrakok.cicerone.sample.ui.common
import com.github.terrakok.cicerone.Router
/**
* Created by terrakok 25.11.16
*/
interface RouterProvider {
val router: Router
}
@@ -0,0 +1,34 @@
package com.github.terrakok.cicerone.sample.ui.main
import android.content.Context
import androidx.fragment.app.Fragment
import moxy.MvpAppCompatFragment
import java.lang.ref.WeakReference
abstract class BaseFragment : MvpAppCompatFragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
val activity = activity
if (activity is ChainHolder) {
(activity as ChainHolder).chain.add(WeakReference<Fragment>(this))
}
}
override fun onDetach() {
val activity = activity
if (activity is ChainHolder) {
val chain = (activity as ChainHolder).chain
val it = chain.iterator()
while (it.hasNext()) {
val fragmentReference = it.next()
val fragment = fragmentReference.get()
if (fragment != null && fragment === this) {
it.remove()
break
}
}
}
super.onDetach()
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone.sample.ui.main
import androidx.fragment.app.Fragment
import java.lang.ref.WeakReference
interface ChainHolder {
val chain: MutableList<WeakReference<Fragment>>
}
@@ -0,0 +1,95 @@
package com.github.terrakok.cicerone.sample.ui.main
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.github.terrakok.cicerone.Command
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Replace
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.sampleScreen
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatActivity
import java.lang.ref.WeakReference
import java.util.*
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class MainActivity : MvpAppCompatActivity(), ChainHolder {
private lateinit var screensSchemeTV: TextView
override val chain = ArrayList<WeakReference<Fragment>>()
@Inject
lateinit var navigatorHolder: NavigatorHolder
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container, supportFragmentManager, supportFragmentManager.fragmentFactory) {
override fun applyCommands(commands: Array<out Command>) {
super.applyCommands(commands)
supportFragmentManager.executePendingTransactions()
printScreensScheme()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
screensSchemeTV = findViewById<View>(R.id.screens_scheme) as TextView
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(sampleScreen(1))))
} else {
printScreensScheme()
}
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentById(R.id.main_container)
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
super.onBackPressed()
}
}
private fun printScreensScheme() {
val fragments = ArrayList<SampleFragment>()
for (fragmentReference in chain) {
val fragment = fragmentReference.get()
if (fragment != null && fragment is SampleFragment) {
fragments.add(fragment)
}
}
fragments.sortWith { f1, f2 ->
val t = f1.creationTime - f2.creationTime
if (t > 0) 1 else if (t < 0) -1 else 0
}
val keys = ArrayList<Int>()
for (fragment in fragments) {
keys.add(fragment.number)
}
screensSchemeTV.text = "Chain: $keys"
}
}
@@ -0,0 +1,87 @@
package com.github.terrakok.cicerone.sample.ui.main
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentSampleBinding
import com.github.terrakok.cicerone.sample.mvp.main.SamplePresenter
import com.github.terrakok.cicerone.sample.mvp.main.SampleView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class SampleFragment : BaseFragment(), SampleView, BackButtonListener {
lateinit var binding: FragmentSampleBinding
@Inject
lateinit var router: Router
@InjectPresenter
lateinit var presenter: SamplePresenter
@ProvidePresenter
fun createSamplePresenter() = SamplePresenter(router, arguments!!.getInt(EXTRA_NUMBER))
val number: Int
get() = arguments!!.getInt(EXTRA_NUMBER)
val creationTime: Long
get() = arguments!!.getLong(EXTRA_TIME, 0L)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSampleBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.toolbar.setNavigationOnClickListener { presenter.onBackCommandClick() }
binding.backCommand.setOnClickListener { presenter.onBackCommandClick() }
binding.forwardCommand.setOnClickListener { presenter.onForwardCommandClick() }
binding.replaceCommand.setOnClickListener { presenter.onReplaceCommandClick() }
binding.newChainCommand.setOnClickListener { presenter.onNewChainCommandClick() }
binding.newRootCommand.setOnClickListener { presenter.onNewRootCommandClick() }
binding.forwardDelayCommand.setOnClickListener { presenter.onForwardWithDelayCommandClick() }
binding.backToCommand.setOnClickListener { presenter.onBackToCommandClick() }
binding.finishChainCommand.setOnClickListener { presenter.onFinishChainCommandClick() }
}
override fun setTitle(title: String) {
binding.toolbar.title = title
}
override fun onBackPressed(): Boolean {
presenter.onBackCommandClick()
return true
}
companion object {
private const val EXTRA_NUMBER = "extra_number"
private const val EXTRA_TIME = "extra_time"
fun getNewInstance(number: Int): SampleFragment {
return SampleFragment().apply {
arguments = Bundle().apply {
putInt(EXTRA_NUMBER, number)
putLong(EXTRA_TIME, System.currentTimeMillis())
}
}
}
}
}
@@ -0,0 +1,70 @@
package com.github.terrakok.cicerone.sample.ui.start
import android.os.Bundle
import android.view.View
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.mvp.start.StartActivityPresenter
import com.github.terrakok.cicerone.sample.mvp.start.StartActivityView
import moxy.MvpAppCompatActivity
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by terrakok 21.11.16
*/
class StartActivity : MvpAppCompatActivity(), StartActivityView {
@Inject
lateinit var router: Router
@Inject
lateinit var navigatorHolder: NavigatorHolder
@InjectPresenter
lateinit var presenter: StartActivityPresenter
private val navigator: Navigator = AppNavigator(this, -1, supportFragmentManager, supportFragmentManager.fragmentFactory)
@ProvidePresenter
fun createStartActivityPresenter() = StartActivityPresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_start)
initViews()
}
private fun initViews() {
findViewById<View>(R.id.ordinary_nav_button).setOnClickListener {
presenter.onOrdinaryPressed()
}
findViewById<View>(R.id.multi_nav_button).setOnClickListener {
presenter.onMultiPressed()
}
findViewById<View>(R.id.result_and_anim_button).setOnClickListener {
presenter.onResultWithAnimationPressed()
}
}
override fun onResume() {
super.onResume()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
presenter.onBackPressed()
}
}
@@ -1,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,10 +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_";
}
@@ -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);
}
@@ -1,132 +0,0 @@
package ru.terrakok.cicerone.sample.ui.main;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.widget.TextView;
import android.widget.Toast;
import com.arellomobile.mvp.MvpAppCompatActivity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.android.SupportFragmentNavigator;
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.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.Screens;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class MainActivity extends MvpAppCompatActivity {
private static final String STATE_SCREEN_NAMES = "state_screen_names";
private List<String> screenNames = new ArrayList<>();
private TextView screensSchemeTV;
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();
}
@Override
public void applyCommand(Command command) {
super.applyCommand(command);
updateScreenNames(command);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
screensSchemeTV = (TextView) findViewById(R.id.screens_scheme);
if (savedInstanceState == null) {
navigator.applyCommand(new Replace(Screens.SAMPLE_SCREEN, 1));
} else {
screenNames = (List<String>) savedInstanceState.getSerializable(STATE_SCREEN_NAMES);
printScreensScheme();
}
}
@Override
protected void onResume() {
super.onResume();
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(navigator);
}
@Override
protected void onPause() {
super.onPause();
SampleApplication.INSTANCE.getNavigatorHolder().removeNavigator();
}
@Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
if (fragment != null && fragment instanceof SampleFragment) {
((SampleFragment) fragment).onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(STATE_SCREEN_NAMES, (Serializable) screenNames);
}
private void updateScreenNames(Command command) {
if (command instanceof Back) {
if (screenNames.size() > 0) {
screenNames.remove(screenNames.size() - 1);
}
} else if (command instanceof Forward) {
int i = (int) ((Forward) command).getTransitionData();
screenNames.add(i + "");
} else if (command instanceof Replace) {
int i = (int) ((Replace) command).getTransitionData();
if (screenNames.size() > 0) {
screenNames.remove(screenNames.size() - 1);
}
screenNames.add(i + "");
} else if (command instanceof BackTo) {
screenNames = new ArrayList<>(screenNames.subList(0, getSupportFragmentManager().getBackStackEntryCount() + 1));
}
printScreensScheme();
}
private void printScreensScheme() {
String str = "";
for (String name : screenNames) {
if (!str.isEmpty()) {
str += "" + name;
} else {
str = "[" + name + "]";
}
}
screensSchemeTV.setText("Chain: " + str + "");
}
}
@@ -1,141 +0,0 @@
package ru.terrakok.cicerone.sample.ui.main;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.arellomobile.mvp.MvpAppCompatFragment;
import com.arellomobile.mvp.presenter.InjectPresenter;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.mvp.main.SamplePresenter;
import ru.terrakok.cicerone.sample.mvp.main.SampleView;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class SampleFragment extends MvpAppCompatFragment implements SampleView {
private static final String EXTRA_NUMBER = "extra_number";
private Toolbar toolbar;
private View backCommandBt;
private View forwardCommandBt;
private View replaceCommandBt;
private View newChainCommandBt;
private View newRootCommandBt;
private View backWithMessageCommandBt;
private View forwardWithDelayCommandBt;
private View backToCommandBt;
@InjectPresenter
SamplePresenter presenter;
public static SampleFragment getNewInstance(int number) {
SampleFragment fragment = new SampleFragment();
Bundle args = new Bundle();
args.putInt(EXTRA_NUMBER, number);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter.init(getArguments().getInt(EXTRA_NUMBER));
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_sample, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
toolbar = (Toolbar) view.findViewById(R.id.toolbar);
backCommandBt = view.findViewById(R.id.back_command);
forwardCommandBt = view.findViewById(R.id.forward_command);
replaceCommandBt = view.findViewById(R.id.replace_command);
newChainCommandBt = view.findViewById(R.id.new_chain_command);
newRootCommandBt = view.findViewById(R.id.new_root_command);
backWithMessageCommandBt = view.findViewById(R.id.back_with_message_command);
forwardWithDelayCommandBt = view.findViewById(R.id.forward_delay_command);
backToCommandBt = view.findViewById(R.id.back_to_command);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onBackCommandClick();
}
});
backCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onBackCommandClick();
}
});
forwardCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onForwardCommandClick();
}
});
replaceCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onReplaceCommandClick();
}
});
newChainCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onNewChainCommandClick();
}
});
newRootCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onNewRootCommandClick();
}
});
backWithMessageCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onBackWithMessageCommandClick();
}
});
forwardWithDelayCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onForwardWithDelayCommandClick();
}
});
backToCommandBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onBackToCommandClick();
}
});
}
@Override
public void setTitle(String title) {
toolbar.setTitle(title);
}
public void onBackPressed() {
presenter.onBackCommandClick();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

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