103 Commits

Author SHA1 Message Date
Konstantin 9e1436a223 Merge pull request #178 from adpops/master
Edited Readme
2023-05-15 13:35:24 +02:00
Jaimin Nimavat bd9ac36efc Grammar changes
Fixed spelling and grammar, added formatting for text, changed Cicerone definition, changed some titles for clearer heading
2022-07-24 20:35:33 -07:00
Jaimin Nimavat 2dc6fca41c Capitalized bullet points 2022-07-24 20:07:53 -07:00
Konstantin e210e45c36 Update README.md 2021-08-06 13:49:23 +03:00
Konstantin f1a5881a9e Update README.md 2021-08-06 12:39:28 +03:00
terrakok af396eb974 Bump version to '7.1' 2021-07-20 20:58:44 +03:00
terrakok b37a6c979d Improvements for SemiTransparentFragment. 2021-07-20 20:43:42 +03:00
Konstantin 701feee394 Merge pull request #159 from phansier/clearContainer_sample
Add sample for ClearContainer = false property of FragmentScreen
2021-07-20 17:05:59 +03:00
Andrey Beryukhov b399dd3173 Add back button 2021-07-20 15:35:43 +03:00
Konstantin 2a4969d756 Merge pull request #164 from VitalyPeryatin/bug/action_after_on_save_instance_state
Fix bug with 'Can not perform this action after onSaveInstanceState'
2021-07-20 12:22:57 +03:00
VitalyPeryatin 8521b7e19d Fix bug with 'Can not perform this action after onSaveInstanceState' 2021-07-17 08:57:57 +03:00
Andrey Beryukhov 8b2eab847b Add sample for ClearContainer = false option 2021-06-10 21:02:28 +03:00
terrakok cbaabaf0bc Update README.md 2021-04-17 14:43:45 +03:00
terrakok 192e35ca71 Bump version to "7.0" and disable publication config for successful sample build without credentials. 2021-04-17 14:26:44 +03:00
terrakok ff77162654 Fix setupFragmentTransaction in sample app. 2021-04-17 14:10:00 +03:00
terrakok 82256a9785 Refactor FragmentScreen and ActivityScreen to interfaces for flexibility in some cases.
See https://github.com/terrakok/Cicerone/pull/144
2021-04-17 14:09:41 +03:00
Konstantin bbd27daee3 Merge pull request #152 from eduard1abdulmanov123/bug_fix_same_screen_key
Fix bugs with identical screen keys
2021-04-17 14:06:54 +03:00
eduard1abdulmanov123 4a54fc1dd4 Fix bugs with identical screen keys 2021-04-17 10:45:29 +03:00
terrakok 4fc5b26980 Add fragment screen to setupFragmentTransaction method. 2021-04-09 17:14:13 +03:00
terrakok daef586461 Move fragment transaction type to FragmentScreen property. 2021-04-09 17:09:21 +03:00
Konstantin 7a862cb061 Merge pull request #151 from VitalyPeryatin/handle_transactions_by_ui_handler
Add navigation transactions in message queue
2021-04-08 16:06:04 +03:00
VitalyPeryatin f2de564600 Add navigation transactions in message queue 2021-04-08 12:58:35 +03:00
Konstantin 4c674c2583 Update README.md 2021-02-14 21:15:57 +03:00
terrakok 22182bddb8 Change jCenter badge to mavenCentral 2021-02-14 21:08:57 +03:00
terrakok ae9f4aea70 Setup publication to mavenCentral. 2021-02-14 21:01:10 +03:00
Konstantin edfc5b5300 Merge pull request #140 from dmitrynerd/fix/license_information_in_pom_file
Add license information to POM file on maven repository.
2021-02-05 14:24:50 +03:00
Dmitry Beshenov ee2dd68f09 Add license information to POM file on maven repository. 2021-02-05 14:08:52 +03:00
terrakok c3873c6e9d Bump version to "6.6" 2020-11-26 13:31:05 +03:00
terrakok f44410fcfe Fix problem with loss of result listener.
Issue #130
2020-11-26 13:30:22 +03:00
Konstantin 608a32c3be Update README.md 2020-11-18 22:34:28 +03:00
Konstantin 17428f0189 Update README.md 2020-11-18 22:08:21 +03:00
terrakok 1701bc4f36 Fixed launching Activity via no concrete Intent. 2020-11-18 22:02:18 +03:00
terrakok 9c9c56bc9b Bump version to "6.4" 2020-11-05 17:24:22 +03:00
terrakok 3da8fe52ca Set fragmentReorderingAllowed=TRUE by default. 2020-11-05 17:21:01 +03:00
terrakok a943aab08e Move ResultListener to another file. 2020-11-05 16:15:28 +03:00
Konstantin 86ac421fb8 Merge pull request #128 from aradxxx/feature/java-interop
java interop
2020-11-05 16:06:50 +03:00
aradxxx f7009f53e8 java interop 2020-11-03 05:13:13 +04:00
Konstantin 994c497147 Update README.md 2020-11-02 20:55:34 +03:00
Konstantin bb13ace1c9 Update README.md 2020-11-02 15:29:24 +03:00
Konstantin dbe065275d Update README.md 2020-11-02 15:15:24 +03:00
Konstantin 2ebbc98a99 Update README.md 2020-11-02 15:10:17 +03:00
Konstantin ba7eea60f5 Update README.md 2020-11-02 10:10:17 +03:00
Konstantin 6347fe7d95 Update README.md 2020-11-01 21:30:28 +03:00
Konstantin 2b6e2ee874 Update README.md 2020-11-01 18:34:03 +03:00
terrakok f50a4d8106 Bump version to "6.3" 2020-11-01 17:07:08 +03:00
terrakok 3535f491f5 Refactor Screen class as interface. 2020-11-01 17:07:04 +03:00
Konstantin 1c5b89d2ec Update README.md 2020-11-01 14:54:59 +03:00
Konstantin 127cce1fe4 Update README.md 2020-11-01 14:06:53 +03:00
terrakok 3bea311400 Bump version to "6.2" 2020-10-30 12:31:15 +03:00
terrakok a0b3dfa71e Fix sample app design 2020-10-30 12:27:24 +03:00
Konstantin 0e9ff3f7f5 Merge pull request #126 from MonStar1/fix/newRootChain
fix newRootChain
2020-10-30 12:12:13 +03:00
Andrei Papko 004faf77c4 fix newRootChain 2020-10-28 17:21:57 +03:00
Konstantin 214a6eddf1 Update README.md 2020-10-21 17:32:59 +03:00
terrakok a4c94003ad Bump version to "6.1" 2020-10-21 17:14:18 +03:00
terrakok 4f7c1c1607 Add screenKey to fragment transaction tag. 2020-10-21 17:14:18 +03:00
terrakok 16f8fd833e Refactor sample screens names 2020-10-21 17:14:18 +03:00
terrakok 77c3ecc76e Rename ResultBus to ResultWire 2020-10-21 17:14:18 +03:00
terrakok 062073cb37 Add simple result messaging. 2020-10-21 17:14:18 +03:00
Konstantin b9cddc290e Update README.md 2020-10-19 15:52:39 +03:00
terrakok df860d2173 Change version name to "6.0" and update bintray setup. 2020-10-19 15:37:29 +03:00
terrakok dbfc6a4f72 Change "Tckhovrebov" -> "Tskhovrebov" everywhere. 2020-10-19 15:34:14 +03:00
terrakok 69e4632058 Change "all" -> "bin" for gradle wrapper. 2020-10-19 15:33:04 +03:00
Konstantin f38b5685a2 Merge pull request #124 from Javernaut/feature/updated_sample
Sample project reviving
2020-10-17 22:55:10 +03:00
Javernaut b81d6bc8e5 Removing redundant explicit dependency for Kotlin stdlib 2020-10-17 21:54:03 +03:00
Javernaut ab8036a13e Migrating to moxy-community 2020-10-17 21:52:27 +03:00
Javernaut a9688db7f5 Migrating the sample applicationId to 'com.github.terrakok.cicerone.sample' 2020-10-16 19:34:59 +03:00
Javernaut e63f6c2339 The Animation sample was migrated to Kotlin 2020-10-16 18:47:15 +03:00
Javernaut 1de7a50b58 Bottom navigation sample was migrated to Kotlin 2020-10-16 18:33:18 +03:00
Javernaut 0efab8731b Main functionality of the sample was migrated to Kotlin 2020-10-16 18:18:44 +03:00
Javernaut 39d043f5d7 StartActivity was migrated to Kotlin 2020-10-16 17:10:02 +03:00
Javernaut f5397994ab Migrating Dagger code to Kotlin 2020-10-16 17:00:14 +03:00
Javernaut af272bc587 Reviving the sample module 2020-10-16 16:22:44 +03:00
Javernaut 9e6c08d5c5 Build tools updating 2020-10-16 14:45:57 +03:00
Konstantin 9c67451d11 Update README.md 2020-10-16 10:58:22 +03:00
Konstantin 86674c0275 Update README.md 2020-10-14 23:45:46 +03:00
terrakok 70b8fcbf4f Rename version to 6.1 2020-10-14 22:51:54 +03:00
terrakok 8be84acb80 Update readme for version 6.0 2020-10-14 22:41:40 +03:00
terrakok 4872d10d6a Add screen keys and update version to 6.0.3-dev 2020-07-26 00:39:01 +03:00
terrakok ac5efa84ed Use default FragmentFactory and update version to 6.0.2-dev 2020-06-10 22:04:30 +03:00
terrakok 3e982794ec Update version to 6.0.1-dev 2020-06-10 18:07:44 +03:00
terrakok a748473697 Refactor FragmentFactory usage and remove outdated sample. 2020-06-10 17:11:28 +03:00
terrakok 0734a4eb22 Refactor AppNavigator. 2020-05-24 23:21:12 +03:00
terrakok 748f2f657c Setup new publication. 2020-05-24 12:48:08 +03:00
terrakok 88884ac51e Migrate to kotlin. 2020-05-23 16:25:32 +03:00
terrakok 0b35127778 Add new parameter for clear container or not during navigation to new screen. 2020-05-21 23:32:45 +03:00
terrakok 9aeb02cfae Update version to 5.1.1 2020-05-10 17:23:43 +03:00
Konstantin 7c31cdc1bd Merge pull request #112 from adolgiy/hotfix/fragment-factory-npe
Fix NPE when localStackCopy.size() == 0 and FragmentFactory is used
2020-05-10 17:11:14 +03:00
Aleksey Dolgiy 05a8ec8f44 Extract 'forward' to reuse in replace when localStackCopy.size > 0 2020-02-20 10:29:18 +03:00
Aleksey Dolgiy d08e6b1afe Code Review: Use internal replace for fragmentForward(Forward) and add explicit exception with null checks 2020-02-19 20:46:31 +03:00
Aleksey Dolgiy cea829ae88 Fix NPE when localStackCopy.size() == 0 and FragmentFactory is used 2020-02-19 17:37:25 +03:00
Konstantin Tskhovrebov dcf1532103 Merge branch 'develop' 2020-02-11 14:42:17 +03:00
Konstantin Tskhovrebov 046410e132 Update version of Cicerone to "5.1.0". 2020-02-11 14:41:24 +03:00
Konstantin e80b2d5432 Merge pull request #111 from terrakok/handle_command_error
Add errorOnApplyCommand for ability manual error handling.
2020-02-11 14:38:09 +03:00
Konstantin Tskhovrebov 3f1cf714df Add errorOnApplyCommand for ability manual error handling. 2020-02-11 14:36:13 +03:00
Konstantin febae878a4 Merge pull request #99 from asitnkova/feature/add_nullability_annotations
Add androidx nullability annotations
2020-02-11 12:45:10 +03:00
Konstantin 6cc2d3e474 Merge branch 'develop' into feature/add_nullability_annotations 2020-02-11 12:42:04 +03:00
Konstantin dc2f732ec6 Merge pull request #110 from asitnkova/feature/add_new_methods_fragment_1.2.0
Feature/add new methods fragment 1.2.0
2020-02-11 12:38:47 +03:00
asitnkova 5d6880c95f Add support for new FragmentTransaction.replace overload 2020-02-04 22:34:10 +07:00
Alexander e1e2dd8b9b Bump build tools 3.5.0-alpha01 -> 3.5.0, gradle 5.1-milestone-1 -> 5.6.4 2020-02-04 22:32:51 +07:00
Konstantin 4df0f34e34 Merge pull request #101 from Anton111111/develop
Made properties protected to add ability use them in overridden methods.
2019-11-18 12:52:07 +03:00
anton f36f6a1e44 Made properties protected to add ability use them in overridden methods. 2019-09-17 19:42:11 +03:00
Anton Potekhin be63c91abe Made properties protected to add ability use them in overridden methods. 2019-02-07 09:36:05 +03:00
Alexander Sitnikov 8e9bf90ee6 Add nullability annotations 2019-01-26 17:31:29 +07:00
111 changed files with 2392 additions and 3006 deletions
+4 -1
View File
@@ -10,4 +10,7 @@ local.properties
**/*.iml
# Mac OS
.DS_Store
.DS_Store
# GPG keys
*.gpg
+174 -103
View File
@@ -1,164 +1,235 @@
# Cicerone
[![jCenter](https://api.bintray.com/packages/terrakok/terramaven/cicerone/images/download.svg)](https://bintray.com/terrakok/terramaven/cicerone/_latestVersion)
[![Maven Central](https://img.shields.io/maven-central/v/com.github.terrakok/cicerone)](https://repo1.maven.org/maven2/com/github/terrakok/cicerone/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Join the chat at https://gitter.im/terrakok/Cicerone](https://img.shields.io/badge/Gitter-Join%20Chat-brightred.svg?style=flat)](https://gitter.im/terrakok/Cicerone)
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Cicerone-green.svg?style=true)](https://android-arsenal.com/details/1/4700)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-250-green.svg)](http://androidweekly.net/issues/issue-250)
[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-271-green.svg)](http://androidweekly.net/issues/issue-271)
![](https://habrastorage.org/files/644/32e/9eb/64432e9eb3664723b3ee438449dab3b0.png)
<table>
<tr>
<td>
<img src="https://github.com/terrakok/Cicerone/raw/master/media/navigation.gif" width="256"/>
</td>
<td>
<img src="https://github.com/terrakok/Cicerone/raw/master/media/insta_tabs.gif" width="256"/>
</td>
<td>
<img src="https://github.com/terrakok/Cicerone/raw/master/media/animations.gif" width="256"/>
</td>
</tr>
<tr>
<td>
Power navigation
</td>
<td>
Multibackstack
</td>
<td>
Result listeners
</td>
</tr>
</table>
Cicerone (a guide, one who conducts sightseers) is a lightweight library that makes the navigation in an Android app easy.
It was designed to be used with the MVP pattern (try [Moxy](https://github.com/Arello-Mobile/Moxy)), but will work great with any architecture.
Cicerone (a guide who gives information about antiquities and places of interest to sightseers) is a lightweight library that makes the navigation in an Android app easy.
It was designed to be used with the MVP/MVVM/MVI patterns but will work great with any architecture.
## Main advantages
+ is not tied to Fragments
+ not a framework
+ short navigation calls (no builders)
+ lifecycle-safe!
+ functionality is simple to extend
+ suitable for Unit Testing
+ Is not tied to Fragments
+ Not a framework (very lightweight)
+ Short navigation calls (no builders)
+ Static typed checks for screen parameters!
+ Lifecycle-safe!
+ Functionality is simple to extend
+ Suitable for Unit Testing
## Additional features
+ opening several screens inside single call (for example: deeplink)
+ implementation of parallel navigation (Instagram like)
+ predefined navigator ready for Single-Activity apps
+ predefined navigator ready for setup transition animation
+ 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
**See the sample application**
## How to add
## How to add Cicerone to your application
Add the dependency in your build.gradle:
```groovy
```kotlin
dependencies {
//Cicerone
compile 'ru.terrakok.cicerone:cicerone:X.X.X'
implementation("com.github.terrakok:cicerone:X.X.X")
}
```
Initialize the library (for example in your Application class):
```java
public class SampleApplication extends MvpApplication {
public static SampleApplication INSTANCE;
private Cicerone<Router> cicerone;
```kotlin
class App : Application() {
private val cicerone = Cicerone.create()
val router get() = cicerone.router
val navigatorHolder get() = cicerone.getNavigatorHolder()
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
initCicerone();
override fun onCreate() {
super.onCreate()
INSTANCE = this
}
private void initCicerone() {
cicerone = Cicerone.create();
}
public NavigatorHolder getNavigatorHolder() {
return cicerone.getNavigatorHolder();
}
public Router getRouter() {
return cicerone.getRouter();
companion object {
internal lateinit var INSTANCE: App
private set
}
}
```
## How it works?
<img src="https://github.com/terrakok/Cicerone/raw/develop/media/CiceroneDiagram.png" alt="drawing" width="800"/>
## How does it work?
<img src="https://github.com/terrakok/Cicerone/blob/master/media/CiceroneDiagram.png" alt="CiceroneDiagram.png" width="800"/>
Presenter calls navigation method of Router.
The `Presenter` calls the 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(new SomeScreen());
fun onBackPressed() {
router.exit()
}
}
```
Router converts the navigation call to the set of Commands and sends them to CommandBuffer.
`Router` converts the navigation call to the set of commands and sends them to `CommandBuffer`.
CommandBuffer checks whether there are _"active"_ Navigator:
- If yes, it passes the commands to the Navigator. Navigator will process them to achive the desired transition.
- If no, then CommandBuffer saves the commands in a queue, and will apply them as soon as new _"active"_ Navigator will appear.
`CommandBuffer` checks whether there are the `_"active"_ Navigator`:
- If yes, it passes the commands to the Navigator. `Navigator` will process them to achive the desired transition.
- If no, then `CommandBuffer` saves the commands in a queue, and will apply them as soon as a new `_"active"_ Navigator` will appear.
```java
void executeCommands(Command[] commands) {
if (navigator != null) {
navigator.applyCommands(commands);
} else {
pendingCommands.add(commands);
}
}
```kotlin
fun executeCommands(commands: Array<out Command>) {
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
}
```
Navigator processes the navigation commands. Usually it is an anonymous class inside the Activity.
Activity provides Navigator to the CommandBuffer in _onResume_ and removes it in _onPause_.
`Navigator` processes the navigation commands. Usually it is an anonymous class inside `Activity`.
`Activity` provides `Navigator` to the `CommandBuffer` in `_onResume_` and removes it in `_onPause_`.
**Attention**: Use _onResumeFragments()_ with FragmentActivity ([more info](https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()))
**Attention**: Use `_onResumeFragments()_` with `FragmentActivity` ([more info](https://developer.android.com/reference/android/support/v4/app/FragmentActivity.html#onResume()))
```java
@Override
protected void onResume() {
super.onResume();
SampleApplication.INSTANCE.getNavigatorHolder().setNavigator(navigator);
```kotlin
private val navigator = AppNavigator(this, R.id.container)
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
@Override
protected void onPause() {
super.onPause();
SampleApplication.INSTANCE.getNavigatorHolder().removeNavigator();
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
private Navigator navigator = new Navigator() {
@Override
public void applyCommands(Command[] commands) {
//implement commands logic (apply command batch to navigation container)
}
};
```
## Navigation commands
This commands set will fulfill the needs of the most applications. But if you need something special - just add it!
These commands 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)
![](https://github.com/terrakok/Cicerone/raw/master/media/forward_img.png)
+ Back - Rolls back the last transition
![](https://github.com/terrakok/Cicerone/raw/develop/media/back_img.png)
![](https://github.com/terrakok/Cicerone/raw/master/media/back_img.png)
+ BackTo - Rolls back to the needed screen in the screens chain
![](https://github.com/terrakok/Cicerone/raw/develop/media/backTo_img.png)
![](https://github.com/terrakok/Cicerone/raw/master/media/backTo_img.png)
+ Replace - Replaces the current screen
![](https://github.com/terrakok/Cicerone/raw/develop/media/replace_img.png)
![](https://github.com/terrakok/Cicerone/raw/master/media/replace_img.png)
## Predefined navigators
The library provides predefined navigators for _Fragments_ and _Activity_.
## Predefined navigator
The library provides predefined navigator for _Fragments_ and _Activity_.
To use, just provide it with the container and _FragmentManager_.
```java
private Navigator navigator = new SupportAppNavigator(this, R.id.container);
```kotlin
private val navigator = AppNavigator(this, R.id.container)
```
## Sample
To see how to add, initialize and use the library and predefined navigators check out the sample.
Or look at [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)
A custom navigator can be useful sometimes:
```kotlin
private val navigator = object : AppNavigator(this, R.id.container) {
override fun setupFragmentTransaction(
screen: FragmentScreen,
fragmentTransaction: FragmentTransaction,
currentFragment: Fragment?,
nextFragment: Fragment
) {
//setup your animation
}
override fun applyCommands(commands: Array<out Command>) {
hideKeyboard()
super.applyCommands(commands)
}
}
```
## Screens
Describe your screens as you like e.g. create a Kotlin `object` with all application screens:
```kotlin
object Screens {
fun Main() = FragmentScreen { MainFragment() }
fun AddressSearch() = FragmentScreen { AddressSearchFragment() }
fun Profile(userId: Long) = FragmentScreen("Profile_$userId") { ProfileFragment(userId) }
fun Browser(url: String) = ActivityScreen { Intent(Intent.ACTION_VIEW, Uri.parse(url)) }
}
```
Additional you can use `FragmentFactory` for creating your screens:
```kotlin
fun SomeScreen() = FragmentScreen { factory: FragmentFactory -> ... }
```
## Screen parameters and result listener
```kotlin
//you have to specify screen parameters via new FragmentScreen creation
fun SelectPhoto(resultKey: String) = FragmentScreen {
SelectPhotoFragment.getNewInstance(resultKey)
}
```
```kotlin
//listen result
fun onSelectPhotoClicked() {
router.setResultListener(RESULT_KEY) { data ->
view.showPhoto(data as Bitmap)
}
router.navigateTo(SelectPhoto(RESULT_KEY))
}
//send result
fun onPhotoClick(photo: Bitmap) {
router.sendResult(resultKey, photoRes)
router.exit()
}
```
## Sample
To see how to add, initialize and use the library and predefined navigators see the **sample project**
(thank you [@Javernaut](https://github.com/Javernaut) for support new library version and migrate sample project to Kotlin!)
For more complex use case check out the [GitFox (Android GitLab client)](https://gitlab.com/terrakok/gitlab-client)
## Applications that use Cicerone
<a href="https://play.google.com/store/apps/details?id=ru.foodfox.client"><img src="https://play-lh.googleusercontent.com/gWYedIqy8QujCQOn0kzEIBEkGLBSpuKvFm-fMcfkWnJ1Oirtv847xAE4OyhAaohdcp5V=s360" width="64" /> Яндекс.Еда — доставка еды/продуктов. Food delivery</a><br>
<a href="https://play.google.com/store/apps/details?id=com.kms.me"><img src="https://play-lh.googleusercontent.com/IBzu0tlHd_amw2HbjBLOZiCfK-0tn0CnwkMdOd1toP23rdHUV-i7L2ViNKgIg687=s360" width="64" /> Kaspersky Internet Security</a><br>
<a href="https://play.google.com/store/apps/details?id=com.deliveryclub"><img src="https://play-lh.googleusercontent.com/m6-gFunvj7aQD5fdv8EdJZBN5M4REIobTaPZPYS0K5Td7CNYnazN7fOKiPwwaY3hJw=s360" width="64" /> Delivery Club Доставка еды и продуктов</a><br>
<a href="https://play.google.com/store/apps/details?id=ru.hh.android"><img src="https://play-lh.googleusercontent.com/YpAV7Q-ZJhI5tzFk_wEX-7-x2BydtnCtFTVUrmq0zAO6jLCLA4nNcfem3p_Pyowg9w=s360" width="64" /> Поиск работы на hh. Вакансии рядом с домом</a><br>
<a href="https://play.google.com/store/apps/details?id=com.foodient.whisk"><img src="https://play-lh.googleusercontent.com/eKotZjJcZOU2_L9t2l34EEY7aGl5zhvKVuEbF0Kc4MRs_pAC2SJgOnWMkMTFjR_e9EY=s360" width="64" /> Whisk: Recipe Saver, Meal Planner & Grocery List</a><br>
<a href="https://play.google.com/store/apps/details?id=kz.beeline.odp"><img src="https://play-lh.googleusercontent.com/hzgjpQQpy6Z-Byye0aVKSv9P7h8yx58i6pVkQtiM6jB99iWFXjYfKeaPqJ3wm6Rtb38=s360" width="64" /> Мой Beeline (Казахстан)</a><br>
<a href="https://play.google.com/store/apps/details?id=com.mercuryo.app"><img src="https://play-lh.googleusercontent.com/FKulXdc15r5PWX6hTZi2i3iaJjcQHwd9xParp6YPiQ2KiBqza7jwEt_b_tqLwXpyEHg=s360" width="64" /> Mercuryo Bitcoin Cryptowallet</a><br>
<a href="https://play.google.com/store/apps/details?id=com.warefly.checkscan"><img src="https://play-lh.googleusercontent.com/2c2uuiSl2vwGgp-vdI-VArQEMdSSXk1neUK5A-Udc0WANPcvp5kBJFEugrFiXnxUc7k=s360" width="64" /> ЧекСкан - кэшбэк за чеки, цены и акции в магазинах</a><br>
<a href="https://github.com/eduard1abdulmanov123/News"><img src="https://raw.githubusercontent.com/eduard1abdulmanov123/News/dev/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" width="64" /> RSS Reader для Вести.Ru</a><br>
<a href="https://play.google.com/store/apps/details?id=com.epam.connect.android"><img src="https://play-lh.googleusercontent.com/aN7R6BiR7yt7b3oEoBI30pVwzsdzaWe3TWpw8c9igqoOj79Pm2xVh4_C4qwjSKwjVio=s360" width="64" /> EPAM Connect</a><br>
<a href="https://play.google.com/store/apps/details?id=org.consumerreports.ratings"><img src="https://play-lh.googleusercontent.com/dEdOwZOjXAdamytxY1TgY8LS-Hc9FKCcit5HP1RyaKqRAWjDJEyFSQS1XlqQPpeY5UI=s360" width="64" /> Consumer Reports: Product Reviews & Ratings</a><br>
<a href="https://play.google.com/store/apps/details?id=ru.zakaz.android"><img src="https://play-lh.googleusercontent.com/jj18yK2dB2MHZ_QdO21aXyznGXteIF2q4mgxY4ubLhFv9gwZqHVDeu1i2FmanS-0Furm=s360" width="64" /> Zakaz.ru</a><br>
## Participants
+ idea and code - Konstantin Tskhovrebov (@terrakok)
+ architecture advice, documentation and publication - Vasili Chyrvon (@Jeevuz)
+ Idea and code - Konstantin Tskhovrebov (@terrakok)
+ Architecture advice, documentation and publication - Vasili Chyrvon (@Jeevuz)
## License
```
+5 -6
View File
@@ -1,22 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.5.21'
repositories {
jcenter()
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0-alpha01'
// For the library uploading to the Bintray
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.1'
classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
}
}
allprojects {
repositories {
jcenter()
mavenCentral()
google()
}
}
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1-milestone-1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
-14
View File
@@ -1,14 +0,0 @@
apply plugin: 'com.github.dcendents.android-maven'
group = publishedGroupId // Maven Group ID for the artifact
install {
repositories.mavenInstaller {
// This generates POM.xml with proper parameters
pom.project {
packaging 'jar'
groupId publishedGroupId
artifactId artifact
}
}
}
-24
View File
@@ -1,24 +0,0 @@
apply plugin: 'com.jfrog.bintray'
version = libraryVersion // Library version
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
bintray {
// User and ApiKey stored in local.properties
user = properties.getProperty("bintrayUser")
key = properties.getProperty("bintrayApiKey")
configurations = ['archives']
// Package info for Bintray
pkg {
repo = bintrayRepo
name = bintrayName
licenses = allLicenses
vcsUrl = gitUrl
publish = true
}
}
// Dependency to call only bintrayUpload task
bintrayUpload.dependsOn install
+7 -28
View File
@@ -1,4 +1,4 @@
apply plugin: 'java'
apply plugin: 'kotlin'
// This is important even if Android Studio claims it isn't
// used. Android can't interpret Java 8 byte code.
@@ -8,37 +8,16 @@ targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
compileOnly project(':stub-android')
compileOnly 'com.google.android:android:4.1.1.4'
implementation "org.jetbrains.kotlin:kotlin-stdlib"
}
ext {
// This params is for the library uploading to the Bintray
bintrayRepo = 'terramaven'
bintrayName = 'cicerone'
publishedGroupId = 'ru.terrakok.cicerone'
publishedGroupId = 'com.github.terrakok'
artifact = 'cicerone'
libraryVersion = '5.0.0'
gitUrl = 'https://github.com/terrakok/Cicerone'
allLicenses = ['MIT']
libraryVersion = '7.1'
}
// Configuration of the library uploading to the Bintray
// Note: Call 'bintrayUpload' task (it will execute 'install' task first)
// I try to include as little properties as possible in these files
project.archivesBaseName ='cicerone' // to fix that project name different from artifact name
apply from: 'androidmaven.gradle'
apply from: 'bintray.gradle'
project.archivesBaseName = artifact // to fix that project name different from artifact name
// Tasks for sources and javadocs jars
task sourcesJar(type: Jar) {
from sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
//apply from: 'publish-mavencentral.gradle'
//for publication use './gradlew publishReleasePublicationToSonatypeRepository'
+94
View File
@@ -0,0 +1,94 @@
apply plugin: 'maven-publish'
apply plugin: 'signing'
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
properties.each { name, value -> ext[name] = value }
group = publishedGroupId
version = libraryVersion
task sourcesJar(type: Jar) {
from sourceSets.main.kotlin.srcDirs
classifier = 'sources'
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
publishing {
publications {
release(MavenPublication) {
groupId = publishedGroupId
artifactId = artifact
version = libraryVersion
artifact("$buildDir/libs/${artifact}-${version}.jar")
artifact sourcesJar
artifact javadocJar
pom {
name = artifact
description = 'Cicerone is a lightweight library that makes the navigation in an Android app easy.'
url = 'https://github.com/terrakok/Cicerone'
licenses {
license {
name = "MIT"
url = "https://opensource.org/licenses/MIT"
}
}
developers {
developer {
id = 'terrakok'
name = 'Konstantin Tskhovrebov'
email = 'terrakok@gmail.com'
}
}
scm {
developerConnection = 'scm:git:ssh://github.com/terrakok/Cicerone.git'
url = 'https://github.com/terrakok/Cicerone'
}
// A slightly hacky fix so that your POM will include any transitive dependencies
// that your library builds upon
withXml {
def dependenciesNode = asNode().appendNode('dependencies')
project.configurations.implementation.allDependencies.each {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
}
}
}
}
}
repositories {
maven {
name = "sonatype"
url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
credentials {
username = ossrhUsername
password = ossrhPassword
}
}
}
}
signing {
sign publishing.publications
}
tasks.getByName("publishReleasePublicationToSonatypeRepository").dependsOn("assemble", "sourcesJar", "javadocJar")
@@ -1,32 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone;
import ru.terrakok.cicerone.commands.Command;
/**
* 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 array to {@link CommandBuffer}.
*
* @param commands navigation command array to execute
*/
protected void executeCommands(Command... commands) {
commandBuffer.executeCommands(commands);
}
}
@@ -1,44 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.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 {@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,47 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone;
import java.util.LinkedList;
import java.util.Queue;
import ru.terrakok.cicerone.commands.Command;
/**
* 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) {
executeCommands(pendingCommands.poll());
} else break;
}
}
@Override
public void removeNavigator() {
this.navigator = null;
}
/**
* Passes {@code commands} to the {@link Navigator} if it available.
* Else puts it to the pending commands queue to pass it later.
* @param commands navigation command array
*/
void executeCommands(Command[] commands) {
if (navigator != null) {
navigator.applyCommands(commands);
} else {
pendingCommands.add(commands);
}
}
}
@@ -1,117 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone;
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;
/**
* 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 screen screen
*/
public void navigateTo(Screen screen) {
executeCommands(new Forward(screen));
}
/**
* Clear all screens and open new one as root.
*
* @param screen screen
*/
public void newRootScreen(Screen screen) {
executeCommands(
new BackTo(null),
new 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
*/
public void replaceScreen(Screen screen) {
executeCommands(new 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 {@link BackTo} command in a {@link Navigator} implementation.
*
* @param screen screen
*/
public void backTo(Screen screen) {
executeCommands(new BackTo(screen));
}
/**
* Opens several screens inside single transaction.
* @param screens
*/
public void newChain(Screen... screens) {
Command[] commands = new Command[screens.length];
for (int i = 0; i < commands.length; i++) {
commands[i] = new Forward(screens[i]);
}
executeCommands(commands);
}
/**
* Clear current stack and open several screens inside single transaction.
* @param screens
*/
public void newRootChain(Screen... screens) {
Command[] commands = new Command[screens.length + 1];
commands[0] = new BackTo(null);
if (screens.length > 0) {
commands[1] = new Replace(screens[0]);
for (int i = 1; i < screens.length; i++) {
commands[i + 1] = new Forward(screens[i]);
}
}
executeCommands(commands);
}
/**
* Remove all screens from the chain and exit.
* It's mostly used to finish the application or close a supplementary navigation chain.
*/
public void finishChain() {
executeCommands(
new BackTo(null),
new Back()
);
}
/**
* Return to the previous screen in the chain.
* Behavior in the case when the current screen is the root depends on
* the processing of the {@link Back} command in a {@link Navigator} implementation.
*/
public void exit() {
executeCommands(new Back());
}
}
@@ -1,12 +0,0 @@
package ru.terrakok.cicerone;
/**
* Screen is class for description application screen.
*/
public abstract class Screen {
protected String screenKey = getClass().getCanonicalName();
public String getScreenKey() {
return screenKey;
}
}
@@ -1,280 +0,0 @@
package ru.terrakok.cicerone.android.pure;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import java.util.LinkedList;
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;
/**
* Navigator implementation for launch fragments and activities.<br>
* Feature {@link BackTo} works only for fragments.<br>
* Recommendation: most useful for Single-Activity application.
*/
public class AppNavigator implements Navigator {
private final Activity activity;
private final FragmentManager fragmentManager;
private final int containerId;
private LinkedList<String> localStackCopy;
public AppNavigator(Activity activity, int containerId) {
this(activity, activity.getFragmentManager(), containerId);
}
public AppNavigator(Activity activity, FragmentManager fragmentManager, int containerId) {
this.activity = activity;
this.fragmentManager = fragmentManager;
this.containerId = containerId;
}
@Override
public void applyCommands(Command[] commands) {
fragmentManager.executePendingTransactions();
//copy stack before apply commands
copyStackToLocal();
for (Command command : commands) {
applyCommand(command);
}
}
private void copyStackToLocal() {
localStackCopy = new LinkedList<>();
final int stackSize = fragmentManager.getBackStackEntryCount();
for (int i = 0; i < stackSize; i++) {
localStackCopy.add(fragmentManager.getBackStackEntryAt(i).getName());
}
}
/**
* Perform transition described by the navigation command
*
* @param command the navigation command to apply
*/
protected void applyCommand(Command command) {
if (command instanceof Forward) {
activityForward((Forward) command);
} else if (command instanceof Replace) {
activityReplace((Replace) command);
} else if (command instanceof BackTo) {
backTo((BackTo) command);
} else if (command instanceof Back) {
fragmentBack();
}
}
protected void activityForward(Forward command) {
AppScreen screen = (AppScreen) command.getScreen();
Intent activityIntent = screen.getActivityIntent(activity);
// Start activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
checkAndStartActivity(screen, activityIntent, options);
} else {
fragmentForward(command);
}
}
protected void fragmentForward(Forward command) {
AppScreen screen = (AppScreen) command.getScreen();
Fragment fragment = createFragment(screen);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransaction(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(screen.getScreenKey())
.commit();
localStackCopy.add(screen.getScreenKey());
}
protected void fragmentBack() {
if (localStackCopy.size() > 0) {
fragmentManager.popBackStack();
localStackCopy.removeLast();
} else {
activityBack();
}
}
protected void activityBack() {
activity.finish();
}
protected void activityReplace(Replace command) {
AppScreen screen = (AppScreen) command.getScreen();
Intent activityIntent = screen.getActivityIntent(activity);
// Replace activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
checkAndStartActivity(screen, activityIntent, options);
activity.finish();
} else {
fragmentReplace(command);
}
}
protected void fragmentReplace(Replace command) {
AppScreen screen = (AppScreen) command.getScreen();
Fragment fragment = createFragment(screen);
if (localStackCopy.size() > 0) {
fragmentManager.popBackStack();
localStackCopy.removeLast();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransaction(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(screen.getScreenKey())
.commit();
localStackCopy.add(screen.getScreenKey());
} else {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransaction(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.commit();
}
}
/**
* Performs {@link BackTo} command transition
*/
protected void backTo(BackTo command) {
if (command.getScreen() == null) {
backToRoot();
} else {
String key = command.getScreen().getScreenKey();
int index = localStackCopy.indexOf(key);
int size = localStackCopy.size();
if (index != -1) {
for (int i = 1; i < size - index; i++) {
localStackCopy.removeLast();
}
fragmentManager.popBackStack(key, 0);
} else {
backToUnexisting((AppScreen) command.getScreen());
}
}
}
private void backToRoot() {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
localStackCopy.clear();
}
/**
* Override this method to setup fragment transaction {@link FragmentTransaction}.
* For example: setCustomAnimations(...), addSharedElement(...) or setReorderingAllowed(...)
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param currentFragment current fragment in container
* (for {@link Replace} command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
* @param fragmentTransaction fragment transaction
*/
protected void setupFragmentTransaction(Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
}
/**
* Override this method to create option for start activity
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param activityIntent activity intent
* @return transition options
*/
protected Bundle createStartActivityOptions(Command command, Intent activityIntent) {
return null;
}
private void checkAndStartActivity(AppScreen screen, Intent activityIntent, Bundle options) {
// Check if we can start activity
if (activityIntent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivity(activityIntent, options);
} else {
unexistingActivity(screen, activityIntent);
}
}
/**
* Called when there is no activity to open {@code screenKey}.
*
* @param screen screen
* @param activityIntent intent passed to start Activity for the {@code screenKey}
*/
protected void unexistingActivity(AppScreen screen, Intent activityIntent) {
// Do nothing by default
}
/**
* Creates Fragment matching {@code screenKey}.
*
* @param screen screen
* @return instantiated fragment for the passed screen
*/
protected Fragment createFragment(AppScreen screen) {
Fragment fragment = screen.getFragment();
if (fragment == null) {
errorWhileCreatingScreen(screen);
}
return fragment;
}
/**
* Called when we tried to fragmentBack to some specific screen (via {@link BackTo} command),
* but didn't found it.
*
* @param screen screen
*/
protected void backToUnexisting(AppScreen screen) {
backToRoot();
}
protected void errorWhileCreatingScreen(AppScreen screen) {
throw new RuntimeException("Can't create a screen: " + screen.getScreenKey());
}
}
@@ -1,23 +0,0 @@
package ru.terrakok.cicerone.android.pure;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import ru.terrakok.cicerone.Screen;
/**
* AppScreen is base class for description and creation application screen.<br>
* NOTE: If you have described the creation of Intent then Activity will be started.<br>
* Recommendation: Use Intents for launch external application.
*/
public abstract class AppScreen extends Screen {
public Fragment getFragment() {
return null;
}
public Intent getActivityIntent(Context context) {
return null;
}
}
@@ -1,281 +0,0 @@
package ru.terrakok.cicerone.android.support;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import java.util.LinkedList;
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;
/**
* Navigator implementation for launch fragments and activities.<br>
* Feature {@link BackTo} works only for fragments.<br>
* Recommendation: most useful for Single-Activity application.
*/
public class SupportAppNavigator implements Navigator {
private final Activity activity;
private final FragmentManager fragmentManager;
private final int containerId;
private LinkedList<String> localStackCopy;
public SupportAppNavigator(FragmentActivity activity, int containerId) {
this(activity, activity.getSupportFragmentManager(), containerId);
}
public SupportAppNavigator(FragmentActivity activity, FragmentManager fragmentManager, int containerId) {
this.activity = activity;
this.fragmentManager = fragmentManager;
this.containerId = containerId;
}
@Override
public void applyCommands(Command[] commands) {
fragmentManager.executePendingTransactions();
//copy stack before apply commands
copyStackToLocal();
for (Command command : commands) {
applyCommand(command);
}
}
private void copyStackToLocal() {
localStackCopy = new LinkedList<>();
final int stackSize = fragmentManager.getBackStackEntryCount();
for (int i = 0; i < stackSize; i++) {
localStackCopy.add(fragmentManager.getBackStackEntryAt(i).getName());
}
}
/**
* Perform transition described by the navigation command
*
* @param command the navigation command to apply
*/
protected void applyCommand(Command command) {
if (command instanceof Forward) {
activityForward((Forward) command);
} else if (command instanceof Replace) {
activityReplace((Replace) command);
} else if (command instanceof BackTo) {
backTo((BackTo) command);
} else if (command instanceof Back) {
fragmentBack();
}
}
protected void activityForward(Forward command) {
SupportAppScreen screen = (SupportAppScreen) command.getScreen();
Intent activityIntent = screen.getActivityIntent(activity);
// Start activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
checkAndStartActivity(screen, activityIntent, options);
} else {
fragmentForward(command);
}
}
protected void fragmentForward(Forward command) {
SupportAppScreen screen = (SupportAppScreen) command.getScreen();
Fragment fragment = createFragment(screen);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransaction(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(screen.getScreenKey())
.commit();
localStackCopy.add(screen.getScreenKey());
}
protected void fragmentBack() {
if (localStackCopy.size() > 0) {
fragmentManager.popBackStack();
localStackCopy.removeLast();
} else {
activityBack();
}
}
protected void activityBack() {
activity.finish();
}
protected void activityReplace(Replace command) {
SupportAppScreen screen = (SupportAppScreen) command.getScreen();
Intent activityIntent = screen.getActivityIntent(activity);
// Replace activity
if (activityIntent != null) {
Bundle options = createStartActivityOptions(command, activityIntent);
checkAndStartActivity(screen, activityIntent, options);
activity.finish();
} else {
fragmentReplace(command);
}
}
protected void fragmentReplace(Replace command) {
SupportAppScreen screen = (SupportAppScreen) command.getScreen();
Fragment fragment = createFragment(screen);
if (localStackCopy.size() > 0) {
fragmentManager.popBackStack();
localStackCopy.removeLast();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransaction(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.addToBackStack(screen.getScreenKey())
.commit();
localStackCopy.add(screen.getScreenKey());
} else {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
setupFragmentTransaction(
command,
fragmentManager.findFragmentById(containerId),
fragment,
fragmentTransaction
);
fragmentTransaction
.replace(containerId, fragment)
.commit();
}
}
/**
* Performs {@link BackTo} command transition
*/
protected void backTo(BackTo command) {
if (command.getScreen() == null) {
backToRoot();
} else {
String key = command.getScreen().getScreenKey();
int index = localStackCopy.indexOf(key);
int size = localStackCopy.size();
if (index != -1) {
for (int i = 1; i < size - index; i++) {
localStackCopy.removeLast();
}
fragmentManager.popBackStack(key, 0);
} else {
backToUnexisting((SupportAppScreen) command.getScreen());
}
}
}
private void backToRoot() {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
localStackCopy.clear();
}
/**
* Override this method to setup fragment transaction {@link FragmentTransaction}.
* For example: setCustomAnimations(...), addSharedElement(...) or setReorderingAllowed(...)
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param currentFragment current fragment in container
* (for {@link Replace} command it will be screen previous in new chain, NOT replaced screen)
* @param nextFragment next screen fragment
* @param fragmentTransaction fragment transaction
*/
protected void setupFragmentTransaction(Command command,
Fragment currentFragment,
Fragment nextFragment,
FragmentTransaction fragmentTransaction) {
}
/**
* Override this method to create option for start activity
*
* @param command current navigation command. Will be only {@link Forward} or {@link Replace}
* @param activityIntent activity intent
* @return transition options
*/
protected Bundle createStartActivityOptions(Command command, Intent activityIntent) {
return null;
}
private void checkAndStartActivity(SupportAppScreen screen, Intent activityIntent, Bundle options) {
// Check if we can start activity
if (activityIntent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivity(activityIntent, options);
} else {
unexistingActivity(screen, activityIntent);
}
}
/**
* Called when there is no activity to open {@code screenKey}.
*
* @param screen screen
* @param activityIntent intent passed to start Activity for the {@code screenKey}
*/
protected void unexistingActivity(SupportAppScreen screen, Intent activityIntent) {
// Do nothing by default
}
/**
* Creates Fragment matching {@code screenKey}.
*
* @param screen screen
* @return instantiated fragment for the passed screen
*/
protected Fragment createFragment(SupportAppScreen screen) {
Fragment fragment = screen.getFragment();
if (fragment == null) {
errorWhileCreatingScreen(screen);
}
return fragment;
}
/**
* Called when we tried to fragmentBack to some specific screen (via {@link BackTo} command),
* but didn't found it.
*
* @param screen screen
*/
protected void backToUnexisting(SupportAppScreen screen) {
backToRoot();
}
protected void errorWhileCreatingScreen(SupportAppScreen screen) {
throw new RuntimeException("Can't create a screen: " + screen.getScreenKey());
}
}
@@ -1,23 +0,0 @@
package ru.terrakok.cicerone.android.support;
import android.content.Context;
import android.content.Intent;
import androidx.fragment.app.Fragment;
import ru.terrakok.cicerone.Screen;
/**
* AppScreen is base class for description and creation application screen.<br>
* NOTE: If you have described the creation of Intent then Activity will be started.<br>
* Recommendation: Use Intents for launch external application.
*/
public abstract class SupportAppScreen extends Screen {
public Fragment getFragment() {
return null;
}
public Intent getActivityIntent(Context context) {
return null;
}
}
@@ -1,17 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone.commands;
/**
* Rolls fragmentBack the last transition from the screens chain.
*/
public class Back implements Command {
/**
* Creates a {@link Back} navigation command.
*/
public Back() {
}
}
@@ -1,30 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone.commands;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.Screen;
/**
* 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 {@link Navigator}.
* But the recommended behavior is to return to the root.
*/
public class BackTo implements Command {
private Screen screen;
/**
* Creates a {@link BackTo} navigation command.
*
* @param screen screen
*/
public BackTo(Screen screen) {
this.screen = screen;
}
public Screen getScreen() {
return screen;
}
}
@@ -1,12 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone.commands;
/**
* Navigation command describes screens transition.
* that can be processed by {@link ru.terrakok.cicerone.Navigator}.
*/
public interface Command {
}
@@ -1,27 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone.commands;
import ru.terrakok.cicerone.Screen;
/**
* Opens new screen.
*/
public class Forward implements Command {
private Screen screen;
/**
* Creates a {@link Forward} navigation command.
*
* @param screen screen
*/
public Forward(Screen screen) {
this.screen = screen;
}
public Screen getScreen() {
return screen;
}
}
@@ -1,27 +0,0 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone.commands;
import ru.terrakok.cicerone.Screen;
/**
* Replaces the current screen.
*/
public class Replace implements Command {
private Screen screen;
/**
* Creates a {@link Replace} navigation command.
*
* @param screen screen
*/
public Replace(Screen screen) {
this.screen = screen;
}
public Screen getScreen() {
return screen;
}
}
@@ -0,0 +1,40 @@
package com.github.terrakok.cicerone
/**
* BaseRouter is an abstract class to implement high-level navigation.
*
* Extend it to add needed transition methods.
*/
abstract class BaseRouter {
internal val commandBuffer = CommandBuffer()
private val resultWire = ResultWire()
/**
* Sets data listener with given key
* and returns [ResultListenerHandler] for availability to dispose subscription.
*
* After first call listener will be removed.
*/
fun setResultListener(
key: String,
listener: ResultListener
): ResultListenerHandler {
return resultWire.setResultListener(key, listener)
}
/**
* Sends data to listener with given key.
*/
fun sendResult(key: String, data: Any) {
resultWire.sendResult(key, data)
}
/**
* Sends navigation command array to [CommandBuffer].
*
* @param commands navigation command array to execute
*/
protected fun executeCommands(vararg commands: Command) {
commandBuffer.executeCommands(commands)
}
}
@@ -0,0 +1,30 @@
package com.github.terrakok.cicerone
/**
* Cicerone is the holder for other library components.
*
* To use it, instantiate it using one of the {@link #create()} methods.
*
* When you need a [NavigatorHolder] or router, get it here.
*
* @param router type of router. You can use the default [Router] or pass your own [BaseRouter] implementation.
*/
class Cicerone<T : BaseRouter> private constructor(val router: T) {
fun getNavigatorHolder(): NavigatorHolder = router.commandBuffer
companion object {
/**
* Creates the Cicerone instance with the default [Router]
*/
@JvmStatic
fun create() = create(Router())
/**
* Creates the Cicerone instance with the custom router.
* @param customRouter the custom router extending [BaseRouter]
*/
@JvmStatic
fun <T : BaseRouter> create(customRouter: T) = Cicerone(customRouter)
}
}
@@ -0,0 +1,35 @@
package com.github.terrakok.cicerone
import android.os.Handler
import android.os.Looper
/**
* Passes navigation command to an active [Navigator]
* or stores it in the pending commands queue to pass it later.
*/
internal class CommandBuffer : NavigatorHolder {
private var navigator: Navigator? = null
private val pendingCommands = mutableListOf<Array<out Command>>()
private val mainHandler = Handler(Looper.getMainLooper())
override fun setNavigator(navigator: Navigator) {
this.navigator = navigator
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>) {
mainHandler.post {
navigator?.applyCommands(commands) ?: pendingCommands.add(commands)
}
}
}
@@ -1,21 +1,14 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone;
import ru.terrakok.cicerone.commands.Command;
package com.github.terrakok.cicerone
/**
* The low-level navigation interface.
* Navigator is the one who actually performs any transition.
*/
public interface Navigator {
interface Navigator {
/**
* Performs transition described by the navigation command
*
* @param commands the navigation command array to apply per single transaction
*/
void applyCommands(Command[] commands);
}
fun applyCommands(commands: Array<out Command>)
}
@@ -1,24 +1,20 @@
/*
* Created by Konstantin Tskhovrebov (aka @terrakok)
*/
package ru.terrakok.cicerone;
package com.github.terrakok.cicerone
/**
* Navigator holder interface.
* Use it to connect a {@link Navigator} to the {@link Cicerone}.
*
* Use it to connect a [Navigator] to the [Cicerone].
*/
public interface NavigatorHolder {
interface NavigatorHolder {
/**
* Set an active Navigator for the Cicerone and start receive commands.
*
* @param navigator new active Navigator
*/
void setNavigator(Navigator navigator);
fun setNavigator(navigator: Navigator)
/**
* Remove the current Navigator and stop receive commands.
*/
void removeNavigator();
fun removeNavigator()
}
@@ -0,0 +1,30 @@
package com.github.terrakok.cicerone
/**
* Interface definition for a result callback.
*/
fun interface ResultListener {
fun onResult(data: Any)
}
/**
* Handler for manual delete subscription and avoid leak
*/
fun interface ResultListenerHandler {
fun dispose()
}
internal class ResultWire {
private val listeners = mutableMapOf<String, ResultListener>()
fun setResultListener(key: String, listener: ResultListener): ResultListenerHandler {
listeners[key] = listener
return ResultListenerHandler {
listeners.remove(key)
}
}
fun sendResult(key: String, data: Any) {
listeners.remove(key)?.onResult(data)
}
}
@@ -0,0 +1,97 @@
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
*/
fun navigateTo(screen: Screen) {
executeCommands(Forward(screen))
}
/**
* 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
*/
fun newChain(vararg screens: Screen) {
val commands = screens.map { Forward(it) }
executeCommands(*commands.toTypedArray())
}
/**
* Clear current stack and open several screens inside single transaction.
*
* @param screens
*/
fun newRootChain(vararg screens: Screen) {
val commands = screens.mapIndexed { index, screen ->
if (index == 0)
Replace(screen)
else
Forward(screen)
}
executeCommands(BackTo(null), *commands.toTypedArray())
}
/**
* Remove all screens from the chain and exit.
*
* It's mostly used to finish the application or close a supplementary navigation chain.
*/
fun finishChain() {
executeCommands(BackTo(null), Back())
}
/**
* Return to the previous screen in the chain.
*
* Behavior in the case when the current screen is the root depends on
* the processing of the [Back] command in a [Navigator] implementation.
*/
fun exit() {
executeCommands(Back())
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone
/**
* Screen is interface for description application screen.
*/
interface Screen {
val screenKey: String get() = this::class.java.name
}
@@ -0,0 +1,214 @@
package com.github.terrakok.cicerone.androidx
import android.content.ActivityNotFoundException
import android.content.Intent
import androidx.fragment.app.*
import com.github.terrakok.cicerone.*
/**
* Navigator implementation for launch fragments and activities.
*
* Feature [BackTo] works only for fragments.
*
* Recommendation: most useful for Single-Activity application.
*/
open class AppNavigator @JvmOverloads constructor(
protected val activity: FragmentActivity,
protected val containerId: Int,
protected val fragmentManager: FragmentManager = activity.supportFragmentManager,
protected val fragmentFactory: FragmentFactory = fragmentManager.fragmentFactory
) : Navigator {
protected val localStackCopy = mutableListOf<String>()
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) {
localStackCopy.add(fragmentManager.getBackStackEntryAt(i).name)
}
}
/**
* 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) {
is ActivityScreen -> {
checkAndStartActivity(screen)
}
is FragmentScreen -> {
commitNewFragmentScreen(screen, true)
}
}
}
protected open fun replace(command: Replace) {
when (val screen = command.screen) {
is ActivityScreen -> {
checkAndStartActivity(screen)
activity.finish()
}
is FragmentScreen -> {
if (localStackCopy.isNotEmpty()) {
fragmentManager.popBackStack()
localStackCopy.removeAt(localStackCopy.lastIndex)
commitNewFragmentScreen(screen, true)
} else {
commitNewFragmentScreen(screen, 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,
addToBackStack: Boolean
) {
val fragment = screen.createFragment(fragmentFactory)
val transaction = fragmentManager.beginTransaction()
transaction.setReorderingAllowed(true)
setupFragmentTransaction(
screen,
transaction,
fragmentManager.findFragmentById(containerId),
fragment
)
if (screen.clearContainer) {
transaction.replace(containerId, fragment, screen.screenKey)
} else {
transaction.add(containerId, fragment, screen.screenKey)
}
if (addToBackStack) {
transaction.addToBackStack(screen.screenKey)
localStackCopy.add(screen.screenKey)
}
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 }
if (index != -1) {
val forRemove = localStackCopy.subList(index, localStackCopy.size)
fragmentManager.popBackStack(forRemove.first().toString(), 0)
forRemove.clear()
} else {
backToUnexisting(command.screen)
}
}
}
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(
screen: FragmentScreen,
fragmentTransaction: FragmentTransaction,
currentFragment: Fragment?,
nextFragment: Fragment
) {
// Do nothing by default
}
private fun checkAndStartActivity(screen: ActivityScreen) {
// Check if we can start activity
val activityIntent = screen.createIntent(activity)
try {
activity.startActivity(activityIntent, screen.startActivityOptions)
} catch (e: ActivityNotFoundException) {
unexistingActivity(screen, activityIntent)
}
}
/**
* Called when there is no activity to open `screenKey`.
*
* @param screen screen
* @param activityIntent intent passed to start Activity for the `screenKey`
*/
protected open fun unexistingActivity(
screen: ActivityScreen,
activityIntent: Intent
) {
// Do nothing by default
}
/**
* Called when we tried to fragmentBack to some specific screen (via [BackTo] command),
* but didn't found it.
*
* @param screen screen
*/
protected open fun backToUnexisting(screen: Screen) {
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,46 @@
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
fun interface Creator<A, R> {
fun create(argument: A): R
}
interface FragmentScreen : Screen {
val clearContainer: Boolean get() = true
fun createFragment(factory: FragmentFactory): Fragment
companion object {
operator fun invoke(
key: String? = null,
clearContainer: Boolean = true,
fragmentCreator: Creator<FragmentFactory, Fragment>
) = object : FragmentScreen {
override val screenKey = key ?: fragmentCreator::class.java.name
override val clearContainer = clearContainer
override fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
}
}
}
interface ActivityScreen : Screen {
val startActivityOptions: Bundle? get() = null
fun createIntent(context: Context): Intent
companion object {
operator fun invoke(
key: String? = null,
startActivityOptions: Bundle? = null,
intentCreator: Creator<Context, Intent>
) = object : ActivityScreen {
override val screenKey = key ?: intentCreator::class.java.name
override val startActivityOptions = startActivityOptions
override fun createIntent(context: Context) = intentCreator.create(context)
}
}
}
@@ -0,0 +1,31 @@
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) : 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,7 +4,7 @@ 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
*/
@@ -3,7 +3,7 @@ package androidx.fragment.app;
import android.app.Activity;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@@ -0,0 +1,9 @@
package androidx.fragment.app;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentFactory {
}
@@ -1,7 +1,7 @@
package androidx.fragment.app;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@@ -36,6 +36,10 @@ public class FragmentManager {
throw new RuntimeException("Stub!");
}
public FragmentFactory getFragmentFactory() {
throw new RuntimeException("Stub!");
}
public interface BackStackEntry {
int getId();
@@ -1,12 +1,20 @@
package androidx.fragment.app;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
public class FragmentTransaction {
public FragmentTransaction replace(int containerViewId, Fragment fragment) {
public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
throw new RuntimeException("Stub!");
}
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
throw new RuntimeException("Stub!");
}
+27 -10
View File
@@ -1,7 +1,9 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
repositories {
jcenter()
mavenCentral()
}
android {
@@ -12,7 +14,7 @@ android {
targetSdkVersion 28
versionCode 1
versionName "1.0.0"
applicationId "ru.terrakok.cicerone.sample"
applicationId "com.github.terrakok.cicerone.sample"
}
buildTypes {
@@ -24,33 +26,48 @@ android {
debug {
}
}
buildFeatures {
viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
ext {
androidXVersion = "1.0.0"
moxyVersion = "1.4.6"
daggerVersion = "2.10"
androidXVersion = "1.3.0"
materialVersion = "1.4.0"
moxyVersion = "2.2.2"
daggerVersion = "2.38"
}
dependencies {
// Support libraries
implementation "androidx.appcompat:appcompat:$androidXVersion"
implementation "com.google.android.material:material:$androidXVersion"
implementation "com.google.android.material:material:$materialVersion"
//MVP Moxy
implementation "com.arello-mobile:moxy:$moxyVersion"
implementation "com.arello-mobile:moxy-app-compat:$moxyVersion"
annotationProcessor "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
implementation project(':library')
//DI
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$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.6.0"
}
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="ru.terrakok.cicerone.sample"
package="com.github.terrakok.cicerone.sample"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
@@ -0,0 +1,25 @@
package com.github.terrakok.cicerone.sample
import android.app.Application
import com.github.terrakok.cicerone.sample.dagger.AppComponent
import com.github.terrakok.cicerone.sample.dagger.DaggerAppComponent
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class SampleApplication : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent.builder().build()
}
override fun onCreate() {
super.onCreate()
INSTANCE = this
}
companion object {
lateinit var INSTANCE: SampleApplication
}
}
@@ -0,0 +1,67 @@
package com.github.terrakok.cicerone.sample
import android.content.Intent
import android.net.Uri
import com.github.terrakok.cicerone.androidx.ActivityScreen
import com.github.terrakok.cicerone.androidx.FragmentScreen
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
import com.github.terrakok.cicerone.sample.ui.bottom.ForwardFragment
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
import com.github.terrakok.cicerone.sample.ui.main.SemiTransparentFragment
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
object Screens {
fun Sample(number: Int) = FragmentScreen("Sample($number)") {
SampleFragment.getNewInstance(number)
}
fun Start() = ActivityScreen {
Intent(it, StartActivity::class.java)
}
fun Main() = ActivityScreen {
Intent(it, MainActivity::class.java)
}
fun BottomNavigation() = ActivityScreen {
Intent(it, BottomNavigationActivity::class.java)
}
fun Tab(tabName: String) = FragmentScreen {
TabContainerFragment.getNewInstance(tabName)
}
fun Forward(containerName: String, number: Int) = FragmentScreen {
ForwardFragment.getNewInstance(containerName, number)
}
fun Github() = ActivityScreen {
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"))
}
fun Profile() = ActivityScreen {
Intent(it, ProfileActivity::class.java)
}
fun ProfileInfo() = FragmentScreen {
ProfileFragment()
}
fun SelectPhoto(resultKey: String) = FragmentScreen {
SelectPhotoFragment.getNewInstance(resultKey)
}
fun SemiTransparent() = FragmentScreen(clearContainer = false) {
SemiTransparentFragment()
}
}
@@ -0,0 +1,43 @@
package com.github.terrakok.cicerone.sample.dagger
import com.github.terrakok.cicerone.sample.dagger.module.LocalNavigationModule
import com.github.terrakok.cicerone.sample.dagger.module.NavigationModule
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity
import com.github.terrakok.cicerone.sample.ui.bottom.TabContainerFragment
import com.github.terrakok.cicerone.sample.ui.main.MainActivity
import com.github.terrakok.cicerone.sample.ui.main.SampleFragment
import com.github.terrakok.cicerone.sample.ui.main.SemiTransparentFragment
import com.github.terrakok.cicerone.sample.ui.start.StartActivity
import dagger.Component
import javax.inject.Singleton
/**
* Created by terrakok 24.11.16
*/
@Singleton
@Component(modules = [
NavigationModule::class,
LocalNavigationModule::class]
)
interface AppComponent {
fun inject(activity: StartActivity)
fun inject(activity: MainActivity)
fun inject(fragment: SampleFragment)
fun inject(activity: BottomNavigationActivity)
fun inject(fragment: TabContainerFragment)
fun inject(fragment: ProfileFragment)
fun inject(fragment: SelectPhotoFragment)
fun inject(activity: ProfileActivity)
fun inject(fragment: SemiTransparentFragment)
}
@@ -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,35 @@
package com.github.terrakok.cicerone.sample.mvp.animation.photos
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class SelectPhotoPresenter(
private val resultKey: String,
private val router: Router
) : MvpPresenter<SelectPhotoView>() {
override fun onFirstViewAttach() {
super.onFirstViewAttach()
viewState!!.showPhotos(intArrayOf(
R.drawable.ava_1,
R.drawable.ava_2,
R.drawable.ava_3,
R.drawable.ava_4
))
}
fun onPhotoClick(photoRes: Int) {
router.sendResult(resultKey, photoRes)
router.exit()
}
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.animation.photos
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface SelectPhotoView : MvpView {
fun showPhotos(resourceIds: IntArray)
}
@@ -0,0 +1,44 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import com.github.terrakok.cicerone.ResultListenerHandler
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.Screens.SelectPhoto
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
class ProfilePresenter(
private val router: Router
) : MvpPresenter<ProfileView>() {
private var resultListenerHandler: ResultListenerHandler? = null
companion object {
private const val RESULT_KEY = "photo_result"
private const val DEFAULT_PHOTO = R.drawable.ava_1
}
override fun onFirstViewAttach() {
super.onFirstViewAttach()
viewState!!.showPhoto(DEFAULT_PHOTO)
}
fun onPhotoClicked() {
resultListenerHandler = router.setResultListener(RESULT_KEY) { data ->
viewState!!.showPhoto(data as Int)
}
router.navigateTo(SelectPhoto(RESULT_KEY))
}
override fun onDestroy() {
resultListenerHandler?.dispose()
super.onDestroy()
}
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.animation.profile
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface ProfileView : MvpView {
fun showPhoto(resId: Int)
}
@@ -0,0 +1,18 @@
package com.github.terrakok.cicerone.sample.mvp.bottom
import com.github.terrakok.cicerone.Router
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by terrakok 25.11.16
*/
@InjectViewState
class BottomNavigationPresenter(
private val router: Router
) : MvpPresenter<BottomNavigationView>() {
fun onBackPressed() {
router.exit()
}
}
@@ -0,0 +1,11 @@
package com.github.terrakok.cicerone.sample.mvp.bottom
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by terrakok 25.11.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface BottomNavigationView : MvpView
@@ -0,0 +1,42 @@
package com.github.terrakok.cicerone.sample.mvp.bottom.forward
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.Forward
import com.github.terrakok.cicerone.sample.Screens.Github
import moxy.InjectViewState
import moxy.MvpPresenter
/**
* Created by terrakok 26.11.16
*/
@InjectViewState
class ForwardPresenter(
private val container: String,
private val router: Router,
private val number: Int
) : MvpPresenter<ForwardView>() {
private fun createChain(number: Int): String {
var chain = "[0]"
for (i in 0 until number) {
chain += "" + (i + 1)
}
return chain
}
fun onForwardPressed() {
router.navigateTo(Forward(container, number + 1))
}
fun onGithubPressed() {
router.navigateTo(Github())
}
fun onBackPressed() {
router.exit()
}
init {
viewState?.setChainText(createChain(number))
}
}
@@ -0,0 +1,13 @@
package com.github.terrakok.cicerone.sample.mvp.bottom.forward
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by terrakok 26.11.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface ForwardView : MvpView {
fun setChainText(chainText: String)
}
@@ -0,0 +1,84 @@
package com.github.terrakok.cicerone.sample.mvp.main
import android.os.Handler
import android.os.Looper
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens
import com.github.terrakok.cicerone.sample.Screens.Sample
import moxy.InjectViewState
import moxy.MvpPresenter
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@InjectViewState
class SamplePresenter(
private val router: Router,
private val screenNumber: Int
) : MvpPresenter<SampleView>() {
private val executorService = Executors.newSingleThreadScheduledExecutor()
private var future: ScheduledFuture<*>? = null
fun onBackCommandClick() {
router.exit()
}
fun onForwardCommandClick() {
router.navigateTo(Sample(screenNumber + 1))
}
fun onReplaceCommandClick() {
router.replaceScreen(Sample(screenNumber + 1))
}
fun onNewChainCommandClick() {
router.newChain(
Sample(screenNumber + 1),
Sample(screenNumber + 2),
Sample(screenNumber + 3)
)
}
fun onNewRootChainCommandClick() {
router.newRootChain(
Sample(screenNumber + 1),
Sample(screenNumber + 2),
Sample(screenNumber + 3)
)
}
fun onFinishChainCommandClick() {
router.finishChain()
}
fun onForwardNccCommandClick() {
router.navigateTo(Screens.SemiTransparent())
}
fun onNewRootCommandClick() {
router.newRootScreen(Sample(screenNumber + 1))
}
fun onForwardWithDelayCommandClick() {
future?.cancel(true)
future = executorService.schedule({ //WARNING! Navigation must be only in UI thread.
Handler(Looper.getMainLooper()).post {
router.navigateTo(Sample(screenNumber + 1))
}
}, 5, TimeUnit.SECONDS)
}
fun onBackToCommandClick() {
router.backTo(Sample(3))
}
init {
viewState?.setTitle("Screen $screenNumber")
}
}
@@ -0,0 +1,14 @@
package com.github.terrakok.cicerone.sample.mvp.main
import moxy.MvpView
import moxy.viewstate.strategy.AddToEndSingleStrategy
import moxy.viewstate.strategy.StateStrategyType
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface SampleView : MvpView {
fun setTitle(title: String)
}
@@ -0,0 +1,30 @@
package com.github.terrakok.cicerone.sample.mvp.start
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.Screens.BottomNavigation
import com.github.terrakok.cicerone.sample.Screens.Main
import com.github.terrakok.cicerone.sample.Screens.Profile
import moxy.MvpPresenter
/**
* Created by terrakok 21.11.16
*/
class StartActivityPresenter(private val router: Router) : MvpPresenter<StartActivityView>() {
fun onOrdinaryPressed() {
router.navigateTo(Main())
}
fun onMultiPressed() {
router.navigateTo(BottomNavigation())
}
fun onResultWithAnimationPressed() {
router.navigateTo(Profile())
}
fun 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,94 @@
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.androidx.FragmentScreen
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.ProfileInfo
import com.github.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment
import com.github.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class ProfileActivity : AppCompatActivity() {
@Inject
lateinit var navigatorHolder: NavigatorHolder
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_container)
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(ProfileInfo())))
}
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
private val navigator: Navigator = object : AppNavigator(this, R.id.container) {
override fun setupFragmentTransaction(
screen: FragmentScreen,
fragmentTransaction: FragmentTransaction,
currentFragment: Fragment?,
nextFragment: Fragment
) {
if (currentFragment is ProfileFragment
&& nextFragment is SelectPhotoFragment) {
setupSharedElementForProfileToSelectPhoto(
currentFragment,
nextFragment,
fragmentTransaction
)
}
}
}
private fun setupSharedElementForProfileToSelectPhoto(profileFragment: ProfileFragment,
selectPhotoFragment: SelectPhotoFragment,
fragmentTransaction: FragmentTransaction) {
val changeBounds = ChangeBounds()
selectPhotoFragment.sharedElementEnterTransition = changeBounds
selectPhotoFragment.sharedElementReturnTransition = changeBounds
profileFragment.sharedElementEnterTransition = changeBounds
profileFragment.sharedElementReturnTransition = changeBounds
val view = profileFragment.avatarViewForAnimation
fragmentTransaction.addSharedElement(view!!, PHOTO_TRANSITION)
selectPhotoFragment.setAnimationDestinationId((view.tag as Int))
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentById(R.id.container)
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
super.onBackPressed()
}
}
companion object {
const val PHOTO_TRANSITION = "photo_trasition"
}
}
@@ -0,0 +1,112 @@
package com.github.terrakok.cicerone.sample.ui.animations.photos
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentSelectPhotoBinding
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoPresenter
import com.github.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class SelectPhotoFragment : MvpAppCompatFragment(), SelectPhotoView, BackButtonListener {
private lateinit var binding: FragmentSelectPhotoBinding
private lateinit var allImages: Array<ImageView>
@Inject
lateinit var router: Router
@InjectPresenter
lateinit var presenter: SelectPhotoPresenter
fun setAnimationDestinationId(resId: Int) {
var arguments = arguments
if (arguments == null) arguments = Bundle()
arguments.putInt(ARG_ANIM_DESTINATION, resId)
setArguments(arguments)
}
private val animationDestionationId: Int
get() = arguments!!.getInt(ARG_ANIM_DESTINATION)
private val resultKey: String
get() = arguments!!.getString(EXTRA_RESULT_KEY)!!
@ProvidePresenter
fun providePresenter() = SelectPhotoPresenter(resultKey, router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSelectPhotoBinding.inflate(inflater, container, false)
allImages = arrayOf(
binding.selectImage1,
binding.selectImage2,
binding.selectImage3,
binding.selectImage4
)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
allImages.forEach {
it.setOnClickListener(clickListener)
}
}
private val clickListener = View.OnClickListener { v ->
allImages.forEach {
it.transitionName = null
}
v.transitionName = ProfileActivity.PHOTO_TRANSITION
presenter.onPhotoClick((v.tag as Int))
}
override fun showPhotos(resourceIds: IntArray) {
if (resourceIds.size >= 4) {
//for shared element animation
val animRes = animationDestionationId
allImages.forEachIndexed {
index, imageView ->
imageView.setImageResource(resourceIds[index])
imageView.tag = resourceIds[index]
imageView.transitionName = if (animRes == resourceIds[index]) ProfileActivity.PHOTO_TRANSITION else null
}
}
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val ARG_ANIM_DESTINATION = "arg_anim_dest"
private const val EXTRA_RESULT_KEY = "extra_result_key"
fun getNewInstance(resultKey: String): SelectPhotoFragment {
return SelectPhotoFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_RESULT_KEY, resultKey)
}
}
}
}
}
@@ -0,0 +1,70 @@
package com.github.terrakok.cicerone.sample.ui.animations.profile
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfilePresenter
import com.github.terrakok.cicerone.sample.mvp.animation.profile.ProfileView
import com.github.terrakok.cicerone.sample.ui.animations.ProfileActivity
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
class ProfileFragment : MvpAppCompatFragment(), ProfileView, BackButtonListener {
private lateinit var avatar: ImageView
@Inject
lateinit var router: Router
@InjectPresenter
lateinit var presenter: ProfilePresenter
@ProvidePresenter
fun providePresenter() = ProfilePresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_profile, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
avatar = view.findViewById<View>(R.id.avatar_imageView) as ImageView
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
avatar.transitionName = ProfileActivity.PHOTO_TRANSITION
avatar.setOnClickListener { presenter.onPhotoClicked() }
}
override fun showPhoto(resId: Int) {
avatar.setImageResource(resId)
//for shared element animation
avatar.tag = resId
}
val avatarViewForAnimation: View?
get() = avatar
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
}
@@ -0,0 +1,119 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.Tab
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.BottomNavigationView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import moxy.MvpAppCompatActivity
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
import javax.inject.Inject
/**
* Created by terrakok 25.11.16
*/
class BottomNavigationActivity : MvpAppCompatActivity(), BottomNavigationView, RouterProvider {
private lateinit var bottomNavigationBar: BottomNavigationBar
@Inject
override lateinit var router: Router
@InjectPresenter
lateinit var presenter: BottomNavigationPresenter
@ProvidePresenter
fun createBottomNavigationPresenter() = BottomNavigationPresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bottom)
bottomNavigationBar = findViewById<View>(R.id.ab_bottom_navigation_bar) as BottomNavigationBar
initViews()
if (savedInstanceState == null) {
bottomNavigationBar.selectTab(0, true)
}
}
private fun initViews() {
bottomNavigationBar
.addItem(BottomNavigationItem(R.drawable.ic_android_white_24dp, R.string.tab_android))
.addItem(BottomNavigationItem(R.drawable.ic_bug_report_white_24dp, R.string.tab_bug))
.addItem(BottomNavigationItem(R.drawable.ic_pets_white_24dp, R.string.tab_dog))
.initialise()
bottomNavigationBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabSelected(position: Int) {
when (position) {
0 -> selectTab("ANDROID")
1 -> selectTab("BUG")
2 -> selectTab("DOG")
}
bottomNavigationBar.selectTab(position, false)
}
override fun onTabUnselected(position: Int) {}
override fun onTabReselected(position: Int) {
onTabSelected(position)
}
})
}
private fun selectTab(tab: String) {
val fm = supportFragmentManager
var currentFragment: Fragment? = null
val fragments = fm.fragments
for (f in fragments) {
if (f.isVisible) {
currentFragment = f
break
}
}
val newFragment = fm.findFragmentByTag(tab)
if (currentFragment != null && newFragment != null && currentFragment === newFragment) return
val transaction = fm.beginTransaction()
if (newFragment == null) {
transaction.add(
R.id.ab_container,
Tab(tab).createFragment(fm.fragmentFactory), tab
)
}
if (currentFragment != null) {
transaction.hide(currentFragment)
}
if (newFragment != null) {
transaction.show(newFragment)
}
transaction.commitNow()
}
override fun onBackPressed() {
val fm = supportFragmentManager
var fragment: Fragment? = null
val fragments = fm.fragments
for (f in fragments) {
if (f.isVisible) {
fragment = f
break
}
}
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
presenter.onBackPressed()
}
}
}
@@ -0,0 +1,69 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.terrakok.cicerone.sample.databinding.FragmentForwardBinding
import com.github.terrakok.cicerone.sample.mvp.bottom.forward.ForwardPresenter
import com.github.terrakok.cicerone.sample.mvp.bottom.forward.ForwardView
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import moxy.MvpAppCompatFragment
import moxy.presenter.InjectPresenter
import moxy.presenter.ProvidePresenter
/**
* Created by terrakok 26.11.16
*/
class ForwardFragment : MvpAppCompatFragment(), ForwardView, BackButtonListener {
private lateinit var binding: FragmentForwardBinding
@InjectPresenter
lateinit var presenter: ForwardPresenter
@ProvidePresenter
fun provideForwardPresenter(): ForwardPresenter {
return ForwardPresenter(
arguments!!.getString(EXTRA_NAME),
(parentFragment as RouterProvider).router,
arguments!!.getInt(EXTRA_NUMBER)
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentForwardBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.toolbar.title = arguments!!.getString(EXTRA_NAME)
binding.toolbar.setNavigationOnClickListener { presenter.onBackPressed() }
binding.forwardButton.setOnClickListener { presenter.onForwardPressed() }
binding.githubButton.setOnClickListener { presenter.onGithubPressed() }
}
override fun setChainText(chainText: String) {
binding.chainText.text = chainText
}
override fun onBackPressed(): Boolean {
presenter.onBackPressed()
return true
}
companion object {
private const val EXTRA_NAME = "extra_name"
private const val EXTRA_NUMBER = "extra_number"
fun getNewInstance(name: String?, number: Int): ForwardFragment {
return ForwardFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NAME, name)
putInt(EXTRA_NUMBER, number)
}
}
}
}
}
@@ -0,0 +1,88 @@
package com.github.terrakok.cicerone.sample.ui.bottom
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.github.terrakok.cicerone.Cicerone
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.Forward
import com.github.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import com.github.terrakok.cicerone.sample.ui.common.RouterProvider
import javax.inject.Inject
/**
* Created by terrakok 25.11.16
*/
class TabContainerFragment : Fragment(), RouterProvider, BackButtonListener {
private val navigator: Navigator by lazy {
AppNavigator(activity!!, R.id.ftc_container, childFragmentManager)
}
@Inject
lateinit var ciceroneHolder: LocalCiceroneHolder
private val containerName: String
get() = arguments!!.getString(EXTRA_NAME)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
private val cicerone: Cicerone<Router>
get() = ciceroneHolder.getCicerone(containerName)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_tab_container, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (childFragmentManager.findFragmentById(R.id.ftc_container) == null) {
cicerone.router.replaceScreen(Forward(containerName, 0))
}
}
override fun onResume() {
super.onResume()
cicerone.getNavigatorHolder().setNavigator(navigator)
}
override fun onPause() {
cicerone.getNavigatorHolder().removeNavigator()
super.onPause()
}
override val router: Router
get() = cicerone.router
override fun onBackPressed(): Boolean {
val fragment = childFragmentManager.findFragmentById(R.id.ftc_container)
return if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
true
} else {
(activity as RouterProvider?)!!.router.exit()
true
}
}
companion object {
private const val EXTRA_NAME = "tcf_extra_name"
fun getNewInstance(name: String?) =
TabContainerFragment().apply {
arguments = Bundle().apply {
putString(EXTRA_NAME, name)
}
}
}
}
@@ -0,0 +1,8 @@
package com.github.terrakok.cicerone.sample.ui.common
/**
* Created by terrakok 26.11.16
*/
interface BackButtonListener {
fun onBackPressed(): Boolean
}
@@ -0,0 +1,10 @@
package com.github.terrakok.cicerone.sample.ui.common
import com.github.terrakok.cicerone.Router
/**
* Created by terrakok 25.11.16
*/
interface RouterProvider {
val router: Router
}
@@ -0,0 +1,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(), ChainScreen {
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,13 @@
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>>
}
interface ChainScreen {
val name: String
val creationTime: Long
}
@@ -0,0 +1,95 @@
package com.github.terrakok.cicerone.sample.ui.main
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.github.terrakok.cicerone.Command
import com.github.terrakok.cicerone.Navigator
import com.github.terrakok.cicerone.NavigatorHolder
import com.github.terrakok.cicerone.Replace
import com.github.terrakok.cicerone.androidx.AppNavigator
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.Screens.Sample
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatActivity
import java.lang.ref.WeakReference
import java.util.*
import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok)
* on 11.10.16
*/
class MainActivity : MvpAppCompatActivity(), ChainHolder {
private lateinit var screensSchemeTV: TextView
override val chain = ArrayList<WeakReference<Fragment>>()
@Inject
lateinit var navigatorHolder: NavigatorHolder
private val navigator: Navigator = object : AppNavigator(this, R.id.main_container) {
override fun applyCommands(commands: Array<out Command>) {
super.applyCommands(commands)
supportFragmentManager.executePendingTransactions()
printScreensScheme()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
screensSchemeTV = findViewById<View>(R.id.screens_scheme) as TextView
if (savedInstanceState == null) {
navigator.applyCommands(arrayOf<Command>(Replace(Sample(1))))
} else {
printScreensScheme()
}
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentById(R.id.main_container)
if (fragment != null && fragment is BackButtonListener
&& (fragment as BackButtonListener).onBackPressed()) {
return
} else {
super.onBackPressed()
}
}
private fun printScreensScheme() {
val fragments = ArrayList<ChainScreen>()
for (fragmentReference in chain) {
val fragment = fragmentReference.get()
if (fragment != null && fragment is ChainScreen) {
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<String>()
for (fragment in fragments) {
keys.add(fragment.name)
}
screensSchemeTV.text = "Chain: $keys"
}
}
@@ -0,0 +1,89 @@
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))
override val name: String
get() = arguments!!.getInt(EXTRA_NUMBER).toString()
override val creationTime: Long
get() = arguments!!.getLong(EXTRA_TIME, 0L)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSampleBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.toolbar.setNavigationOnClickListener { presenter.onBackCommandClick() }
binding.backCommand.setOnClickListener { presenter.onBackCommandClick() }
binding.forwardCommand.setOnClickListener { presenter.onForwardCommandClick() }
binding.replaceCommand.setOnClickListener { presenter.onReplaceCommandClick() }
binding.newChainCommand.setOnClickListener { presenter.onNewChainCommandClick() }
binding.newRootChainCommand.setOnClickListener { presenter.onNewRootChainCommandClick() }
binding.newRootCommand.setOnClickListener { presenter.onNewRootCommandClick() }
binding.forwardDelayCommand.setOnClickListener { presenter.onForwardWithDelayCommandClick() }
binding.backToCommand.setOnClickListener { presenter.onBackToCommandClick() }
binding.finishChainCommand.setOnClickListener { presenter.onFinishChainCommandClick() }
binding.forwardNccCommand.setOnClickListener { presenter.onForwardNccCommandClick() }
}
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,46 @@
package com.github.terrakok.cicerone.sample.ui.main
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import com.github.terrakok.cicerone.Router
import com.github.terrakok.cicerone.sample.R
import com.github.terrakok.cicerone.sample.SampleApplication
import com.github.terrakok.cicerone.sample.databinding.FragmentSampleBinding
import com.github.terrakok.cicerone.sample.databinding.FragmentSemitransparentBinding
import com.github.terrakok.cicerone.sample.ui.common.BackButtonListener
import moxy.MvpAppCompatFragment
import javax.inject.Inject
class SemiTransparentFragment : BaseFragment(), BackButtonListener {
lateinit var binding: FragmentSemitransparentBinding
@Inject
lateinit var router: Router
override val name: String = "D"
override val creationTime: Long = System.currentTimeMillis()
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSemitransparentBinding.inflate(inflater)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.background.setOnClickListener { onBackPressed() }
binding.backButton.setOnClickListener { onBackPressed() }
}
override fun onBackPressed(): Boolean {
router.exit()
return true
}
}
@@ -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)
@ProvidePresenter
fun createStartActivityPresenter() = StartActivityPresenter(router)
override fun onCreate(savedInstanceState: Bundle?) {
SampleApplication.INSTANCE.appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_start)
initViews()
}
private fun initViews() {
findViewById<View>(R.id.ordinary_nav_button).setOnClickListener {
presenter.onOrdinaryPressed()
}
findViewById<View>(R.id.multi_nav_button).setOnClickListener {
presenter.onMultiPressed()
}
findViewById<View>(R.id.result_and_anim_button).setOnClickListener {
presenter.onResultWithAnimationPressed()
}
}
override fun onResume() {
super.onResume()
navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
navigatorHolder.removeNavigator()
super.onPause()
}
override fun onBackPressed() {
presenter.onBackPressed()
}
}
@@ -1,29 +0,0 @@
package ru.terrakok.cicerone.sample;
import android.app.Application;
import ru.terrakok.cicerone.sample.dagger.AppComponent;
import ru.terrakok.cicerone.sample.dagger.DaggerAppComponent;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class SampleApplication extends Application {
public static SampleApplication INSTANCE;
private AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
}
public AppComponent getAppComponent() {
if (appComponent == null) {
appComponent = DaggerAppComponent.builder().build();
}
return appComponent;
}
}
@@ -1,115 +0,0 @@
package ru.terrakok.cicerone.sample;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.fragment.app.Fragment;
import ru.terrakok.cicerone.android.support.SupportAppScreen;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment;
import ru.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment;
import ru.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity;
import ru.terrakok.cicerone.sample.ui.bottom.ForwardFragment;
import ru.terrakok.cicerone.sample.ui.bottom.TabContainerFragment;
import ru.terrakok.cicerone.sample.ui.main.MainActivity;
import ru.terrakok.cicerone.sample.ui.main.SampleFragment;
import ru.terrakok.cicerone.sample.ui.start.StartActivity;
/**
* Created by Konstantin Tckhovrebov (aka @terrakok)
* on 11.10.16
*/
public class Screens {
public static final class SampleScreen extends SupportAppScreen {
private final int number;
public SampleScreen(int number) {
this.number = number;
this.screenKey = getClass().getSimpleName() + "_" + number;
}
@Override
public Fragment getFragment() {
return SampleFragment.getNewInstance(number);
}
}
public static final class StartScreen extends SupportAppScreen {
@Override
public Intent getActivityIntent(Context context) {
return new Intent(context, StartActivity.class);
}
}
public static final class MainScreen extends SupportAppScreen {
@Override
public Intent getActivityIntent(Context context) {
return new Intent(context, MainActivity.class);
}
}
public static final class BottomNavigationScreen extends SupportAppScreen {
@Override
public Intent getActivityIntent(Context context) {
return new Intent(context, BottomNavigationActivity.class);
}
}
public static final class TabScreen extends SupportAppScreen {
private final String tabName;
public TabScreen(String tabName) {
this.tabName = tabName;
}
@Override
public Fragment getFragment() {
return TabContainerFragment.getNewInstance(tabName);
}
}
public static final class ForwardScreen extends SupportAppScreen {
private final String containerName;
private final int number;
public ForwardScreen(String containerName, int number) {
this.containerName = containerName;
this.number = number;
}
@Override
public Fragment getFragment() {
return ForwardFragment.getNewInstance(containerName, number);
}
}
public static final class GithubScreen extends SupportAppScreen {
@Override
public Intent getActivityIntent(Context context) {
return new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/terrakok/Cicerone"));
}
}
public static final class ProfileScreen extends SupportAppScreen {
@Override
public Intent getActivityIntent(Context context) {
return new Intent(context, ProfileActivity.class);
}
}
public static final class ProfileInfoScreen extends SupportAppScreen {
@Override
public Fragment getFragment() {
return new ProfileFragment();
}
}
public static final class SelectPhotoScreen extends SupportAppScreen {
@Override
public Fragment getFragment() {
return new SelectPhotoFragment();
}
}
}
@@ -1,45 +0,0 @@
package ru.terrakok.cicerone.sample.dagger;
import javax.inject.Singleton;
import dagger.Component;
import ru.terrakok.cicerone.sample.dagger.module.LocalNavigationModule;
import ru.terrakok.cicerone.sample.dagger.module.NavigationModule;
import ru.terrakok.cicerone.sample.dagger.module.PhotoSelectionModule;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment;
import ru.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment;
import ru.terrakok.cicerone.sample.ui.bottom.BottomNavigationActivity;
import ru.terrakok.cicerone.sample.ui.bottom.TabContainerFragment;
import ru.terrakok.cicerone.sample.ui.main.MainActivity;
import ru.terrakok.cicerone.sample.ui.main.SampleFragment;
import ru.terrakok.cicerone.sample.ui.start.StartActivity;
/**
* Created by terrakok 24.11.16
*/
@Singleton
@Component(modules = {
NavigationModule.class,
LocalNavigationModule.class,
PhotoSelectionModule.class
})
public interface AppComponent {
void inject(StartActivity activity);
void inject(MainActivity activity);
void inject(SampleFragment fragment);
void inject(BottomNavigationActivity activity);
void inject(TabContainerFragment fragment);
void inject(ProfileFragment fragment);
void inject(SelectPhotoFragment fragment);
void inject(ProfileActivity activity);
}
@@ -1,21 +0,0 @@
package ru.terrakok.cicerone.sample.dagger.module;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import ru.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder;
/**
* Created by terrakok 24.11.16
*/
@Module
public class LocalNavigationModule {
@Provides
@Singleton
LocalCiceroneHolder provideLocalNavigationHolder() {
return new LocalCiceroneHolder();
}
}
@@ -1,34 +0,0 @@
package ru.terrakok.cicerone.sample.dagger.module;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import ru.terrakok.cicerone.Cicerone;
import ru.terrakok.cicerone.NavigatorHolder;
import ru.terrakok.cicerone.Router;
/**
* Created by terrakok 24.11.16
*/
@Module
public class NavigationModule {
private Cicerone<Router> cicerone;
public NavigationModule() {
cicerone = Cicerone.create();
}
@Provides
@Singleton
Router provideRouter() {
return cicerone.getRouter();
}
@Provides
@Singleton
NavigatorHolder provideNavigatorHolder() {
return cicerone.getNavigatorHolder();
}
}
@@ -1,23 +0,0 @@
package ru.terrakok.cicerone.sample.dagger.module;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.mvp.animation.PhotoSelection;
/**
* Created by terrakok 24.11.16
*/
@Module
public class PhotoSelectionModule {
private PhotoSelection photoSelection = new PhotoSelection(R.drawable.ava_1);
@Provides
@Singleton
PhotoSelection providePhotoSelectionr() {
return photoSelection;
}
}
@@ -1,32 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.animation;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 22.09.18.
*/
public class PhotoSelection {
private int selectedPhoto;
private Listener listener;
public PhotoSelection(int defaultPhoto) {
this.selectedPhoto = defaultPhoto;
}
public int getSelectedPhoto() {
return selectedPhoto;
}
public void setSelectedPhoto(int selectedPhoto) {
this.selectedPhoto = selectedPhoto;
if (listener != null) {
listener.onChange(selectedPhoto);
}
}
public void setListener(Listener listener) {
this.listener = listener;
}
public interface Listener {
void onChange(int selectedPhoto);
}
}
@@ -1,44 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.animation.photos;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.mvp.animation.PhotoSelection;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
public class SelectPhotoPresenter extends MvpPresenter<SelectPhotoView> {
private Router router;
private PhotoSelection photoSelection;
public SelectPhotoPresenter(PhotoSelection photoSelection, Router router) {
this.photoSelection = photoSelection;
this.router = router;
}
@Override
protected void onFirstViewAttach() {
super.onFirstViewAttach();
getViewState().showPhotos(new int[] {
R.drawable.ava_1,
R.drawable.ava_2,
R.drawable.ava_3,
R.drawable.ava_4
});
}
public void onPhotoClick(int photoRes) {
photoSelection.setSelectedPhoto(photoRes);
router.exit();
}
public void onBackPressed() {
router.exit();
}
}
@@ -1,14 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.animation.photos;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy;
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy.class)
public interface SelectPhotoView extends MvpView {
void showPhotos(int[] resurceIds);
}
@@ -1,55 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.animation.profile;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.Screens;
import ru.terrakok.cicerone.sample.mvp.animation.PhotoSelection;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@InjectViewState
public class ProfilePresenter extends MvpPresenter<ProfileView> {
private Router router;
private PhotoSelection photoSelection;
public ProfilePresenter(PhotoSelection photoSelection, Router router) {
this.photoSelection = photoSelection;
this.router = router;
photoSelection.setListener(new PhotoSelection.Listener() {
@Override
public void onChange(int selectedPhoto) {
updatePhoto();
}
});
}
@Override
protected void onFirstViewAttach() {
super.onFirstViewAttach();
updatePhoto();
}
@Override
public void onDestroy() {
photoSelection.setListener(null);
super.onDestroy();
}
private void updatePhoto() {
getViewState().showPhoto(photoSelection.getSelectedPhoto());
}
public void onPhotoClicked() {
router.navigateTo(new Screens.SelectPhotoScreen());
}
public void onBackPressed() {
router.exit();
}
}
@@ -1,14 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.animation.profile;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy;
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
@StateStrategyType(AddToEndSingleStrategy.class)
public interface ProfileView extends MvpView {
void showPhoto(int resId);
}
@@ -1,22 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.bottom;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
/**
* Created by terrakok 25.11.16
*/
@InjectViewState
public class BottomNavigationPresenter extends MvpPresenter<BottomNavigationView> {
private Router router;
public BottomNavigationPresenter(Router router) {
this.router = router;
}
public void onBackPressed() {
router.exit();
}
}
@@ -1,12 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.bottom;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy;
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType;
/**
* Created by terrakok 25.11.16
*/
@StateStrategyType(AddToEndSingleStrategy.class)
public interface BottomNavigationView extends MvpView {
}
@@ -1,48 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.bottom.forward;
import com.arellomobile.mvp.InjectViewState;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.Screens;
/**
* Created by terrakok 26.11.16
*/
@InjectViewState
public class ForwardPresenter extends MvpPresenter<ForwardView> {
private String container;
private Router router;
private int number;
public ForwardPresenter(String container, Router router, int number) {
this.container = container;
this.router = router;
this.number = number;
getViewState().setChainText(createChain(number));
}
private String createChain(int number) {
String chain = "[0]";
for (int i = 0; i < number; i++) {
chain += "" + (i + 1);
}
return chain;
}
public void onForwardPressed() {
router.navigateTo(new Screens.ForwardScreen(container, number + 1));
}
public void onGithubPressed() {
router.navigateTo(new Screens.GithubScreen());
}
public void onBackPressed() {
router.exit();
}
}
@@ -1,14 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.bottom.forward;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy;
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType;
/**
* Created by terrakok 26.11.16
*/
@StateStrategyType(AddToEndSingleStrategy.class)
public interface ForwardView extends MvpView {
void setChainText(String chainText);
}
@@ -1,86 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.main;
import android.os.Handler;
import android.os.Looper;
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.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 router, int screenNumber) {
this.router = router;
this.screenNumber = screenNumber;
executorService = Executors.newSingleThreadScheduledExecutor();
getViewState().setTitle("Screen " + screenNumber);
}
public void onBackCommandClick() {
router.exit();
}
public void onForwardCommandClick() {
router.navigateTo(new Screens.SampleScreen(screenNumber + 1));
}
public void onReplaceCommandClick() {
router.replaceScreen(new Screens.SampleScreen(screenNumber + 1));
}
public void onNewChainCommandClick() {
router.newChain(
new Screens.SampleScreen(screenNumber + 1),
new Screens.SampleScreen(screenNumber + 2),
new Screens.SampleScreen(screenNumber + 3)
);
}
public void onFinishChainCommandClick() {
router.finishChain();
}
public void onNewRootCommandClick() {
router.newRootScreen(new Screens.SampleScreen(screenNumber + 1));
}
public void onForwardWithDelayCommandClick() {
if (future != null) future.cancel(true);
future = executorService.schedule(new Runnable() {
@Override
public void run() {
//WARNING! Navigation must be only in UI thread.
new Handler(Looper.getMainLooper()).post(
new Runnable() {
@Override
public void run() {
router.navigateTo(new Screens.SampleScreen(screenNumber + 1));
}
}
);
}
}, 5, TimeUnit.SECONDS);
}
public void onBackToCommandClick() {
router.backTo(new Screens.SampleScreen(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,33 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.start;
import com.arellomobile.mvp.MvpPresenter;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.Screens;
/**
* Created by terrakok 21.11.16
*/
public class StartActivityPresenter extends MvpPresenter<StartActivityView> {
private Router router;
public StartActivityPresenter(Router router) {
this.router = router;
}
public void onOrdinaryPressed() {
router.navigateTo(new Screens.MainScreen());
}
public void onMultiPressed() {
router.navigateTo(new Screens.BottomNavigationScreen());
}
public void onResultWithAnimationPressed() {
router.navigateTo(new Screens.ProfileScreen());
}
public void onBackPressed() {
router.exit();
}
}
@@ -1,9 +0,0 @@
package ru.terrakok.cicerone.sample.mvp.start;
import com.arellomobile.mvp.MvpView;
/**
* Created by terrakok 21.11.16
*/
public interface StartActivityView extends MvpView {
}
@@ -1,24 +0,0 @@
package ru.terrakok.cicerone.sample.subnavigation;
import java.util.HashMap;
import ru.terrakok.cicerone.Cicerone;
import ru.terrakok.cicerone.Router;
/**
* Created by terrakok 27.11.16
*/
public class LocalCiceroneHolder {
private HashMap<String, Cicerone<Router>> containers;
public LocalCiceroneHolder() {
containers = new HashMap<>();
}
public Cicerone<Router> getCicerone(String containerTag) {
if (!containers.containsKey(containerTag)) {
containers.put(containerTag, Cicerone.create());
}
return containers.get(containerTag);
}
}
@@ -1,100 +0,0 @@
package ru.terrakok.cicerone.sample.ui.animations;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.transition.ChangeBounds;
import android.view.View;
import javax.inject.Inject;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.NavigatorHolder;
import ru.terrakok.cicerone.android.support.SupportAppNavigator;
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;
import ru.terrakok.cicerone.sample.ui.animations.photos.SelectPhotoFragment;
import ru.terrakok.cicerone.sample.ui.animations.profile.ProfileFragment;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
public class ProfileActivity extends AppCompatActivity {
public static final String PHOTO_TRANSITION = "photo_trasition";
@Inject
NavigatorHolder navigatorHolder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_container);
if (savedInstanceState == null) {
navigator.applyCommands(new Command[]{new Replace(new Screens.ProfileInfoScreen())});
}
}
@Override
protected void onResumeFragments() {
super.onResumeFragments();
navigatorHolder.setNavigator(navigator);
}
@Override
protected void onPause() {
navigatorHolder.removeNavigator();
super.onPause();
}
private Navigator navigator = new SupportAppNavigator(this, R.id.container) {
@Override
protected void setupFragmentTransaction(Command command, Fragment currentFragment, Fragment nextFragment, FragmentTransaction fragmentTransaction) {
if (command instanceof Forward
&& currentFragment instanceof ProfileFragment
&& nextFragment instanceof SelectPhotoFragment) {
setupSharedElementForProfileToSelectPhoto(
(ProfileFragment) currentFragment,
(SelectPhotoFragment) nextFragment,
fragmentTransaction
);
}
}
};
private void setupSharedElementForProfileToSelectPhoto(ProfileFragment profileFragment,
SelectPhotoFragment selectPhotoFragment,
FragmentTransaction fragmentTransaction) {
ChangeBounds changeBounds = new ChangeBounds();
selectPhotoFragment.setSharedElementEnterTransition(changeBounds);
selectPhotoFragment.setSharedElementReturnTransition(changeBounds);
profileFragment.setSharedElementEnterTransition(changeBounds);
profileFragment.setSharedElementReturnTransition(changeBounds);
View view = profileFragment.getAvatarViewForAnimation();
fragmentTransaction.addSharedElement(view, PHOTO_TRANSITION);
selectPhotoFragment.setAnimationDestinationId((Integer) view.getTag());
}
@Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
if (fragment != null
&& fragment instanceof BackButtonListener
&& ((BackButtonListener) fragment).onBackPressed()) {
return;
} else {
super.onBackPressed();
}
}
}
@@ -1,132 +0,0 @@
package ru.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 androidx.annotation.Nullable;
import com.arellomobile.mvp.MvpAppCompatFragment;
import com.arellomobile.mvp.presenter.InjectPresenter;
import com.arellomobile.mvp.presenter.ProvidePresenter;
import javax.inject.Inject;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.mvp.animation.PhotoSelection;
import ru.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoPresenter;
import ru.terrakok.cicerone.sample.mvp.animation.photos.SelectPhotoView;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
public class SelectPhotoFragment extends MvpAppCompatFragment
implements SelectPhotoView, BackButtonListener {
private static final String ARG_ANIM_DESTINATION = "arg_anim_dest";
private ImageView photo1;
private ImageView photo2;
private ImageView photo3;
private ImageView photo4;
@Inject
Router router;
@Inject
PhotoSelection photoSelection;
@InjectPresenter
SelectPhotoPresenter presenter;
public void setAnimationDestinationId(int resId) {
Bundle arguments = getArguments();
if (arguments == null) arguments = new Bundle();
arguments.putInt(ARG_ANIM_DESTINATION, resId);
setArguments(arguments);
}
private int getAnimationDestionationId() {
return getArguments().getInt(ARG_ANIM_DESTINATION);
}
@ProvidePresenter
SelectPhotoPresenter providePresenter() {
return new SelectPhotoPresenter(photoSelection, router);
}
@Override
public void onCreate(Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_select_photo, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
photo1 = (ImageView) view.findViewById(R.id.select_image_1);
photo2 = (ImageView) view.findViewById(R.id.select_image_2);
photo3 = (ImageView) view.findViewById(R.id.select_image_3);
photo4 = (ImageView) view.findViewById(R.id.select_image_4);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
photo1.setOnClickListener(clickListener);
photo2.setOnClickListener(clickListener);
photo3.setOnClickListener(clickListener);
photo4.setOnClickListener(clickListener);
}
private View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
photo1.setTransitionName(null);
photo2.setTransitionName(null);
photo3.setTransitionName(null);
photo4.setTransitionName(null);
v.setTransitionName(ProfileActivity.PHOTO_TRANSITION);
presenter.onPhotoClick((Integer) v.getTag());
}
};
@Override
public void showPhotos(int[] resurceIds) {
if (resurceIds.length >= 4) {
photo1.setImageResource(resurceIds[0]);
photo2.setImageResource(resurceIds[1]);
photo3.setImageResource(resurceIds[2]);
photo4.setImageResource(resurceIds[3]);
photo1.setTag(resurceIds[0]);
photo2.setTag(resurceIds[1]);
photo3.setTag(resurceIds[2]);
photo4.setTag(resurceIds[3]);
//for shared element animation
int animRes = getAnimationDestionationId();
photo1.setTransitionName(animRes == resurceIds[0] ? ProfileActivity.PHOTO_TRANSITION : null);
photo2.setTransitionName(animRes == resurceIds[1] ? ProfileActivity.PHOTO_TRANSITION : null);
photo3.setTransitionName(animRes == resurceIds[2] ? ProfileActivity.PHOTO_TRANSITION : null);
photo4.setTransitionName(animRes == resurceIds[3] ? ProfileActivity.PHOTO_TRANSITION : null);
}
}
@Override
public boolean onBackPressed() {
presenter.onBackPressed();
return true;
}
}
@@ -1,93 +0,0 @@
package ru.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 androidx.annotation.Nullable;
import com.arellomobile.mvp.MvpAppCompatFragment;
import com.arellomobile.mvp.presenter.InjectPresenter;
import com.arellomobile.mvp.presenter.ProvidePresenter;
import javax.inject.Inject;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.mvp.animation.PhotoSelection;
import ru.terrakok.cicerone.sample.mvp.animation.profile.ProfilePresenter;
import ru.terrakok.cicerone.sample.mvp.animation.profile.ProfileView;
import ru.terrakok.cicerone.sample.ui.animations.ProfileActivity;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 14.07.17.
*/
public class ProfileFragment extends MvpAppCompatFragment implements ProfileView, BackButtonListener {
private ImageView avatar;
@Inject
Router router;
@InjectPresenter
ProfilePresenter presenter;
@Inject
PhotoSelection photoSelection;
@ProvidePresenter
ProfilePresenter providePresenter() {
return new ProfilePresenter(photoSelection, router);
}
@Override
public void onCreate(Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_profile, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
avatar = (ImageView) view.findViewById(R.id.avatar_imageView);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
avatar.setTransitionName(ProfileActivity.PHOTO_TRANSITION);
avatar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.onPhotoClicked();
}
});
}
@Override
public void showPhoto(int resId) {
avatar.setImageResource(resId);
//for shared element animation
avatar.setTag(resId);
}
public View getAvatarViewForAnimation() {
return avatar;
}
@Override
public boolean onBackPressed() {
presenter.onBackPressed();
return true;
}
}
@@ -1,153 +0,0 @@
package ru.terrakok.cicerone.sample.ui.bottom;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.arellomobile.mvp.MvpAppCompatActivity;
import com.arellomobile.mvp.presenter.InjectPresenter;
import com.arellomobile.mvp.presenter.ProvidePresenter;
import com.ashokvarma.bottomnavigation.BottomNavigationBar;
import com.ashokvarma.bottomnavigation.BottomNavigationItem;
import java.util.List;
import javax.inject.Inject;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.Screens;
import ru.terrakok.cicerone.sample.mvp.bottom.BottomNavigationPresenter;
import ru.terrakok.cicerone.sample.mvp.bottom.BottomNavigationView;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
import ru.terrakok.cicerone.sample.ui.common.RouterProvider;
/**
* Created by terrakok 25.11.16
*/
public class BottomNavigationActivity extends MvpAppCompatActivity
implements BottomNavigationView, RouterProvider {
private BottomNavigationBar bottomNavigationBar;
@Inject
Router router;
@InjectPresenter
BottomNavigationPresenter presenter;
@ProvidePresenter
public BottomNavigationPresenter createBottomNavigationPresenter() {
return new BottomNavigationPresenter(router);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom);
bottomNavigationBar = (BottomNavigationBar) findViewById(R.id.ab_bottom_navigation_bar);
initViews();
if (savedInstanceState == null) {
bottomNavigationBar.selectTab(0, true);
}
}
private void initViews() {
bottomNavigationBar
.addItem(new BottomNavigationItem(R.drawable.ic_android_white_24dp, R.string.tab_android))
.addItem(new BottomNavigationItem(R.drawable.ic_bug_report_white_24dp, R.string.tab_bug))
.addItem(new BottomNavigationItem(R.drawable.ic_pets_white_24dp, R.string.tab_dog))
.initialise();
bottomNavigationBar.setTabSelectedListener(new BottomNavigationBar.OnTabSelectedListener() {
@Override
public void onTabSelected(int position) {
switch (position) {
case 0:
selectTab("ANDROID");
break;
case 1:
selectTab("BUG");
break;
case 2:
selectTab("DOG");
break;
}
bottomNavigationBar.selectTab(position, false);
}
@Override
public void onTabUnselected(int position) {
}
@Override
public void onTabReselected(int position) {
onTabSelected(position);
}
});
}
private void selectTab(String tab) {
FragmentManager fm = getSupportFragmentManager();
Fragment currentFragment = null;
List<Fragment> fragments = fm.getFragments();
if (fragments != null) {
for (Fragment f : fragments) {
if (f.isVisible()) {
currentFragment = f;
break;
}
}
}
Fragment newFragment = fm.findFragmentByTag(tab);
if (currentFragment != null && newFragment != null && currentFragment == newFragment) return;
FragmentTransaction transaction = fm.beginTransaction();
if (newFragment == null) {
transaction.add(R.id.ab_container, new Screens.TabScreen(tab).getFragment(), tab);
}
if (currentFragment != null) {
transaction.hide(currentFragment);
}
if (newFragment != null) {
transaction.show(newFragment);
}
transaction.commitNow();
}
@Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = null;
List<Fragment> fragments = fm.getFragments();
if (fragments != null) {
for (Fragment f : fragments) {
if (f.isVisible()) {
fragment = f;
break;
}
}
}
if (fragment != null
&& fragment instanceof BackButtonListener
&& ((BackButtonListener) fragment).onBackPressed()) {
return;
} else {
presenter.onBackPressed();
}
}
@Override
public Router getRouter() {
return router;
}
}
@@ -1,105 +0,0 @@
package ru.terrakok.cicerone.sample.ui.bottom;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import com.arellomobile.mvp.MvpAppCompatFragment;
import com.arellomobile.mvp.presenter.InjectPresenter;
import com.arellomobile.mvp.presenter.ProvidePresenter;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.mvp.bottom.forward.ForwardPresenter;
import ru.terrakok.cicerone.sample.mvp.bottom.forward.ForwardView;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
import ru.terrakok.cicerone.sample.ui.common.RouterProvider;
/**
* Created by terrakok 26.11.16
*/
public class ForwardFragment extends MvpAppCompatFragment implements ForwardView, BackButtonListener {
private static final String EXTRA_NAME = "extra_name";
private static final String EXTRA_NUMBER = "extra_number";
private Toolbar toolbar;
private TextView chainTV;
private View forwardBt;
private View githubBt;
@InjectPresenter
ForwardPresenter presenter;
@ProvidePresenter
ForwardPresenter provideForwardPresenter() {
return new ForwardPresenter(
getArguments().getString(EXTRA_NAME),
((RouterProvider) getParentFragment()).getRouter(),
getArguments().getInt(EXTRA_NUMBER)
);
}
public static ForwardFragment getNewInstance(String name, int number) {
ForwardFragment fragment = new ForwardFragment();
Bundle arguments = new Bundle();
arguments.putString(EXTRA_NAME, name);
arguments.putInt(EXTRA_NUMBER, number);
fragment.setArguments(arguments);
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_forward, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
toolbar = (Toolbar) view.findViewById(R.id.toolbar);
chainTV = (TextView) view.findViewById(R.id.chain_text);
forwardBt = view.findViewById(R.id.forward_button);
githubBt = view.findViewById(R.id.github_button);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
toolbar.setTitle(getArguments().getString(EXTRA_NAME));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onBackPressed();
}
});
forwardBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onForwardPressed();
}
});
githubBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.onGithubPressed();
}
});
}
@Override
public void setChainText(String chainText) {
chainTV.setText(chainText);
}
@Override
public boolean onBackPressed() {
presenter.onBackPressed();
return true;
}
}
@@ -1,109 +0,0 @@
package ru.terrakok.cicerone.sample.ui.bottom;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import javax.inject.Inject;
import ru.terrakok.cicerone.Cicerone;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.android.support.SupportAppNavigator;
import ru.terrakok.cicerone.sample.R;
import ru.terrakok.cicerone.sample.SampleApplication;
import ru.terrakok.cicerone.sample.Screens;
import ru.terrakok.cicerone.sample.subnavigation.LocalCiceroneHolder;
import ru.terrakok.cicerone.sample.ui.common.BackButtonListener;
import ru.terrakok.cicerone.sample.ui.common.RouterProvider;
/**
* Created by terrakok 25.11.16
*/
public class TabContainerFragment extends Fragment implements RouterProvider, BackButtonListener {
private static final String EXTRA_NAME = "tcf_extra_name";
private Navigator navigator;
@Inject
LocalCiceroneHolder ciceroneHolder;
public static TabContainerFragment getNewInstance(String name) {
TabContainerFragment fragment = new TabContainerFragment();
Bundle arguments = new Bundle();
arguments.putString(EXTRA_NAME, name);
fragment.setArguments(arguments);
return fragment;
}
private String getContainerName() {
return getArguments().getString(EXTRA_NAME);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
SampleApplication.INSTANCE.getAppComponent().inject(this);
super.onCreate(savedInstanceState);
}
private Cicerone<Router> getCicerone() {
return ciceroneHolder.getCicerone(getContainerName());
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_tab_container, container, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getChildFragmentManager().findFragmentById(R.id.ftc_container) == null) {
getCicerone().getRouter().replaceScreen(new Screens.ForwardScreen(getContainerName(), 0));
}
}
@Override
public void onResume() {
super.onResume();
getCicerone().getNavigatorHolder().setNavigator(getNavigator());
}
@Override
public void onPause() {
getCicerone().getNavigatorHolder().removeNavigator();
super.onPause();
}
private Navigator getNavigator() {
if (navigator == null) {
navigator = new SupportAppNavigator(getActivity(), getChildFragmentManager(), R.id.ftc_container);
}
return navigator;
}
@Override
public Router getRouter() {
return getCicerone().getRouter();
}
@Override
public boolean onBackPressed() {
Fragment fragment = getChildFragmentManager().findFragmentById(R.id.ftc_container);
if (fragment != null
&& fragment instanceof BackButtonListener
&& ((BackButtonListener) fragment).onBackPressed()) {
return true;
} else {
((RouterProvider) getActivity()).getRouter().exit();
return true;
}
}
}
@@ -1,8 +0,0 @@
package ru.terrakok.cicerone.sample.ui.common;
/**
* Created by terrakok 26.11.16
*/
public interface BackButtonListener {
boolean onBackPressed();
}
@@ -1,10 +0,0 @@
package ru.terrakok.cicerone.sample.ui.common;
import ru.terrakok.cicerone.Router;
/**
* Created by terrakok 25.11.16
*/
public interface RouterProvider {
Router getRouter();
}

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