Compare commits

...

50 Commits

Author SHA1 Message Date
Eric Kuck 6f856453c1 Version bump 2018-12-18 14:41:18 -06:00
Eric Kuck a6c50fccca Fix for onAttach potentially being called after destroy 2018-12-18 14:21:48 -06:00
Shaishav Gandhi 9ad140d00f Upgrade AutoDispose to 1.0.0 (#497)
* Upgrade AutoDispose to 1.0.0
2018-11-04 07:36:53 +01:00
Paul Woitaschek bb173b7cdc Migrate to AndroidX. Fixes #490 (#492)
* Gradle 4.10.2
* Android Build tools 3.2.1
* compileSdk 27, targetSdk 27, support libraries 28.0.0
* Migrated to androidX
2018-10-22 20:10:50 +02:00
Andy Byrnes 5f68328131 Fix README typos (#479) 2018-09-18 15:49:47 -05:00
Eric Kuck 61826fb42b Added a list of 3rd party modules to the readme 2018-09-04 11:35:09 -07:00
Shaishav Gandhi 6b5495a13b Update AutoDispose to 1.0.0-RC2 (#469)
* Upgrade AutoDispose to 1.0.0-RC2

Signed-off-by: shaishavgandhi05 <shaishgandhi@gmail.com>

* Remove fully qualified reference

Signed-off-by: shaishavgandhi05 <shaishgandhi@gmail.com>
2018-08-22 09:26:50 -05:00
Eric Kuck 7a24f77b59 No longer re-attaches controllers that shouldn't be attached when popController(controller) is called. Fixes #463. 2018-08-09 14:40:15 -05:00
Paul Woitaschek 562ed171c3 Specify the current api for the IssueRegistry. (#460) 2018-08-02 09:18:56 +02:00
Eric Kuck 241f052a7b Version bump 2018-08-01 16:19:16 -05:00
Eric Kuck f239376b03 Added a more helpful exception when TransactionIndexer object can't be found. 2018-08-01 12:32:00 -05:00
Eric Kuck 9dc5e5def6 setBackstack with an empty list will now remove all visible views when it's supposed to 2018-07-31 15:43:19 -05:00
Eric Kuck 23d2bab764 Fixes potential out-of-order attach calls for children if the framework itself calls them out of order. (Fixes #416) 2018-07-29 22:42:42 -05:00
Eric Kuck 814121d686 Ensures getTransactionIndexer() will never throw a StackOverflowException 2018-07-29 21:37:05 -05:00
Eric Kuck 4a5442e821 Adds more Autodispose options (fixes #452) 2018-07-29 21:33:15 -05:00
Eric Kuck 3b67822b25 Fixed another lifecycle call order issue with Android P 2018-07-19 14:01:31 -05:00
Eric Kuck 24754364c8 Fix rotation issue for SDK 28 (fixes #457) 2018-07-18 13:32:24 -05:00
Eric Kuck 131589670f Now removes savedPageHistory for pages that are reinstated (and therefore no longer saved) 2018-06-20 15:36:34 -05:00
Dhi Aurrahman efca85829b Fix unresolved dep, failed to resolve: common (#443)
Thanks :)
2018-06-12 13:11:26 +02:00
inorichi 3d64903e54 Fix demo not compiling after dependency updates (#437)
LGTM 👍
2018-06-12 10:08:56 +02:00
Eric Kuck ad3b4911be Fixes #367 2018-06-06 14:58:58 -05:00
Eric Kuck a0d92a631f Fixes #432 2018-05-30 13:53:07 -05:00
Eric Kuck f1b38219b4 Fixes #425 2018-05-30 10:58:18 -05:00
Eric Kuck d59e9ccabf Fixes #429 2018-05-30 10:46:54 -05:00
Eric Kuck 4c81db3a09 Fixes #394 2018-05-29 16:01:12 -05:00
Eric Kuck a8cfd4be9f Added a first draft of contribution guidelines 2018-05-29 15:39:08 -05:00
Eric Kuck 894601700d Fixed potential ConcurrentModificationExceptions if developers added or removed routers during lifecycle callbacks 2018-05-29 15:05:36 -05:00
Eric Kuck d72951eb04 Fixed up lint calls and related tests 2018-05-29 14:43:16 -05:00
Eric Kuck ca3ec3ac12 Fixed false positives for new UAST lint checks 2018-05-29 12:13:51 -05:00
Paul Woitaschek 5ce857bb24 Dependency update & Migrate Lint to UAST (#433)
* Use Android Sdk 27

* Updated the unmock plugin and use the default configuration.

* Removed the explicit buildTools and use the one bundled with the plugin.

* Updated the support library, butterknife, leakCanary, rxjava 1 & 2,
rxlifecycle 1 & 2, junit and robolectric.

* Migrated the lint checks to UAST.

* Rename the UClass "declaration" to "field" to be consistent to the
other check and the default name.

* Moved the evaluator of the ControllerIssueDetector to the handler too.
2018-05-29 11:31:32 -05:00
Eric Kuck f6512d3e1c Fixed build 2018-05-29 11:14:28 -05:00
Eric Kuck 52f095d945 Codestyle updates 2018-05-25 14:45:58 -05:00
Stephan Schuster 91da937d4f Improved lifecycle integration of Conductor and Architecture Components (#383)
Previous mapping was:
- () -> ON_CREATE -> CREATED
- preCreateView -> ON_START -> STARTED
- preAttach -> ON_RESUME -> RESUMED
- preDetach -> ON_PAUSE -> STARTED
- preDestroyView -> ON_STOP -> CREATED
- preDestroy -> ON_DESTROY -> DESTROYED

New mapping is:
- () -> INITIALIZED
- postContextAvailable -> ON_CREATE -> CREATED
- postCreateView -> ON_START -> STARTED
- postAttach -> ON_RESUME -> RESUMED
- preDetach -> ON_PAUSE -> STARTED
- preDestroyView -> ON_STOP -> CREATED
- preContextUnavailable -> // do nothing
- preDestroy -> ON_DESTROY -> DESTROYED

Change-Id: I9f65480018dc1204dc89a8065fa7c7f554ad7dd2
2018-05-25 14:41:06 -05:00
Eric Kuck 9e3095957c Autodispose dependency update, fixed build 2018-05-25 14:38:23 -05:00
Eric Kuck fac44b74e9 build.gradle updates 2018-05-25 13:26:48 -05:00
Daniel Stout ed6ba3a5eb Fix ConcurrentModificationException thrown in Router.popController (#406) 2018-05-25 13:15:25 -05:00
Paul Woitaschek df70e983be Removed synthetic accessors (#347)
* Removed synthetic accessors.

* Upon copying child routers, init the list with the new size and use
addAll.

* Initialize some lists with an appropriate initial capacity.

* Don't wrap methods.
2017-10-09 14:55:35 -05:00
Stefan M 4d28dcefac Styles snapshot section (#364)
* Add missing curly braces
* Make snapshot bold and remove double point
* Fix typo
2017-10-09 11:46:22 -05:00
Eric Kuck 8ab528a566 Fixed ordering of onContextAvailable calls when setting the backstack 2017-10-03 16:42:23 -05:00
Eric Kuck 9bbf050cd2 setBackstack now delays destroying old controllers until after the change handler that removes them has completed 2017-08-04 10:59:19 -05:00
Eric Kuck 5a545aca20 Fixes removeAllExceptVisibleAndUnowned when there is more than 1 View that needs to be removed. Fixes #336 2017-08-01 16:13:40 -05:00
inorichi 0987b311d9 Only show options menu of the active item in a ViewPager (#335) 2017-07-28 10:54:12 -05:00
Eric Kuck ff105abf12 Fixes waitOnSharedElementNamed 2017-07-27 13:42:55 -05:00
Gustavo Pagani 422ca7a544 Expose parent constructor with Bundle as parameter (#328)
* Expose constructor with Bundle as parameter
* Add Nullable annotation to Bundle parameter
2017-07-21 11:58:20 -05:00
Eric Kuck 09df937c88 Fixes evaluation of isPush for several cases of setBackstack. Fixes #327 2017-07-17 14:00:11 -05:00
Eric Kuck ec8ac2a371 setBackstack will now make context available to all new controllers as soon as possible 2017-07-17 11:56:41 -05:00
Eric Kuck 66b0458c0e Fixed potential ConcurrentModificationException when listeners are removed during change handler events 2017-07-14 13:10:05 -05:00
Eric Kuck bd0cd361f2 Readme update 2017-07-11 14:54:19 -05:00
Eric Kuck 60c237bf82 Travis yml update 2017-07-10 17:08:03 -05:00
Eric Kuck bae8a16d8c Added SharedElementTransitionChangeHandler, which handles transitions much more robustly 2017-07-10 16:02:51 -05:00
125 changed files with 2299 additions and 907 deletions
+4 -4
View File
@@ -3,8 +3,8 @@ language: android
android:
components:
- tools
- build-tools-25.0.3
- android-25
- build-tools-28.0.3
- android-28
- extra-android-m2repository
licenses:
- '.+'
@@ -31,5 +31,5 @@ sudo: false
env:
global:
- secure: SoRbxmNTxnGY9qPR5Z8HraRLhHrq7eJ2UaHMuiXDSxpLwUI0IMw+8+l59PNdy/C5ZXXrr6jo6cj+sn/4u6VNg74e2h9i//O+kYGvbGJUnBx8uo1INOrVenpzSnIgxbRLxyN3ZDp8YgwJMl8MSCLM1nj2OMzjNRY5EBnEw5h3qNXBs4Hyhpp2FxVk7dA2yLMUZdOpFKJIsqhH1ZHnCEnYrLlx5cVM9yoefFmJ3PptgumtV8ciBnp0lgDGy5nTykPh6zJBz4rAXgOr95WHvoqpyBRAUZIUEgw/vB5aF8/g+CX2gvTlJYF2N9LgJTNHMEwd+zJtmjM8JzkuCfTT3uMDD3JK5O8eNU03a/+9AkbKpK2+Pt829ZPdkObavXi+oJykCmD5IirukVXE9ushR2J+fM4VOvJinsANSI0zjzFpjZMplX63lfhNu/4lj3AWV2G4rkZd3vZQU+4AuhGQ469RA9BFqUJDIsiQQJwHEAWIqo9WNi6H4H8OhferACd2T3d5Y0O3s0EG5JfdADBPh9YDIkB2zEtGc3gGdxFzxVmH48BJViubAHlH4SgJn7gn69T9wyKmJ1M8F9ph/CdhSHT3kADRDELPEEVXCcANG/verCbyxMlAMXvLNKIGgHD+A0/z9QS1WduOOZwWd1mAuNuEg/rq2OB8SoDTv/BseHrXOpc=
- secure: WpqrbdAvNUFn5cM/Iu3zJOaDvT3jWGHCRwvxQCzX9F8iJeTggB5dB2rjgUDCx8LJ8UAt0VCeOcGtR1RT3EHyaHorN3NWeLcBFAHSz2sXv+2xGkspsXwjfygghZTCdYEzhhvmWlz9Ln4s4QJ2fBFZA07pG0jw4Cp1hSQiJ1WlKfDQezldj8D3pPwg1oOq4b5+HVucQ6+PPVwzGk2c3etwb5205L8H8flRjZrP95mFa5n/H3b/HFIsKX5p+CPNIKCrjBEmX0nHXiV0+g6lBQBV1iCwT56vfmN8Urm4KLId71iMpmvstDxlBBRQx3sz41vxIWGFn/oN7iXJI6XfzVFkyvrd9XAQLQFffq4KpN0REy1L3rjO46sYRXu1ycCP5VFVAAwKZn+o1q6xRjCuma2Qj4tqY754pwPNyzXnndFLO7hoN8KjOgV2nk75+XlRG8LhP356CHET62QBZgJ+sl+aFM3hhknsaEuDQywo8Uz4WZL0lPmYqm5BImQT9sTEF6uQNofg4gMy/uqgGhpLtseQW3PoJXB6dmD5JdNxlOalkGSQ+aI/q5QvR6ruIiuap66o4Bu+YTvHiS2hVzmldvMmLFsU1/zECSI6Fs/vkwRN55R9mbPROWi8SzvftYk9shkFMC5QC1FXA/CHqX1W5nl/HpMrs8R9uPhdZ1lifCiW8Rk=
- secure: Px0uj7aMtKUVZVBnoEjHrEHh4cwO2qKJrHHPvwBiDqhwLBaWQIVXYOy0njaYc5o8p96Fv7bMu7NZx/72vMu1+nmTKxgzrMIxvMW6kczBvJUpv6xd1NuC34x+5Xq5gBNOFvb8JarWStcKgIvnFqvBsRUeI1Hsz7Olb8HF+fEo1kShuP18ezSsBkXruw8JuGiU9x0kq4YhZ7vRvFnc3sJX2FL6heuvQsnUWrolUOsKRadNkCibo+Euuls7ExvbbAXN4LEO3rs0G2eBUBbi2wXvTMG9symtItEHTMPO7K+aQfNQnHsY91TYveH/IJM1u5p6OldsUSOUigzpDmpVYW94aLuJaYqc6Ibq3eUws+tv2didOHXZW5zOCFjldDFBIQFPA3fih/wK/JP0taQ0uIu1+2eifvuERarMkGsYlOFe5tJd10ipi+kK5vNxoRwS9kGv5WwP5fVPX2m5XbD2y1LnugCCcAumfNX7NyNBIRqTy7BP34O3EMLZpMxjwSLnUBnYd4V/0LEvoVmbYmrLhWwpojBJmdwe2QknrPuvRErxNujRA1uEVupbU5A6RW1BmrtzSahJYoROI+ayG7UTOSbFN8+DorER1SUXsrOFlawak8yWsoi6OIynTKucrFM3YcBdJ3Su5AIhfBAOASZa6CUa6sn6Zo8mHmDVGKeckvXnLCU=
- secure: 3Dj6roVTO2a9Z8lwlTGzJJ+QGeyIYSuQ/Z6YsYnW3wB9Mw36uWqw9rOmMNIGjjyAlER8bKgalHr90Pus87oaNaIlbEyvq+L+I0FVAwognViFjo/a2apCcq81THoKjT1l0sgGRzvLNDynQe1q2L0tOu21wtBhMLb7FKQYB9+oD3H+rcU63xD2tv3ToJz+j+dccZ9nrtgk0MQ1xAeMtEb4tdq3flKATKhIuDkp9chaDxx/ZGwyhdE0UP29UUyP5Np1QvpFAlAJIZloZPvde2e05fwxTh4rwUCetkfJknDK6WrGiq97WGRXJpfORNuwGn7jxDCtgxcAm9nGF8qmI/v78BhjJ857CfJBTLGv4QI0RszlhXyezJqqRYjCn9S4yx8UAOixVJfJfFNHLqD4MFn41b7j8J3HDJPxNt0t/qYhUMrgrZVosNOUqhwCyQTKDqtrpvmSUbhHpk2+fxZF1GEL5N540rA0OjLmFUUEDSvRQVaa/waeqXrRefOhsXIx20dHs93C6XDffjnxVMKlqtPab2MlHV/23QCuSa6eW+lKyOZ6ZkWLwwDsLbnuD0WJfJpiL1xaTdPJNIDb6kB/Bno4V1O4xrYncEOaA4Vr4amO8le9Q33/QeNrUZSXs/eye5t0f02wCzubuiEDVEeoR/qj5+5CNWv3AWr/f8AgXp80Pzw=
+70
View File
@@ -0,0 +1,70 @@
# Community Contribution Guidelines
As the creators and maintainers of this project, we want to ensure that the project lives and continues to grow. Not blocked by any singular person's computer time. One of the simplest ways of doing this is by encouraging a larger set of shallow contributors. Through this, we hope to mitigate the problems of a project that needs updates, but there is no-one who has the power to do so.
#### Development Process
We maintain two permanent, protected branches: `master` and `development`.
`master` is for the current release. It has already been distributed to jcenter and can't be modified at this point (except for docs that may be incorrect for the current release!).
`development` is where we stage work for the *next* release. Pull requests should be directed to this branch.
When working on a new feature or fix that may span multiple commits, please do so in a feature branch (ex: `feature/my_cool_thing`). Please clean up these feature branches once merged into `development`.
When a new version is ready to be released, please create a pull request to merge `development` into `master`, named something like "Release 10.0". Then we can have some final discussion before we merge it into `master` and push the release out to the public.
Since `development` is a *shared* branch, it is important not to ever rebase this branch onto `master`. If a bug fix is applied to `master` it can be merged into `development` using good old simple `git checkout development && git merge master`. Yes this will clutter the history a little bit, but it also provides important context to know how/when a patch was applied. Merge commits can be considered necessary historical data, not warts on an idealized history graph.
#### Testing
To run tests locally, just run `./gradlew test`. Tests should always be run before pushing anything to the repo.
#### Ownership
If you get a merged pull-request of substance (ie not just a typo fix), then you are eligible for push access to this repo. Simply request access via a new GitHub issue.
Offhand, it is easy to imagine that this would make code quality suffer, but in reality it offers fresh perspectives to the codebase and encourages ownership from people who are depending on the project. If you are building a project that relies on this codebase, then you probably have the skills to improve it and offer valuable feedback. At the end of the day, there isn't too much risk in this, as the `master` branch is still locked, and jcenter access is still being restricted.
Everyone comes in with their own perspective on what a project could/should look like, and encouraging discussion can help expose good ideas sooner.
#### Why do we give out push access?
It can be overwhelming to be offered the chance to wipe the source code for a project. Do not worry, we do not let you push to master. All code is peer-reviewed, and we have the convention that someone other than the submitter should merge non-trivial pull requests.
As a contributor, you can merge other people's pull requests, or other contributors can merge yours. You will not be assigned a pull request, but you are welcome to jump in and take a code review on topics that interest you.
This project is not continuously deployed, there is space for debate after review too. Offering everyone the chance to revert, or make an amending pull request. If it feels right, merge.
#### How can we help you get comfortable contributing?
It is normal for a first pull request to be a potential fix for a problem, and moving on from there to helping the project's direction can be difficult. We will try to help contributors cross that barrier by offering good first step issues. These issues can be fixed without feeling like you are stepping on toes. Ideally, these are non-critical issues that are well defined. They will be purposely avoided by mature contributors to the project, to make space for others.
We aim to keep all project discussion inside GitHub issues. This is to make sure valuable discussion is accessible via search. If you have questions about how to use the library, or how the project is running - GitHub issues are the goto tool for this project.
#### Our expectations on you as a contributor
To quote [@alloy](https://github.com/alloy) from [this issue](https://github.com/Moya/Moya/issues/135):
> Do not ever feel bad for not contributing to open source.
We want contributors to provide ideas, keep the ship shipping and to take some of the load from others. It is non-obligatory; were here to get things done in an enjoyable way. :trophy:
The fact that you will have push access will allow you to:
- Avoid having to fork the project if you want to submit other pull requests, as you will be able to create branches directly on the project.
- Help triage issues and merge pull requests.
- Pick up the project if other maintainers move their focus elsewhere.
It is up to you to use those superpowers or not though 😉
If someone submits a pull request that is not perfect, and you are reviewing, it is better to think about the PR's motivation rather than the specific implementation. Having braces on the wrong line should not be a blocker. Though we do want to keep test coverage high, we will work with you to figure that out together.
#### What about if you have problems that cannot be discussed in a public issue?
[Eric Kuck](https://github.com/erickuck) has a contactable email on his GitHub profile, and is happy to talk about any problems.
#### This is a different way to handle open source! Where did it come from?
The original source of this document can be found at [https://github.com/moya/contributors](https://github.com/moya/contributors).
+20 -13
View File
@@ -20,35 +20,34 @@ Conductor is architecture-agnostic and does not try to force any design decision
## Installation
```gradle
compile 'com.bluelinelabs:conductor:2.1.4'
implementation 'com.bluelinelabs:conductor:3.0.0-rc1'
// If you want the components that go along with
// Android's support libraries (currently just a PagerAdapter):
compile 'com.bluelinelabs:conductor-support:2.1.4'
implementation 'com.bluelinelabs:conductor-support:3.0.0-rc1'
// If you want RxJava lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle:2.1.4'
implementation 'com.bluelinelabs:conductor-rxlifecycle:3.0.0-rc1'
// If you want RxJava2 lifecycle support:
compile 'com.bluelinelabs:conductor-rxlifecycle2:2.1.4'
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc1'
// If you want RxJava2 Autodispose support:
compile 'com.bluelinelabs:conductor-autodispose:2.1.4'
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc1'
// If you want Controllers that are Lifecycle-aware (architecture components):
compile 'com.bluelinelabs:conductor-arch-components-lifecycle:0.1.1'
implementation 'com.bluelinelabs:conductor-archlifecycle:3.0.0-rc1'
```
SNAPSHOT:
**SNAPSHOT**
Just use `2.1.5-SNAPSHOT` as your version number in any of the above dependencies and add the url to the snapshot repository:
Just use `3.0.1-SNAPSHOT` as your version number in any of the dependencies above and add the url to the snapshot repository:
```gradle
allprojects {
repositories {
...
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
}
```
@@ -76,7 +75,7 @@ public class MainActivity extends Activity {
setContentView(R.layout.activity_main);
ViewGroup container = (ViewGroup)findViewById(R.id.controller_container);
ViewGroup container = (ViewGroup) findViewById(R.id.controller_container);
router = Conductor.attachRouter(this, container, savedInstanceState);
if (!router.hasRootController()) {
@@ -102,7 +101,7 @@ public class HomeController extends Controller {
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
View view = inflater.inflate(R.layout.controller_home, container, false);
((TextView)view.findViewById(R.id.tv_title)).setText("Hello World");
((TextView) view.findViewById(R.id.tv_title)).setText("Hello World");
return view;
}
@@ -131,7 +130,15 @@ The lifecycle of a Controller is significantly simpler to understand than that o
`getChildRouter` can be called on a `Controller` in order to get a nested `Router` into which child `Controller`s can be pushed. This enables creating advanced layouts, such as Master/Detail.
### RxJava Lifecycle
If the RxLifecycle dependency has been added, there is an `RxController` available that can be used along with the standard [RxLifecycle library](https://github.com/trello/RxLifecycle). There is also a `ControllerLifecycleProvider` available if you do not wish to use this subclass.
If the AutoDispose dependency has been added, there is a `ControllerScopeProvider` available that can be used along with the standard [AutoDispose library](https://github.com/uber/AutoDispose).
## Community Projects
The community has provided several helpful modules to make developing apps with Conductor even easier. Here's a collection of helpful libraries:
* [ConductorGlide](https://github.com/MkhytarMkhoian/ConductorGlide) - Adds Glide lifecycle support to Controllers
* [ConductorDialog](https://github.com/MkhytarMkhoian/ConductorDialog) - Adds a helpful DialogController (a Conductor version of DialogFragment)
* [Mosby-Conductor](https://github.com/sockeqwe/mosby-conductor) - A plugin to integrate Mosby, an MVP/MVI library
## License
```
+3 -2
View File
@@ -1,17 +1,18 @@
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
jcenter()
mavenCentral()
maven { url 'https://maven.google.com' }
jcenter()
}
}
+4 -4
View File
@@ -8,11 +8,11 @@ configurations {
}
dependencies {
compile rootProject.ext.lintapi
compile rootProject.ext.lintchecks
implementation rootProject.ext.lintapi
implementation rootProject.ext.lintchecks
testCompile rootProject.ext.lint
testCompile rootProject.ext.lintTests
testImplementation rootProject.ext.lint
testImplementation rootProject.ext.lintTests
lintChecks files(jar)
}
@@ -1,6 +1,7 @@
package com.bluelinelabs.conductor.lint;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.client.api.UElementHandler;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
@@ -8,13 +9,16 @@ import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UTypeReferenceExpression;
import java.util.Collections;
import java.util.List;
public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.JavaPsiScanner {
public final class ControllerChangeHandlerIssueDetector extends Detector implements Detector.UastScanner {
public static final Issue ISSUE =
Issue.create("ValidControllerChangeHandler", "ControllerChangeHandler not instantiatable",
@@ -23,47 +27,67 @@ public final class ControllerChangeHandlerIssueDetector extends Detector impleme
Category.CORRECTNESS, 6, Severity.FATAL,
new Implementation(ControllerChangeHandlerIssueDetector.class, Scope.JAVA_FILE_SCOPE));
public ControllerChangeHandlerIssueDetector() { }
private static final String CLASS_NAME = "com.bluelinelabs.conductor.ControllerChangeHandler";
@Override
public List<String> applicableSuperClasses() {
return Collections.singletonList("com.bluelinelabs.conductor.ControllerChangeHandler");
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.<Class<? extends UElement>>singletonList(UClass.class);
}
@Override
public void checkClass(JavaContext context, PsiClass declaration) {
public UElementHandler createUastHandler(final JavaContext context) {
final JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isAbstract(declaration)) {
return;
}
if (!evaluator.isPublic(declaration)) {
String message = String.format("This ControllerChangeHandler class should be public (%1$s)", declaration.getQualifiedName());
context.report(ISSUE, declaration, context.getLocation(declaration), message);
return;
}
return new UElementHandler() {
if (declaration.getContainingClass() != null && !evaluator.isStatic(declaration)) {
String message = String.format("This ControllerChangeHandler inner class should be static (%1$s)", declaration.getQualifiedName());
context.report(ISSUE, declaration, context.getLocation(declaration), message);
return;
}
@Override
public void visitClass(UClass node) {
if (evaluator.isAbstract(node)) {
return;
}
boolean hasDefaultConstructor = false;
PsiMethod[] constructors = declaration.getConstructors();
for (PsiMethod constructor : constructors) {
if (evaluator.isPublic(constructor)) {
if (constructor.getParameterList().getParametersCount() == 0) {
hasDefaultConstructor = true;
break;
boolean hasSuperType = false;
for (UTypeReferenceExpression superType : node.getUastSuperTypes()) {
if (CLASS_NAME.equals(superType.asRenderString())) {
hasSuperType = true;
break;
}
}
if (!hasSuperType) {
return;
}
if (!evaluator.isPublic(node)) {
String message = String.format("This ControllerChangeHandler class should be public (%1$s)", node.getQualifiedName());
context.report(ISSUE, node, context.getLocation((UElement) node), message);
return;
}
if (node.getContainingClass() != null && !evaluator.isStatic(node)) {
String message = String.format("This ControllerChangeHandler inner class should be static (%1$s)", node.getQualifiedName());
context.report(ISSUE, node, context.getLocation((UElement) node), message);
return;
}
boolean hasConstructor = false;
boolean hasDefaultConstructor = false;
for (UMethod method : node.getMethods()) {
if (method.isConstructor()) {
hasConstructor = true;
if (evaluator.isPublic(method) && method.getUastParameters().size() == 0) {
hasDefaultConstructor = true;
break;
}
}
}
if (hasConstructor && !hasDefaultConstructor) {
String message = String.format(
"This ControllerChangeHandler needs to have a public default constructor (`%1$s`)", node.getQualifiedName());
context.report(ISSUE, node, context.getLocation((UElement) node), message);
}
}
}
if (constructors.length > 0 && !hasDefaultConstructor) {
String message = String.format(
"This ControllerChangeHandler needs to have a public default constructor (`%1$s`)", declaration.getQualifiedName());
context.report(ISSUE, declaration, context.getLocation(declaration), message);
}
};
}
}
@@ -2,6 +2,7 @@ package com.bluelinelabs.conductor.lint;
import com.android.SdkConstants;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.client.api.UElementHandler;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
@@ -9,14 +10,17 @@ import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UParameter;
import org.jetbrains.uast.UTypeReferenceExpression;
import java.util.Collections;
import java.util.List;
public final class ControllerIssueDetector extends Detector implements Detector.JavaPsiScanner {
public final class ControllerIssueDetector extends Detector implements Detector.UastScanner {
public static final Issue ISSUE =
Issue.create("ValidController", "Controller not instantiatable",
@@ -25,58 +29,76 @@ public final class ControllerIssueDetector extends Detector implements Detector.
+ " case of the process being killed.", Category.CORRECTNESS, 6, Severity.FATAL,
new Implementation(ControllerIssueDetector.class, Scope.JAVA_FILE_SCOPE));
public ControllerIssueDetector() { }
private static final String CLASS_NAME = "com.bluelinelabs.conductor.Controller";
@Override
public List<String> applicableSuperClasses() {
return Collections.singletonList("com.bluelinelabs.conductor.Controller");
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.<Class<? extends UElement>>singletonList(UClass.class);
}
@Override
public void checkClass(JavaContext context, PsiClass declaration) {
public UElementHandler createUastHandler(final JavaContext context) {
final JavaEvaluator evaluator = context.getEvaluator();
if (evaluator.isAbstract(declaration)) {
return;
}
if (!evaluator.isPublic(declaration)) {
String message = String.format("This Controller class should be public (%1$s)", declaration.getQualifiedName());
context.report(ISSUE, declaration, context.getLocation(declaration), message);
return;
}
return new UElementHandler() {
@Override
public void visitClass(UClass node) {
if (evaluator.isAbstract(node)) {
return;
}
if (declaration.getContainingClass() != null && !evaluator.isStatic(declaration)) {
String message = String.format("This Controller inner class should be static (%1$s)", declaration.getQualifiedName());
context.report(ISSUE, declaration, context.getLocation(declaration), message);
return;
}
boolean hasSuperType = false;
for (UTypeReferenceExpression superType : node.getUastSuperTypes()) {
if (CLASS_NAME.equals(superType.asRenderString())) {
hasSuperType = true;
break;
}
}
if (!hasSuperType) {
return;
}
if (!evaluator.isPublic(node)) {
String message = String.format("This Controller class should be public (%1$s)", node.getQualifiedName());
context.report(ISSUE, node, context.getLocation((UElement) node), message);
return;
}
boolean hasDefaultConstructor = false;
boolean hasBundleConstructor = false;
PsiMethod[] constructors = declaration.getConstructors();
for (PsiMethod constructor : constructors) {
if (evaluator.isPublic(constructor)) {
PsiParameter[] parameters = constructor.getParameterList().getParameters();
if (node.getContainingClass() != null && !evaluator.isStatic(node)) {
String message = String.format("This Controller inner class should be static (%1$s)", node.getQualifiedName());
context.report(ISSUE, node, context.getLocation((UElement) node), message);
return;
}
if (parameters.length == 0) {
hasDefaultConstructor = true;
break;
} else if (parameters.length == 1 &&
parameters[0].getType().equalsToText(SdkConstants.CLASS_BUNDLE) ||
parameters[0].getType().equalsToText("Bundle")) {
hasBundleConstructor = true;
break;
boolean hasConstructor = false;
boolean hasDefaultConstructor = false;
boolean hasBundleConstructor = false;
for (UMethod method : node.getMethods()) {
if (method.isConstructor()) {
hasConstructor = true;
if (evaluator.isPublic(method)) {
List<UParameter> parameters = method.getUastParameters();
if (parameters.size() == 0) {
hasDefaultConstructor = true;
break;
} else if (parameters.size() == 1 &&
(parameters.get(0).getType().equalsToText(SdkConstants.CLASS_BUNDLE)) ||
parameters.get(0).getType().equalsToText("Bundle")) {
hasBundleConstructor = true;
}
}
}
}
if (hasConstructor && !hasDefaultConstructor && !hasBundleConstructor) {
String message = String.format(
"This Controller needs to have either a public default constructor or a" +
" public single-argument constructor that takes a Bundle. (`%1$s`)",
node.getQualifiedName());
context.report(ISSUE, node, context.getLocation((UElement) node), message);
}
}
}
if (constructors.length > 0 && !hasDefaultConstructor && !hasBundleConstructor) {
String message = String.format(
"This Controller needs to have either a public default constructor or a" +
" public single-argument constructor that takes a Bundle. (`%1$s`)",
declaration.getQualifiedName());
context.report(ISSUE, declaration, context.getLocation(declaration), message);
}
};
}
}
@@ -5,10 +5,19 @@ import com.android.tools.lint.detector.api.Issue;
import java.util.Arrays;
import java.util.List;
import static com.android.tools.lint.detector.api.ApiKt.CURRENT_API;
public final class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
@Override public List<Issue> getIssues() {
@Override
public List<Issue> getIssues() {
return Arrays.asList(
ControllerIssueDetector.ISSUE,
ControllerChangeHandlerIssueDetector.ISSUE);
}
@Override
public int getApi() {
return CURRENT_API;
}
}
@@ -1,19 +1,13 @@
package com.bluelinelabs.conductor.lint;
import com.android.tools.lint.checks.infrastructure.LintDetectorTest;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
import org.intellij.lang.annotations.Language;
import org.junit.Test;
import java.util.Collections;
import java.util.List;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static com.google.common.truth.Truth.assertThat;
public class ControllerChangeHandlerDetectorTest {
public class ControllerChangeHandlerDetectorTest extends LintDetectorTest {
private static final String NO_WARNINGS = "No warnings.";
private static final String CONSTRUCTOR =
"src/test/SampleHandler.java:2: Error: This ControllerChangeHandler needs to have a public default constructor (test.SampleHandler) [ValidControllerChangeHandler]\n"
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
@@ -25,72 +19,94 @@ public class ControllerChangeHandlerDetectorTest extends LintDetectorTest {
+ "^\n"
+ "1 errors, 0 warnings\n";
public void testWithNoConstructor() throws Exception {
@Test
public void testWithNoConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expectClean();
}
public void testWithEmptyConstructor() throws Exception {
@Test
public void testWithEmptyConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
+ " public SampleHandler() { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expectClean();
}
public void testWithInvalidConstructor() throws Exception {
@Test
public void testWithInvalidConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
+ " public SampleHandler(int number) { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expect(CONSTRUCTOR);
}
public void testWithEmptyAndInvalidConstructor() throws Exception {
@Test
public void testWithEmptyAndInvalidConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
+ " public SampleHandler() { }\n"
+ " public SampleHandler(int number) { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expectClean();
}
public void testWithPrivateConstructor() throws Exception {
@Test
public void testWithPrivateConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
+ " private SampleHandler() { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expect(CONSTRUCTOR);
}
public void testWithPrivateClass() throws Exception {
@Test
public void testWithPrivateClass() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "private class SampleHandler extends com.bluelinelabs.conductor.ControllerChangeHandler {\n"
+ " public SampleHandler() { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(PRIVATE_CLASS_ERROR);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expect(PRIVATE_CLASS_ERROR);
}
@Override
protected Detector getDetector() {
return new ControllerChangeHandlerIssueDetector();
}
@Override
protected List<Issue> getIssues() {
return Collections.singletonList(ControllerChangeHandlerIssueDetector.ISSUE);
}
@Override
protected boolean allowCompilationErrors() {
return true;
}
}
@@ -1,19 +1,13 @@
package com.bluelinelabs.conductor.lint;
import com.android.tools.lint.checks.infrastructure.LintDetectorTest;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
import org.intellij.lang.annotations.Language;
import org.junit.Test;
import java.util.Collections;
import java.util.List;
import static com.android.tools.lint.checks.infrastructure.TestFiles.java;
import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint;
import static com.google.common.truth.Truth.assertThat;
public class ControllerDetectorTest {
public class ControllerDetectorTest extends LintDetectorTest {
private static final String NO_WARNINGS = "No warnings.";
private static final String CONSTRUCTOR_ERROR =
"src/test/SampleController.java:2: Error: This Controller needs to have either a public default constructor or a public single-argument constructor that takes a Bundle. (test.SampleController) [ValidController]\n"
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
@@ -25,72 +19,94 @@ public class ControllerDetectorTest extends LintDetectorTest {
+ "^\n"
+ "1 errors, 0 warnings\n";
public void testWithNoConstructor() throws Exception {
@Test
public void testWithNoConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expectClean();
}
public void testWithEmptyConstructor() throws Exception {
@Test
public void testWithEmptyConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
+ " public SampleController() { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expectClean();
}
public void testWithInvalidConstructor() throws Exception {
@Test
public void testWithInvalidConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
+ " public SampleController(int number) { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR_ERROR);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expect(CONSTRUCTOR_ERROR);
}
public void testWithEmptyAndInvalidConstructor() throws Exception {
@Test
public void testWithEmptyAndInvalidConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
+ " public SampleController() { }\n"
+ " public SampleController(int number) { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(NO_WARNINGS);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expectClean();
}
public void testWithPrivateConstructor() throws Exception {
@Test
public void testWithPrivateConstructor() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "public class SampleController extends com.bluelinelabs.conductor.Controller {\n"
+ " private SampleController() { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(CONSTRUCTOR_ERROR);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expect(CONSTRUCTOR_ERROR);
}
public void testWithPrivateClass() throws Exception {
@Test
public void testWithPrivateClass() {
@Language("JAVA") String source = ""
+ "package test;\n"
+ "private class SampleController extends com.bluelinelabs.conductor.Controller {\n"
+ " public SampleController() { }\n"
+ "}";
assertThat(lintProject(java(source))).isEqualTo(CLASS_ERROR);
lint()
.files(java(source))
.issues(ControllerIssueDetector.ISSUE, ControllerChangeHandlerIssueDetector.ISSUE)
.run()
.expect(CLASS_ERROR);
}
@Override
protected Detector getDetector() {
return new ControllerIssueDetector();
}
@Override
protected List<Issue> getIssues() {
return Collections.singletonList(ControllerIssueDetector.ISSUE);
}
@Override
protected boolean allowCompilationErrors() {
return true;
}
}
@@ -5,7 +5,6 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
@@ -21,9 +20,9 @@ android {
}
dependencies {
compile rootProject.ext.archComopnentsLifecycle
api rootProject.ext.archComponentsLifecycle
compile project(':conductor')
implementation project(':conductor')
}
ext.artifactId = 'conductor-arch-components-lifecycle'
@@ -1,4 +1,3 @@
POM_NAME=Conductor Architecture Components Lifecycle Extensions
POM_ARTIFACT_ID=conductor-archlifecycle
POM_PACKAGING=aar
VERSION_NAME=0.1.2-SNAPSHOT
@@ -0,0 +1,65 @@
package com.bluelinelabs.conductor.archlifecycle;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.Lifecycle.Event;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import android.content.Context;
import androidx.annotation.NonNull;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
public class ControllerLifecycleOwner implements LifecycleOwner {
private final LifecycleRegistry lifecycleRegistry;
public <T extends Controller & LifecycleOwner> ControllerLifecycleOwner(@NonNull T lifecycleController) {
lifecycleRegistry = new LifecycleRegistry(lifecycleController); // --> State.INITIALIZED
lifecycleController.addLifecycleListener(new LifecycleListener() {
@Override
public void postContextAvailable(@NonNull Controller controller, @NonNull Context context) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_CREATE); // --> State.CREATED;
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_START); // --> State.STARTED;
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME); // --> State.RESUMED;
}
@Override
public void preDetach(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_PAUSE); // --> State.STARTED;
}
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_STOP); // --> State.CREATED;
}
@Override
public void preContextUnavailable(@NonNull Controller controller, @NonNull Context context) {
// do nothing
}
@Override
public void preDestroy(@NonNull Controller controller) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_DESTROY); // --> State.DESTROYED;
}
});
}
@Override @NonNull
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}
@@ -1,79 +0,0 @@
package com.bluelinelabs.conductor.archlifecycle;
import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.Lifecycle.State;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import android.support.annotation.NonNull;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
public class ControllerLifecycleRegistryOwner extends LifecycleListener implements LifecycleRegistryOwner {
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
public ControllerLifecycleRegistryOwner(Controller controller) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_CREATE);
lifecycleRegistry.markState(State.CREATED);
controller.addLifecycleListener(new LifecycleListener() {
@Override
public void preCreateView(@NonNull Controller controller) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_START);
}
@Override
public void postCreateView(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.markState(State.STARTED);
}
@Override
public void preAttach(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME);
}
@Override
public void postAttach(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.markState(State.RESUMED);
}
@Override
public void preDetach(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_PAUSE);
}
@Override
public void postDetach(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.markState(State.STARTED);
}
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_STOP);
}
@Override
public void postDestroyView(@NonNull Controller controller) {
lifecycleRegistry.markState(State.CREATED);
}
@Override
public void preDestroy(@NonNull Controller controller) {
lifecycleRegistry.handleLifecycleEvent(Event.ON_DESTROY);
}
@Override
public void postDestroy(@NonNull Controller controller) {
lifecycleRegistry.markState(State.DESTROYED);
}
});
}
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
}
@@ -1,17 +1,28 @@
package com.bluelinelabs.conductor.archlifecycle;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.Controller;
public abstract class LifecycleController extends Controller implements LifecycleRegistryOwner {
public abstract class LifecycleController extends Controller implements LifecycleOwner {
private final ControllerLifecycleRegistryOwner lifecycleRegistryOwner = new ControllerLifecycleRegistryOwner(this);
private final ControllerLifecycleOwner lifecycleOwner = new ControllerLifecycleOwner(this);
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistryOwner.getLifecycle();
public LifecycleController() {
super();
}
public LifecycleController(@Nullable Bundle args) {
super(args);
}
@Override @NonNull
public Lifecycle getLifecycle() {
return lifecycleOwner.getLifecycle();
}
}
@@ -1,17 +1,28 @@
package com.bluelinelabs.conductor.archlifecycle;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
public abstract class LifecycleRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleRegistryOwner {
public abstract class LifecycleRestoreViewOnCreateController extends RestoreViewOnCreateController implements LifecycleOwner {
private final ControllerLifecycleRegistryOwner lifecycleRegistryOwner = new ControllerLifecycleRegistryOwner(this);
private final ControllerLifecycleOwner mLifecycleOwner = new ControllerLifecycleOwner(this);
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistryOwner.getLifecycle();
public LifecycleRestoreViewOnCreateController() {
super();
}
public LifecycleRestoreViewOnCreateController(@Nullable Bundle args) {
super(args);
}
@Override @NonNull
public Lifecycle getLifecycle() {
return mLifecycleOwner.getLifecycle();
}
}
+4 -4
View File
@@ -5,7 +5,6 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
@@ -21,10 +20,11 @@ android {
}
dependencies {
compile rootProject.ext.rxJava2
compile rootProject.ext.autodispose
api rootProject.ext.rxJava2
api rootProject.ext.autodispose
api rootProject.ext.autodisposeLifecycle
compile project(':conductor')
implementation project(':conductor')
}
ext.artifactId = 'conductor-autodispose'
@@ -1,11 +1,11 @@
package com.bluelinelabs.conductor.autodispose;
import android.content.Context;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
import com.uber.autodispose.OutsideLifecycleException;
import com.uber.autodispose.OutsideScopeException;
import io.reactivex.subjects.BehaviorSubject;
@@ -13,10 +13,10 @@ public class ControllerLifecycleSubjectHelper {
private ControllerLifecycleSubjectHelper() { }
@NonNull
public static BehaviorSubject<ControllerEvent> create(Controller controller) {
public static BehaviorSubject<ControllerEvent> create(@NonNull Controller controller) {
ControllerEvent initialState;
if (controller.isBeingDestroyed() || controller.isDestroyed()) {
throw new OutsideLifecycleException("Cannot bind to Controller lifecycle when outside of it.");
throw new OutsideScopeException("Cannot bind to Controller lifecycle when outside of it.");
} else if (controller.isAttached()) {
initialState = ControllerEvent.ATTACH;
} else if (controller.getView() != null) {
@@ -1,20 +1,22 @@
package com.bluelinelabs.conductor.autodispose;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.Controller;
import com.uber.autodispose.LifecycleScopeProvider;
import com.uber.autodispose.OutsideLifecycleException;
import com.uber.autodispose.OutsideScopeException;
import com.uber.autodispose.lifecycle.LifecycleScopeProvider;
import com.uber.autodispose.lifecycle.LifecycleScopes;
import com.uber.autodispose.lifecycle.CorrespondingEventsFunction;
import io.reactivex.CompletableSource;
import io.reactivex.Observable;
import io.reactivex.functions.Function;
import io.reactivex.subjects.BehaviorSubject;
public class ControllerScopeProvider implements LifecycleScopeProvider<ControllerEvent> {
private static final Function<ControllerEvent, ControllerEvent> CORRESPONDING_EVENTS =
new Function<ControllerEvent, ControllerEvent>() {
private static final CorrespondingEventsFunction<ControllerEvent> CORRESPONDING_EVENTS =
new CorrespondingEventsFunction<ControllerEvent>() {
@Override
public ControllerEvent apply(ControllerEvent lastEvent) throws Exception {
public ControllerEvent apply(ControllerEvent lastEvent) throws OutsideScopeException {
switch (lastEvent) {
case CREATE:
return ControllerEvent.DESTROY;
@@ -27,19 +29,34 @@ public class ControllerScopeProvider implements LifecycleScopeProvider<Controlle
case DETACH:
return ControllerEvent.DESTROY;
default:
throw new OutsideLifecycleException("Cannot bind to Controller lifecycle when outside of it.");
throw new OutsideScopeException("Cannot bind to Controller lifecycle when outside of it.");
}
}
};
@NonNull private final BehaviorSubject<ControllerEvent> lifecycleSubject;
@NonNull private final CorrespondingEventsFunction<ControllerEvent> correspondingEventsFunction;
public static ControllerScopeProvider from(@NonNull Controller controller) {
return new ControllerScopeProvider(controller);
return new ControllerScopeProvider(controller, CORRESPONDING_EVENTS);
}
private ControllerScopeProvider(@NonNull Controller controller) {
public static ControllerScopeProvider from(@NonNull Controller controller, @NonNull final ControllerEvent untilEvent) {
return new ControllerScopeProvider(controller, new CorrespondingEventsFunction<ControllerEvent>() {
@Override
public ControllerEvent apply(ControllerEvent controllerEvent) {
return untilEvent;
}
});
}
public static ControllerScopeProvider from(@NonNull Controller controller, @NonNull final CorrespondingEventsFunction<ControllerEvent> correspondingEventsFunction) {
return new ControllerScopeProvider(controller, correspondingEventsFunction);
}
private ControllerScopeProvider(@NonNull Controller controller, @NonNull CorrespondingEventsFunction<ControllerEvent> correspondingEventsFunction) {
lifecycleSubject = ControllerLifecycleSubjectHelper.create(controller);
this.correspondingEventsFunction = correspondingEventsFunction;
}
@Override
@@ -48,12 +65,17 @@ public class ControllerScopeProvider implements LifecycleScopeProvider<Controlle
}
@Override
public Function<ControllerEvent, ControllerEvent> correspondingEvents() {
return CORRESPONDING_EVENTS;
public CorrespondingEventsFunction<ControllerEvent> correspondingEvents() {
return correspondingEventsFunction;
}
@Override
public ControllerEvent peekLifecycle() {
return lifecycleSubject.getValue();
}
@Override
public CompletableSource requestScope() throws Exception {
return LifecycleScopes.resolveScopeFromLifecycle(this);
}
}
+4 -5
View File
@@ -5,7 +5,6 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
@@ -21,11 +20,11 @@ android {
}
dependencies {
compile rootProject.ext.rxJava
compile rootProject.ext.rxLifecycle
compile rootProject.ext.rxLifecycleAndroid
api rootProject.ext.rxJava
api rootProject.ext.rxLifecycle
api rootProject.ext.rxLifecycleAndroid
compile project(':conductor')
implementation project(':conductor')
}
ext.artifactId = 'conductor-rxlifecycle'
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.rxlifecycle;
import android.content.Context;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.rxlifecycle;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.Controller;
import com.trello.rxlifecycle.LifecycleProvider;
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.rxlifecycle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.trello.rxlifecycle.LifecycleTransformer;
import com.trello.rxlifecycle.OutsideLifecycleException;
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.rxlifecycle;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
import com.trello.rxlifecycle.LifecycleProvider;
+4 -5
View File
@@ -5,7 +5,6 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
@@ -21,11 +20,11 @@ android {
}
dependencies {
compile rootProject.ext.rxJava2
compile rootProject.ext.rxLifecycle2
compile rootProject.ext.rxLifecycleAndroid2
api rootProject.ext.rxJava2
api rootProject.ext.rxLifecycle2
api rootProject.ext.rxLifecycleAndroid2
compile project(':conductor')
implementation project(':conductor')
}
ext.artifactId = 'conductor-rxlifecycle2'
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.content.Context;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
@@ -1,9 +1,9 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.Controller;
import com.trello.rxlifecycle2.LifecycleProvider;
import com.trello.rxlifecycle2.LifecycleTransformer;
@@ -1,6 +1,6 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import com.trello.rxlifecycle2.LifecycleTransformer;
import com.trello.rxlifecycle2.OutsideLifecycleException;
import com.trello.rxlifecycle2.RxLifecycle;
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.rxlifecycle2;
import android.os.Bundle;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.RestoreViewOnCreateController;
import com.trello.rxlifecycle2.LifecycleProvider;
import com.trello.rxlifecycle2.LifecycleTransformer;
+5 -13
View File
@@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'de.mobilej.unmock:UnMockPlugin:0.6.0'
classpath 'de.mobilej.unmock:UnMockPlugin:0.6.5'
}
}
@@ -12,7 +12,6 @@ apply plugin: 'de.mobilej.unmock'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
@@ -28,18 +27,11 @@ android {
}
dependencies {
testCompile rootProject.ext.junit
testCompile rootProject.ext.roboelectric
testImplementation rootProject.ext.junit
testImplementation rootProject.ext.roboelectric
compile rootProject.ext.supportAppCompat
compile project(':conductor')
unmock 'org.robolectric:android-all:4.3_r2-robolectric-0'
}
unMock {
keep "android.os.Bundle"
keep "android.os.BaseBundle"
implementation rootProject.ext.androidxAppCompat
implementation project(':conductor')
}
ext.artifactId = 'conductor-support'
@@ -2,9 +2,9 @@ package com.bluelinelabs.conductor.support;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
@@ -2,9 +2,9 @@ package com.bluelinelabs.conductor.support;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
@@ -30,6 +30,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
private SparseArray<Bundle> savedPages = new SparseArray<>();
private SparseArray<Router> visibleRouters = new SparseArray<>();
private ArrayList<Integer> savedPageHistory = new ArrayList<>();
private Router currentPrimaryRouter;
/**
* Creates a new RouterPagerAdapter using the passed host.
@@ -71,12 +72,19 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
if (routerSavedState != null) {
router.restoreInstanceState(routerSavedState);
savedPages.remove(position);
savedPageHistory.remove((Integer) position);
}
}
router.rebindIfNeeded();
configureRouter(router, position);
if (router != currentPrimaryRouter) {
for (RouterTransaction transaction : router.getBackstack()) {
transaction.controller().setOptionsMenuHidden(true);
}
}
visibleRouters.put(position, router);
return router;
}
@@ -89,7 +97,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
router.saveInstanceState(savedState);
savedPages.put(position, savedState);
savedPageHistory.remove((Integer)position);
savedPageHistory.remove((Integer) position);
savedPageHistory.add(position);
ensurePagesSaved();
@@ -99,6 +107,24 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
visibleRouters.remove(position);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Router router = (Router)object;
if (router != currentPrimaryRouter) {
if (currentPrimaryRouter != null) {
for (RouterTransaction transaction : currentPrimaryRouter.getBackstack()) {
transaction.controller().setOptionsMenuHidden(true);
}
}
if (router != null) {
for (RouterTransaction transaction : router.getBackstack()) {
transaction.controller().setOptionsMenuHidden(false);
}
}
currentPrimaryRouter = router;
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
Router router = (Router)object;
@@ -2,7 +2,7 @@ package com.bluelinelabs.conductor.support;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.util.SparseArray;
import android.widget.FrameLayout;
@@ -17,8 +17,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
import static org.junit.Assert.assertEquals;
@@ -1,6 +1,6 @@
package com.bluelinelabs.conductor.support.util;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -10,7 +10,7 @@ import com.bluelinelabs.conductor.Controller;
public class TestController extends Controller {
@NonNull @Override
@Override @NonNull
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return new FrameLayout(inflater.getContext());
}
+4 -13
View File
@@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'de.mobilej.unmock:UnMockPlugin:0.6.0'
classpath 'de.mobilej.unmock:UnMockPlugin:0.6.5'
}
}
@@ -12,7 +12,6 @@ apply plugin: 'de.mobilej.unmock'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
@@ -33,20 +32,12 @@ configurations {
}
dependencies {
testCompile rootProject.ext.junit
testCompile rootProject.ext.roboelectric
testImplementation rootProject.ext.junit
testImplementation rootProject.ext.roboelectric
compile rootProject.ext.supportAnnotations
api rootProject.ext.androidxAnnotations
lintChecks project(path: ':conductor-lint', configuration: 'lintChecks')
unmock 'org.robolectric:android-all:4.3_r2-robolectric-0'
}
unMock {
keep "android.os.Bundle"
keep "android.os.BaseBundle"
keep "android.text.TextUtils"
}
task copyLintJar(type: Copy) {
@@ -5,8 +5,8 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
@@ -125,7 +125,7 @@ public class ActivityHostedRouter extends Router {
return this;
}
@Override @Nullable
@Override @NonNull
TransactionIndexer getTransactionIndexer() {
return transactionIndexer;
}
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -67,10 +67,6 @@ class Backstack implements Iterable<RouterTransaction> {
return backstack.peek();
}
void remove(@NonNull RouterTransaction transaction) {
backstack.removeFirstOccurrence(transaction);
}
void push(@NonNull RouterTransaction transaction) {
backstack.push(transaction);
}
@@ -85,28 +81,19 @@ class Backstack implements Iterable<RouterTransaction> {
}
void setBackstack(@NonNull List<RouterTransaction> backstack) {
for (RouterTransaction existingTransaction : this.backstack) {
boolean contains = false;
for (RouterTransaction newTransaction : backstack) {
if (existingTransaction.controller == newTransaction.controller) {
contains = true;
break;
}
}
if (!contains) {
existingTransaction.controller.destroy();
}
}
this.backstack.clear();
for (RouterTransaction transaction : backstack) {
this.backstack.push(transaction);
}
}
boolean contains(@NonNull RouterTransaction transaction) {
return backstack.contains(transaction);
boolean contains(@NonNull Controller controller) {
for (RouterTransaction transaction : backstack) {
if (controller == transaction.controller) {
return true;
}
}
return false;
}
void saveInstanceState(@NonNull Bundle outState) {
@@ -3,8 +3,8 @@ package com.bluelinelabs.conductor;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
@@ -2,9 +2,9 @@ package com.bluelinelabs.conductor;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.internal.LifecycleHandler;
@@ -9,9 +9,6 @@ import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -35,6 +32,10 @@ import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* A Controller manages portions of the UI. It is similar to an Activity or Fragment in that it manages its
* own lifecycle and controls interactions between the UI and whatever logic is required. It is, however,
@@ -62,22 +63,23 @@ public abstract class Controller {
Bundle viewState;
private Bundle savedInstanceState;
private boolean isBeingDestroyed;
boolean isBeingDestroyed;
private boolean destroyed;
private boolean attached;
private boolean hasOptionsMenu;
private boolean optionsMenuHidden;
private boolean viewIsAttached;
private boolean viewWasDetached;
private Router router;
private View view;
boolean viewIsAttached;
boolean viewWasDetached;
Router router;
View view;
private Controller parentController;
private String instanceId;
String instanceId;
private String targetInstanceId;
private boolean needsAttach;
private boolean attachedToUnownedParent;
private boolean awaitingParentAttach;
private boolean hasSavedViewState;
private boolean isDetachFrozen;
boolean isDetachFrozen;
private ControllerChangeHandler overriddenPushHandler;
private ControllerChangeHandler overriddenPopHandler;
private RetainViewMode retainViewMode = RetainViewMode.RELEASE_DETACH;
@@ -352,10 +354,8 @@ public abstract class Controller {
*/
@NonNull
public final List<Router> getChildRouters() {
List<Router> routers = new ArrayList<>();
for (Router router : childRouters) {
routers.add(router);
}
List<Router> routers = new ArrayList<>(childRouters.size());
routers.addAll(childRouters);
return routers;
}
@@ -846,9 +846,16 @@ public abstract class Controller {
}
final void activityStopped(@NonNull Activity activity) {
final boolean attached = this.attached;
if (viewAttachHandler != null) {
viewAttachHandler.onActivityStopped();
}
if (attached && activity.isChangingConfigurations()) {
needsAttach = true;
}
onActivityStopped(activity);
}
@@ -875,12 +882,19 @@ public abstract class Controller {
}
}
private void attach(@NonNull View view) {
void attach(@NonNull View view) {
attachedToUnownedParent = router == null || view.getParent() != router.container;
if (attachedToUnownedParent) {
if (attachedToUnownedParent || isBeingDestroyed) {
return;
}
if (parentController != null && !parentController.attached) {
awaitingParentAttach = true;
return;
} else {
awaitingParentAttach = false;
}
hasSavedViewState = false;
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
@@ -889,7 +903,7 @@ public abstract class Controller {
}
attached = true;
needsAttach = false;
needsAttach = router.isActivityStopped;
onAttach(view);
@@ -901,6 +915,14 @@ public abstract class Controller {
for (LifecycleListener lifecycleListener : listeners) {
lifecycleListener.postAttach(Controller.this, view);
}
for (ControllerHostedRouter childRouter : childRouters) {
for (RouterTransaction childTransaction : childRouter.backstack) {
if (childTransaction.controller.awaitingParentAttach) {
childTransaction.controller.attach(childTransaction.controller.view);
}
}
}
}
void detach(@NonNull View view, boolean forceViewRefRemoval, boolean blockViewRefRemoval) {
@@ -919,7 +941,10 @@ public abstract class Controller {
}
attached = false;
onDetach(view);
if (!awaitingParentAttach) {
onDetach(view);
}
if (hasOptionsMenu && !optionsMenuHidden) {
router.invalidateOptionsMenu();
@@ -1157,7 +1182,7 @@ public abstract class Controller {
outState.putBundle(KEY_OVERRIDDEN_POP_HANDLER, overriddenPopHandler.toBundle());
}
ArrayList<Bundle> childBundles = new ArrayList<>();
ArrayList<Bundle> childBundles = new ArrayList<>(childRouters.size());
for (ControllerHostedRouter childRouter : childRouters) {
Bundle routerBundle = new Bundle();
childRouter.saveInstanceState(routerBundle);
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -24,9 +24,9 @@ public abstract class ControllerChangeHandler {
private static final String KEY_CLASS_NAME = "ControllerChangeHandler.className";
private static final String KEY_SAVED_STATE = "ControllerChangeHandler.savedState";
private static final Map<String, ChangeHandlerData> inProgressChangeHandlers = new HashMap<>();
static final Map<String, ChangeHandlerData> inProgressChangeHandlers = new HashMap<>();
private boolean forceRemoveViewOnPush;
boolean forceRemoveViewOnPush;
private boolean hasBeenUsed;
/**
@@ -5,9 +5,9 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler.ControllerChangeListener;
@@ -15,6 +15,7 @@ import com.bluelinelabs.conductor.internal.TransactionIndexer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
class ControllerHostedRouter extends Router {
@@ -204,8 +205,8 @@ class ControllerHostedRouter extends Router {
@Override
void setControllerRouter(@NonNull Controller controller) {
super.setControllerRouter(controller);
controller.setParentController(hostController);
super.setControllerRouter(controller);
}
int getHostId() {
@@ -234,8 +235,21 @@ class ControllerHostedRouter extends Router {
}
}
@Override @Nullable
@Override @NonNull
TransactionIndexer getTransactionIndexer() {
return getRootRouter().getTransactionIndexer();
Router rootRouter = getRootRouter();
if (rootRouter == this) {
String debugInfo;
if (hostController != null) {
debugInfo = String.format(Locale.ENGLISH, "%s (attached? %b, destroyed? %b, parent: %s)",
hostController.getClass().getSimpleName(), hostController.isAttached(), hostController.isBeingDestroyed, hostController.getParentController());
} else {
debugInfo = "null host controller";
}
throw new IllegalStateException("Unable to retrieve TransactionIndexer from " + debugInfo);
} else {
return getRootRouter().getTransactionIndexer();
}
}
}
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -4,9 +4,9 @@ import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -43,6 +43,7 @@ public abstract class Router {
private boolean popsLastView = false;
boolean containerFullyAttached = false;
boolean isActivityStopped = false;
ViewGroup container;
@@ -137,15 +138,20 @@ public abstract class Router {
} else {
RouterTransaction removedTransaction = null;
RouterTransaction nextTransaction = null;
for (RouterTransaction transaction : backstack) {
Iterator<RouterTransaction> iterator = backstack.iterator();
ControllerChangeHandler topPushHandler = topTransaction != null ? topTransaction.pushChangeHandler() : null;
final boolean needsNextTransactionAttach = topPushHandler != null ? !topPushHandler.removesFromViewOnPush() : false;
while (iterator.hasNext()) {
RouterTransaction transaction = iterator.next();
if (transaction.controller == controller) {
if (controller.isAttached()) {
trackDestroyingController(transaction);
}
backstack.remove(transaction);
iterator.remove();
removedTransaction = transaction;
} else if (removedTransaction != null) {
if (!transaction.controller.isAttached()) {
if (needsNextTransactionAttach && !transaction.controller.isAttached()) {
nextTransaction = transaction;
}
break;
@@ -379,7 +385,7 @@ public abstract class Router {
*/
@NonNull
public List<RouterTransaction> getBackstack() {
List<RouterTransaction> list = new ArrayList<>();
List<RouterTransaction> list = new ArrayList<>(backstack.size());
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
list.add(backstackIterator.next());
@@ -399,22 +405,45 @@ public abstract class Router {
public void setBackstack(@NonNull List<RouterTransaction> newBackstack, @Nullable ControllerChangeHandler changeHandler) {
ThreadUtils.ensureMainThread();
List<RouterTransaction> oldTransactions = getBackstack();
List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
boolean newRootRequiresPush = !(newBackstack.size() > 0 && backstack.contains(newBackstack.get(0)));
removeAllExceptVisibleAndUnowned();
ensureOrderedTransactionIndices(newBackstack);
ensureNoDuplicateControllers(newBackstack);
backstack.setBackstack(newBackstack);
for (RouterTransaction transaction : backstack) {
List<RouterTransaction> transactionsToBeRemoved = new ArrayList<>();
for (RouterTransaction oldTransaction : oldTransactions) {
boolean contains = false;
for (RouterTransaction newTransaction : newBackstack) {
if (oldTransaction.controller == newTransaction.controller) {
contains = true;
break;
}
}
if (!contains) {
// Inform the controller that it will be destroyed soon
oldTransaction.controller.isBeingDestroyed = true;
transactionsToBeRemoved.add(oldTransaction);
}
}
// Ensure all new controllers have a valid router set
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
RouterTransaction transaction = backstackIterator.next();
transaction.onAttachedToRouter();
setControllerRouter(transaction.controller);
}
if (newBackstack.size() > 0) {
List<RouterTransaction> reverseNewBackstack = new ArrayList<>(newBackstack);
Collections.reverse(reverseNewBackstack);
List<RouterTransaction> newVisibleTransactions = getVisibleTransactions(reverseNewBackstack.iterator());
boolean newRootRequiresPush = !(newVisibleTransactions.size() > 0 && oldTransactions.contains(newVisibleTransactions.get(0)));
boolean visibleTransactionsChanged = !backstacksAreEqual(newVisibleTransactions, oldVisibleTransactions);
if (visibleTransactionsChanged) {
@@ -450,11 +479,22 @@ public abstract class Router {
}
}
// Ensure all new controllers have a valid router set
for (RouterTransaction transaction : newBackstack) {
transaction.controller.setRouter(this);
} else {
// Remove all visible controllers that were previously on the backstack
for (int i = oldVisibleTransactions.size() - 1; i >= 0; i--) {
RouterTransaction transaction = oldVisibleTransactions.get(i);
ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler();
ControllerChangeHandler.completeHandlerImmediately(transaction.controller.getInstanceId());
performControllerChange(null, transaction, false, localHandler);
}
}
// Destroy all old controllers that are no longer on the backstack. We don't do this when we initially
// set the backstack to prevent the possibility that they'll be destroyed before the controller
// change handler runs.
for (RouterTransaction removedTransaction : transactionsToBeRemoved) {
removedTransaction.controller.destroy();
}
}
/**
@@ -511,6 +551,8 @@ public abstract class Router {
}
public final void onActivityStarted(@NonNull Activity activity) {
isActivityStopped = false;
for (RouterTransaction transaction : backstack) {
transaction.controller.activityStarted(activity);
@@ -548,6 +590,8 @@ public abstract class Router {
childRouter.onActivityStopped(activity);
}
}
isActivityStopped = true;
}
public void onActivityDestroyed(@NonNull Activity activity) {
@@ -574,7 +618,7 @@ public abstract class Router {
container = null;
}
void prepareForHostDetach() {
public void prepareForHostDetach() {
for (RouterTransaction transaction : backstack) {
if (ControllerChangeHandler.completeHandlerImmediately(transaction.controller.getInstanceId())) {
transaction.controller.setNeedsAttach(true);
@@ -584,8 +628,6 @@ public abstract class Router {
}
public void saveInstanceState(@NonNull Bundle outState) {
prepareForHostDetach();
Bundle backstackState = new Bundle();
backstack.saveInstanceState(backstackState);
@@ -688,7 +730,7 @@ public abstract class Router {
@NonNull
final List<Controller> getControllers() {
List<Controller> controllers = new ArrayList<>();
List<Controller> controllers = new ArrayList<>(backstack.size());
Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
while (backstackIterator.hasNext()) {
@@ -726,7 +768,7 @@ public abstract class Router {
performControllerChange(to, from, isPush, changeHandler);
}
private void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) {
void performControllerChange(@Nullable RouterTransaction to, @Nullable RouterTransaction from, boolean isPush, @Nullable ControllerChangeHandler changeHandler) {
Controller toController = to != null ? to.controller : null;
Controller fromController = from != null ? from.controller : null;
boolean forceDetachDestroy = false;
@@ -753,7 +795,7 @@ public abstract class Router {
throw new IllegalStateException("Trying to push a controller that has already been destroyed. (" + to.getClass().getSimpleName() + ")");
}
final ChangeTransaction transaction = new ChangeTransaction(to, from, isPush, container, changeHandler, changeListeners);
final ChangeTransaction transaction = new ChangeTransaction(to, from, isPush, container, changeHandler, new ArrayList<>(changeListeners));
if (pendingControllerChanges.size() > 0) {
// If we already have changes queued up (awaiting full container attach), queue this one up as well so they don't happen
@@ -785,6 +827,9 @@ public abstract class Router {
}
protected void pushToBackstack(@NonNull RouterTransaction entry) {
if (backstack.contains(entry.controller)) {
throw new IllegalStateException("Trying to push a controller that already exists on the backstack.");
}
backstack.push(entry);
}
@@ -823,7 +868,7 @@ public abstract class Router {
}
final int childCount = container.getChildCount();
for (int i = 0; i < childCount; i++) {
for (int i = childCount - 1; i >= 0; i--) {
final View child = container.getChildAt(i);
if (!views.contains(child)) {
container.removeView(child);
@@ -831,10 +876,10 @@ public abstract class Router {
}
}
// Swap around transaction indicies to ensure they don't get thrown out of order by the
// Swap around transaction indices to ensure they don't get thrown out of order by the
// developer rearranging the backstack at runtime.
private void ensureOrderedTransactionIndices(List<RouterTransaction> backstack) {
List<Integer> indices = new ArrayList<>();
List<Integer> indices = new ArrayList<>(backstack.size());
for (RouterTransaction transaction : backstack) {
transaction.ensureValidIndex(getTransactionIndexer());
indices.add(transaction.transactionIndex);
@@ -847,6 +892,17 @@ public abstract class Router {
}
}
private void ensureNoDuplicateControllers(List<RouterTransaction> backstack) {
for (int i = 0; i < backstack.size(); i++) {
Controller controller = backstack.get(i).controller;
for (int j = i + 1; j < backstack.size(); j++) {
if (backstack.get(j).controller == controller) {
throw new IllegalStateException("Trying to push the same controller to the backstack more than once.");
}
}
}
}
private void addRouterViewsToList(@NonNull Router router, @NonNull List<View> list) {
for (Controller controller : router.getControllers()) {
if (controller.getView() != null) {
@@ -906,6 +962,6 @@ public abstract class Router {
abstract boolean hasHost();
@NonNull abstract List<Router> getSiblingRouters();
@NonNull abstract Router getRootRouter();
@Nullable abstract TransactionIndexer getTransactionIndexer();
@NonNull abstract TransactionIndexer getTransactionIndexer();
}
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.internal.TransactionIndexer;
@@ -108,11 +108,8 @@ public class RouterTransaction {
}
}
void ensureValidIndex(@Nullable TransactionIndexer indexer) {
if (indexer == null) {
throw new RuntimeException();
}
if (transactionIndex == INVALID_INDEX && indexer != null) {
void ensureValidIndex(@NonNull TransactionIndexer indexer) {
if (transactionIndex == INVALID_INDEX) {
transactionIndex = indexer.nextIndex();
}
}
@@ -4,8 +4,8 @@ import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -25,11 +25,11 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
public static final long DEFAULT_ANIMATION_DURATION = -1;
private long animationDuration;
private boolean removesFromViewOnPush;
private boolean canceled;
private boolean needsImmediateCompletion;
boolean removesFromViewOnPush;
boolean canceled;
boolean needsImmediateCompletion;
private boolean completed;
private Animator animator;
Animator animator;
private OnAnimationReadyOrAbortedListener onAnimationReadyOrAbortedListener;
@SuppressWarnings("WeakerAccess")
@@ -140,7 +140,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
}
}
private void complete(@NonNull ControllerChangeCompletedListener changeListener, @Nullable AnimatorListener animatorListener) {
void complete(@NonNull ControllerChangeCompletedListener changeListener, @Nullable AnimatorListener animatorListener) {
if (!completed) {
completed = true;
changeListener.onChangeCompleted();
@@ -157,7 +157,7 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
onAnimationReadyOrAbortedListener = null;
}
private void performAnimation(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, final boolean toAddedToContainer, @NonNull final ControllerChangeCompletedListener changeListener) {
void performAnimation(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, final boolean toAddedToContainer, @NonNull final ControllerChangeCompletedListener changeListener) {
if (canceled) {
complete(changeListener, null);
return;
@@ -182,8 +182,12 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
if (from != null && (!isPush || removesFromViewOnPush) && needsImmediateCompletion) {
container.removeView(from);
if (from != null) {
resetFromView(from);
}
if (to != null && to.getParent() == container) {
container.removeView(to);
}
complete(changeListener, this);
@@ -2,8 +2,8 @@ package com.bluelinelabs.conductor.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.transition.AutoTransition;
import android.transition.Transition;
import android.view.View;
@@ -12,6 +12,9 @@ import android.view.ViewGroup;
import com.bluelinelabs.conductor.ControllerChangeHandler;
/**
* @deprecated It's very rare that a simple AutoTransition is what you want when changing controllers. This class
* is deprecated simply because it was often a red herring for people trying to make nice transitions.
*
* A change handler that will use an AutoTransition.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -3,8 +3,8 @@ package com.bluelinelabs.conductor.changehandler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -3,8 +3,8 @@ package com.bluelinelabs.conductor.changehandler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -0,0 +1,654 @@
package com.bluelinelabs.conductor.changehandler;
import android.annotation.TargetApi;
import android.app.SharedElementCallback;
import android.graphics.Rect;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
import android.transition.TransitionSet;
import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.internal.TransitionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* A TransitionChangeHandler that facilitates using different Transitions for the entering view, the exiting view,
* and shared elements between the two.
*/
// Much of this class is based on FragmentTransition.java and FragmentTransitionCompat21.java from the Android support library
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public abstract class SharedElementTransitionChangeHandler extends TransitionChangeHandler {
// A map of from -> to names. Generally these will be the same.
@NonNull final ArrayMap<String, String> sharedElementNames = new ArrayMap<>();
@NonNull final List<String> waitForTransitionNames = new ArrayList<>();
@NonNull final List<ViewParentPair> removedViews = new ArrayList<>();
@Nullable Transition exitTransition;
@Nullable Transition enterTransition;
@Nullable Transition sharedElementTransition;
@Nullable private SharedElementCallback exitTransitionCallback;
@Nullable private SharedElementCallback enterTransitionCallback;
@NonNull
@Override
protected final Transition getTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
exitTransition = getExitTransition(container, from, to, isPush);
enterTransition = getEnterTransition(container, from, to, isPush);
sharedElementTransition = getSharedElementTransition(container, from, to, isPush);
exitTransitionCallback = getExitTransitionCallback(container, from, to, isPush);
enterTransitionCallback = getEnterTransitionCallback(container, from, to, isPush);
if (enterTransition == null && sharedElementTransition == null && exitTransition == null) {
throw new IllegalStateException("SharedElementTransitionChangeHandler must have at least one transaction.");
}
return mergeTransitions(isPush);
}
@Override
public void prepareForTransition(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, @NonNull final Transition transition, final boolean isPush, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
OnTransitionPreparedListener listener = new OnTransitionPreparedListener() {
@Override
public void onPrepared() {
configureTransition(container, from, to, transition, isPush);
onTransitionPreparedListener.onPrepared();
}
};
configureSharedElements(container, from, to, isPush);
if (to != null && to.getParent() == null && waitForTransitionNames.size() > 0) {
waitOnAllTransitionNames(to, listener);
container.addView(to);
} else {
listener.onPrepared();
}
}
@Override
public final void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
if (to != null && removedViews.size() > 0) {
to.setVisibility(View.VISIBLE);
for (ViewParentPair removedView : removedViews) {
removedView.parent.addView(removedView.view);
}
removedViews.clear();
}
super.executePropertyChanges(container, from, to, transition, isPush);
}
@Override
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
super.onAbortPush(newHandler, newTop);
removedViews.clear();
}
void configureTransition(@NonNull final ViewGroup container, @Nullable View from, @Nullable View to, @NonNull final Transition transition, boolean isPush) {
final View nonExistentView = new View(container.getContext());
List<View> fromSharedElements = new ArrayList<>();
List<View> toSharedElements = new ArrayList<>();
configureSharedElements(container, nonExistentView, to, from, isPush, fromSharedElements, toSharedElements);
List<View> exitingViews = exitTransition != null ? configureEnteringExitingViews(exitTransition, from, fromSharedElements, nonExistentView) : null;
if (exitingViews == null || exitingViews.isEmpty()) {
exitTransition = null;
}
if (enterTransition != null) {
enterTransition.addTarget(nonExistentView);
}
final List<View> enteringViews = new ArrayList<>();
scheduleRemoveTargets(transition, enterTransition, enteringViews, exitTransition, exitingViews, sharedElementTransition, toSharedElements);
scheduleTargetChange(container, to, nonExistentView, toSharedElements, enteringViews, exitingViews);
setNameOverrides(container, toSharedElements);
scheduleNameReset(container, toSharedElements);
}
private void waitOnAllTransitionNames(@NonNull final View to, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
OnPreDrawListener onPreDrawListener = new OnPreDrawListener() {
boolean addedSubviewListeners;
@Override
public boolean onPreDraw() {
List<View> foundViews = new ArrayList<>();
boolean allViewsFound = true;
for (String transitionName : waitForTransitionNames) {
View namedView = TransitionUtils.findNamedView(to, transitionName);
if (namedView != null) {
foundViews.add(TransitionUtils.findNamedView(to, transitionName));
} else {
allViewsFound = false;
break;
}
}
if (allViewsFound && !addedSubviewListeners) {
addedSubviewListeners = true;
waitOnChildTransitionNames(to, foundViews, this, onTransitionPreparedListener);
}
return false;
}
};
to.getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);
}
void waitOnChildTransitionNames(@NonNull final View to, @NonNull List<View> foundViews, @NonNull final OnPreDrawListener parentPreDrawListener, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
for (final View view : foundViews) {
OneShotPreDrawListener.add(true, view, new Runnable() {
@Override
public void run() {
waitForTransitionNames.remove(view.getTransitionName());
removedViews.add(new ViewParentPair(view, (ViewGroup)view.getParent()));
((ViewGroup)view.getParent()).removeView(view);
if (waitForTransitionNames.size() == 0) {
to.getViewTreeObserver().removeOnPreDrawListener(parentPreDrawListener);
to.setVisibility(View.INVISIBLE);
onTransitionPreparedListener.onPrepared();
}
}
});
}
}
private void scheduleTargetChange(@NonNull final ViewGroup container, @Nullable final View to, @NonNull final View nonExistentView,
@NonNull final List<View> toSharedElements, @NonNull final List<View> enteringViews, @Nullable final List<View> exitingViews) {
OneShotPreDrawListener.add(true, container, new Runnable() {
@Override
public void run() {
if (enterTransition != null) {
enterTransition.removeTarget(nonExistentView);
List<View> views = configureEnteringExitingViews(enterTransition, to, toSharedElements, nonExistentView);
enteringViews.addAll(views);
}
if (exitingViews != null) {
if (exitTransition != null) {
List<View> tempExiting = new ArrayList<>();
tempExiting.add(nonExistentView);
TransitionUtils.replaceTargets(exitTransition, exitingViews, tempExiting);
}
exitingViews.clear();
exitingViews.add(nonExistentView);
}
}
});
}
private Transition mergeTransitions(boolean isPush) {
boolean overlap = enterTransition == null || exitTransition == null || allowTransitionOverlap(isPush);
if (overlap) {
return TransitionUtils.mergeTransitions(TransitionSet.ORDERING_TOGETHER, exitTransition, enterTransition, sharedElementTransition);
} else {
Transition staggered = TransitionUtils.mergeTransitions(TransitionSet.ORDERING_SEQUENTIAL, exitTransition, enterTransition);
return TransitionUtils.mergeTransitions(TransitionSet.ORDERING_TOGETHER, staggered, sharedElementTransition);
}
}
@NonNull List<View> configureEnteringExitingViews(@NonNull Transition transition, @Nullable View view, @NonNull List<View> sharedElements, @NonNull View nonExistentView) {
List<View> viewList = new ArrayList<>();
if (view != null) {
captureTransitioningViews(viewList, view);
}
viewList.removeAll(sharedElements);
if (!viewList.isEmpty()) {
viewList.add(nonExistentView);
TransitionUtils.addTargets(transition, viewList);
}
return viewList;
}
private void configureSharedElements(@NonNull ViewGroup container, @NonNull final View nonExistentView, @Nullable final View to, @Nullable View from,
final boolean isPush, @NonNull final List<View> fromSharedElements, @NonNull final List<View> toSharedElements) {
if (to == null || from == null) {
return;
}
ArrayMap<String, View> capturedFromSharedElements = captureFromSharedElements(from);
if (sharedElementNames.isEmpty()) {
sharedElementTransition = null;
} else if (capturedFromSharedElements != null) {
fromSharedElements.addAll(capturedFromSharedElements.values());
}
if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
return;
}
callSharedElementStartEnd(capturedFromSharedElements, true);
final Rect toEpicenter;
if (sharedElementTransition != null) {
toEpicenter = new Rect();
TransitionUtils.setTargets(sharedElementTransition, nonExistentView, fromSharedElements);
setFromEpicenter(capturedFromSharedElements);
if (enterTransition != null) {
enterTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
if (toEpicenter.isEmpty()) {
return null;
}
return toEpicenter;
}
});
}
} else {
toEpicenter = null;
}
OneShotPreDrawListener.add(true, container, new Runnable() {
@Override
public void run() {
ArrayMap<String, View> capturedToSharedElements = captureToSharedElements(to, isPush);
if (capturedToSharedElements != null) {
toSharedElements.addAll(capturedToSharedElements.values());
toSharedElements.add(nonExistentView);
}
callSharedElementStartEnd(capturedToSharedElements, false);
if (sharedElementTransition != null) {
sharedElementTransition.getTargets().clear();
sharedElementTransition.getTargets().addAll(toSharedElements);
TransitionUtils.replaceTargets(sharedElementTransition, fromSharedElements, toSharedElements);
final View toEpicenterView = getToEpicenterView(capturedToSharedElements);
if (toEpicenterView != null && toEpicenter != null) {
TransitionUtils.getBoundsOnScreen(toEpicenterView, toEpicenter);
}
}
}
});
}
@Nullable View getToEpicenterView(@Nullable ArrayMap<String, View> toSharedElements) {
if (enterTransition != null && sharedElementNames.size() > 0 && toSharedElements != null) {
return toSharedElements.get(sharedElementNames.valueAt(0));
}
return null;
}
private void setFromEpicenter(@Nullable ArrayMap<String, View> fromSharedElements) {
if (sharedElementNames.size() > 0 && fromSharedElements != null) {
final View fromEpicenterView = fromSharedElements.get(sharedElementNames.keyAt(0));
if (sharedElementTransition != null) {
TransitionUtils.setEpicenter(sharedElementTransition, fromEpicenterView);
}
if (exitTransition != null) {
TransitionUtils.setEpicenter(exitTransition, fromEpicenterView);
}
}
}
@Nullable ArrayMap<String, View> captureToSharedElements(@Nullable final View to, boolean isPush) {
if (sharedElementNames.isEmpty() || sharedElementTransition == null || to == null) {
sharedElementNames.clear();
return null;
}
final ArrayMap<String, View> toSharedElements = new ArrayMap<>();
TransitionUtils.findNamedViews(toSharedElements, to);
for (ViewParentPair removedView : removedViews) {
toSharedElements.put(removedView.view.getTransitionName(), removedView.view);
}
final List<String> names = new ArrayList<>(sharedElementNames.values());
toSharedElements.retainAll(names);
if (enterTransitionCallback != null) {
enterTransitionCallback.onMapSharedElements(names, toSharedElements);
for (int i = names.size() - 1; i >= 0; i--) {
String name = names.get(i);
View view = toSharedElements.get(name);
if (view == null) {
String key = findKeyForValue(sharedElementNames, name);
if (key != null) {
sharedElementNames.remove(key);
}
} else if (!name.equals(view.getTransitionName())) {
String key = findKeyForValue(sharedElementNames, name);
if (key != null) {
sharedElementNames.put(key, view.getTransitionName());
}
}
}
} else {
for (int i = sharedElementNames.size() - 1; i >= 0; i--) {
final String targetName = sharedElementNames.valueAt(i);
if (!toSharedElements.containsKey(targetName)) {
sharedElementNames.removeAt(i);
}
}
}
return toSharedElements;
}
@Nullable String findKeyForValue(@NonNull ArrayMap<String, String> map, @NonNull String value) {
final int numElements = map.size();
for (int i = 0; i < numElements; i++) {
if (value.equals(map.valueAt(i))) {
return map.keyAt(i);
}
}
return null;
}
@Nullable
private ArrayMap<String, View> captureFromSharedElements(@NonNull View from) {
if (sharedElementNames.isEmpty() || sharedElementTransition == null) {
sharedElementNames.clear();
return null;
}
final ArrayMap<String, View> fromSharedElements = new ArrayMap<>();
TransitionUtils.findNamedViews(fromSharedElements, from);
final List<String> names = new ArrayList<>(sharedElementNames.keySet());
fromSharedElements.retainAll(names);
if (exitTransitionCallback != null) {
exitTransitionCallback.onMapSharedElements(names, fromSharedElements);
for (int i = names.size() - 1; i >= 0; i--) {
String name = names.get(i);
View view = fromSharedElements.get(name);
if (view == null) {
sharedElementNames.remove(name);
} else if (!name.equals(view.getTransitionName())) {
String targetValue = sharedElementNames.remove(name);
sharedElementNames.put(view.getTransitionName(), targetValue);
}
}
} else {
sharedElementNames.retainAll(fromSharedElements.keySet());
}
return fromSharedElements;
}
void callSharedElementStartEnd(@Nullable ArrayMap<String, View> sharedElements, boolean isStart) {
if (enterTransitionCallback != null) {
final int count = sharedElements == null ? 0 : sharedElements.size();
List<View> views = new ArrayList<>(count);
List<String> names = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
names.add(sharedElements.keyAt(i));
views.add(sharedElements.valueAt(i));
}
if (isStart) {
enterTransitionCallback.onSharedElementStart(names, views, null);
} else {
enterTransitionCallback.onSharedElementEnd(names, views, null);
}
}
}
private void captureTransitioningViews(@NonNull List<View> transitioningViews, @NonNull View view) {
if (view.getVisibility() == View.VISIBLE) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
if (viewGroup.isTransitionGroup()) {
transitioningViews.add(viewGroup);
} else {
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
View child = viewGroup.getChildAt(i);
captureTransitioningViews(transitioningViews, child);
}
}
} else {
transitioningViews.add(view);
}
}
}
private void scheduleRemoveTargets(@NonNull final Transition overallTransition,
@Nullable final Transition enterTransition, @Nullable final List<View> enteringViews,
@Nullable final Transition exitTransition, @Nullable final List<View> exitingViews,
@Nullable final Transition sharedElementTransition, @Nullable final List<View> toSharedElements) {
overallTransition.addListener(new TransitionListener() {
@Override
public void onTransitionStart(Transition transition) {
if (enterTransition != null && enteringViews != null) {
TransitionUtils.replaceTargets(enterTransition, enteringViews, null);
}
if (exitTransition != null && exitingViews != null) {
TransitionUtils.replaceTargets(exitTransition, exitingViews, null);
}
if (sharedElementTransition != null && toSharedElements != null) {
TransitionUtils.replaceTargets(sharedElementTransition, toSharedElements, null);
}
}
@Override
public void onTransitionEnd(Transition transition) { }
@Override
public void onTransitionCancel(Transition transition) { }
@Override
public void onTransitionPause(Transition transition) { }
@Override
public void onTransitionResume(Transition transition) { }
});
}
private void setNameOverrides(@NonNull final View container, @NonNull final List<View> toSharedElements) {
OneShotPreDrawListener.add(true, container, new Runnable() {
@Override
public void run() {
final int numSharedElements = toSharedElements.size();
for (int i = 0; i < numSharedElements; i++) {
View view = toSharedElements.get(i);
String name = view.getTransitionName();
if (name != null) {
String inName = findKeyForValue(sharedElementNames, name);
view.setTransitionName(inName);
}
}
}
});
}
private void scheduleNameReset(@NonNull final ViewGroup container, @NonNull final List<View> toSharedElements) {
OneShotPreDrawListener.add(true, container, new Runnable() {
@Override
public void run() {
final int numSharedElements = toSharedElements.size();
for (int i = 0; i < numSharedElements; i++) {
final View view = toSharedElements.get(i);
final String name = view.getTransitionName();
final String inName = sharedElementNames.get(name);
view.setTransitionName(inName);
}
}
});
}
/**
* Will be called when views are ready to have their shared elements configured. Within this method one of the addSharedElement methods
* should be called for each shared element that will be used. If one or more of these shared elements will not instantly be available in
* the incoming view (for ex, in a RecyclerView), waitOnSharedElementNamed can be called to delay the transition until everything is available.
*/
public abstract void configureSharedElements(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
/**
* Should return the transition that will be used on the exiting ("from") view, if one is desired.
*/
@Nullable
public abstract Transition getExitTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
/**
* Should return the transition that will be used on shared elements between the from and to views.
*/
@Nullable
public abstract Transition getSharedElementTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
/**
* Should return the transition that will be used on the entering ("to") view, if one is desired.
*/
@Nullable
public abstract Transition getEnterTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush);
/**
* Should return a callback that can be used to customize transition behavior of the shared element transition for the "from" view.
*/
@Nullable
public SharedElementCallback getExitTransitionCallback(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
return null;
}
/**
* Should return a callback that can be used to customize transition behavior of the shared element transition for the "to" view.
*/
@Nullable
public SharedElementCallback getEnterTransitionCallback(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
return null;
}
/**
* Should return whether or not the the exit transition and enter transition should overlap. If true,
* the enter transition will start as soon as possible. Otherwise, the enter transition will wait until the
* completion of the exit transition. Defaults to true.
*/
public boolean allowTransitionOverlap(boolean isPush) {
return true;
}
/**
* Used to register an element that will take part in the shared element transition.
*
* @param name The transition name that is used for both the entering and exiting views.
*/
protected final void addSharedElement(@NonNull String name) {
sharedElementNames.put(name, name);
}
/**
* Used to register an element that will take part in the shared element transition. Maps the name used in the
* "from" view to the name used in the "to" view if they are not the same.
*
* @param fromName The transition name used in the "from" view
* @param toName The transition name used in the "to" view
*/
protected final void addSharedElement(@NonNull String fromName, @NonNull String toName) {
sharedElementNames.put(fromName, toName);
}
/**
* Used to register an element that will take part in the shared element transition. Maps the name used in the
* "from" view to the name used in the "to" view if they are not the same.
*
* @param sharedElement The view from the "from" view that will take part in the shared element transition
* @param toName The transition name used in the "to" view
*/
protected final void addSharedElement(@NonNull View sharedElement, @NonNull String toName) {
String transitionName = sharedElement.getTransitionName();
if (transitionName == null) {
throw new IllegalArgumentException("Unique transitionNames are required for all sharedElements");
}
sharedElementNames.put(transitionName, toName);
}
/**
* The transition will be delayed until the view with the name passed in is available in the "to" hierarchy. This is
* particularly useful for views that don't load instantly, like RecyclerViews. Note that using this method can
* potentially lock up your app indefinitely if the view never loads!
*/
protected final void waitOnSharedElementNamed(@NonNull String name) {
if (!sharedElementNames.values().contains(name)) {
throw new IllegalStateException("Can't wait on a shared element that hasn't been registered using addSharedElement");
}
waitForTransitionNames.add(name);
}
private static class OneShotPreDrawListener implements OnPreDrawListener, View.OnAttachStateChangeListener {
private final View view;
private ViewTreeObserver viewTreeObserver;
private final Runnable runnable;
private final boolean preDrawReturnValue;
private OneShotPreDrawListener(boolean preDrawReturnValue, @NonNull View view, @NonNull Runnable runnable) {
this.preDrawReturnValue = preDrawReturnValue;
this.view = view;
viewTreeObserver = view.getViewTreeObserver();
this.runnable = runnable;
}
@NonNull
public static OneShotPreDrawListener add(boolean preDrawReturnValue, @NonNull View view, @NonNull Runnable runnable) {
OneShotPreDrawListener listener = new OneShotPreDrawListener(preDrawReturnValue, view, runnable);
view.getViewTreeObserver().addOnPreDrawListener(listener);
view.addOnAttachStateChangeListener(listener);
return listener;
}
@Override
public boolean onPreDraw() {
removeListener();
runnable.run();
return preDrawReturnValue;
}
private void removeListener() {
if (viewTreeObserver.isAlive()) {
viewTreeObserver.removeOnPreDrawListener(this);
} else {
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
view.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewAttachedToWindow(View v) {
viewTreeObserver = v.getViewTreeObserver();
}
@Override
public void onViewDetachedFromWindow(View v) {
removeListener();
}
}
private static class ViewParentPair {
@NonNull final View view;
@NonNull final ViewGroup parent;
ViewParentPair(@NonNull View view, ViewGroup parent) {
this.view = view;
this.parent = parent;
}
}
}
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.changehandler;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
@@ -2,8 +2,8 @@ package com.bluelinelabs.conductor.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
import android.transition.TransitionManager;
@@ -23,7 +23,7 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
void onPrepared();
}
private boolean canceled;
boolean canceled;
private boolean needsImmediateCompletion;
/**
@@ -2,8 +2,8 @@ package com.bluelinelabs.conductor.changehandler;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -3,8 +3,8 @@ package com.bluelinelabs.conductor.changehandler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.internal;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
public class ClassUtils {
@@ -11,8 +11,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
@@ -40,6 +40,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
private boolean hasRegisteredCallbacks;
private boolean destroyed;
private boolean attached;
private boolean hasPreparedForHostDetach;
private static final Map<Activity, LifecycleHandler> activeLifecycleHandlers = new HashMap<>();
private SparseArray<String> permissionRequestMap = new SparseArray<>();
@@ -195,7 +196,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
}
}
for (ActivityHostedRouter router : routerMap.values()) {
for (ActivityHostedRouter router : new ArrayList<>(routerMap.values())) {
router.onContextAvailable();
}
}
@@ -205,7 +206,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
destroyed = true;
if (activity != null) {
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
router.onActivityDestroyed(activity);
}
}
@@ -218,7 +219,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
String instanceId = activityRequestMap.get(requestCode);
if (instanceId != null) {
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
router.onActivityResult(instanceId, requestCode, resultCode, data);
}
}
@@ -230,7 +231,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
String instanceId = permissionRequestMap.get(requestCode);
if (instanceId != null) {
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
router.onRequestPermissionsResult(instanceId, requestCode, permissions, grantResults);
}
}
@@ -238,7 +239,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
@Override
public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
Boolean handled = router.handleRequestedPermission(permission);
if (handled != null) {
return handled;
@@ -251,7 +252,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
router.onCreateOptionsMenu(menu, inflater);
}
}
@@ -260,14 +261,14 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
router.onPrepareOptionsMenu(menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
if (router.onOptionsItemSelected(item)) {
return true;
}
@@ -321,7 +322,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
if (this.activity == null && findInActivity(activity) == LifecycleHandler.this) {
this.activity = activity;
for (ActivityHostedRouter router : routerMap.values()) {
for (ActivityHostedRouter router : new ArrayList<>(routerMap.values())) {
router.onContextAvailable();
}
}
@@ -330,7 +331,9 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
@Override
public void onActivityStarted(Activity activity) {
if (this.activity == activity) {
for (Router router : routerMap.values()) {
hasPreparedForHostDetach = false;
for (Router router : getRouters()) {
router.onActivityStarted(activity);
}
}
@@ -339,7 +342,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
@Override
public void onActivityResumed(Activity activity) {
if (this.activity == activity) {
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
router.onActivityResumed(activity);
}
}
@@ -348,7 +351,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
@Override
public void onActivityPaused(Activity activity) {
if (this.activity == activity) {
for (Router router : routerMap.values()) {
for (Router router : getRouters()) {
router.onActivityPaused(activity);
}
}
@@ -357,7 +360,9 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
@Override
public void onActivityStopped(Activity activity) {
if (this.activity == activity) {
for (Router router : routerMap.values()) {
prepareForHostDetachIfNeeded();
for (Router router : getRouters()) {
router.onActivityStopped(activity);
}
}
@@ -366,7 +371,9 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
if (this.activity == activity) {
for (Router router : routerMap.values()) {
prepareForHostDetachIfNeeded();
for (Router router : getRouters()) {
Bundle bundle = new Bundle();
router.saveInstanceState(bundle);
outState.putBundle(KEY_ROUTER_STATE_PREFIX + router.getContainerId(), bundle);
@@ -379,6 +386,16 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
activeLifecycleHandlers.remove(activity);
}
private void prepareForHostDetachIfNeeded() {
if (!hasPreparedForHostDetach) {
hasPreparedForHostDetach = true;
for (Router router : getRouters()) {
router.prepareForHostDetach();
}
}
}
private static class PendingPermissionRequest implements Parcelable {
final String instanceId;
final String[] permissions;
@@ -390,7 +407,7 @@ public class LifecycleHandler extends Fragment implements ActivityLifecycleCallb
this.requestCode = requestCode;
}
private PendingPermissionRequest(Parcel in) {
PendingPermissionRequest(Parcel in) {
instanceId = in.readString();
permissions = in.createStringArray();
requestCode = in.readInt();
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.internal;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -2,7 +2,7 @@ package com.bluelinelabs.conductor.internal;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.util.SparseArray;
public class StringSparseArrayParceler implements Parcelable {
@@ -13,7 +13,7 @@ public class StringSparseArrayParceler implements Parcelable {
this.stringSparseArray = stringSparseArray;
}
private StringSparseArrayParceler(@NonNull Parcel in) {
StringSparseArrayParceler(@NonNull Parcel in) {
stringSparseArray = new SparseArray<>();
final int size = in.readInt();
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.internal;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
public class TransactionIndexer {
@@ -0,0 +1,185 @@
package com.bluelinelabs.conductor.internal;
import android.annotation.TargetApi;
import android.graphics.Rect;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.transition.Transition;
import android.transition.TransitionSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import java.util.Map;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class TransitionUtils {
public static void findNamedViews(@NonNull Map<String, View> namedViews, View view) {
if (view.getVisibility() == View.VISIBLE) {
String transitionName = view.getTransitionName();
if (transitionName != null) {
namedViews.put(transitionName, view);
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = viewGroup.getChildAt(i);
findNamedViews(namedViews, child);
}
}
}
}
@Nullable
public static View findNamedView(@NonNull View view, @NonNull String transitionName) {
if (transitionName.equals(view.getTransitionName())) {
return view;
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View viewWithTransitionName = findNamedView(viewGroup.getChildAt(i), transitionName);
if (viewWithTransitionName != null) {
return viewWithTransitionName;
}
}
}
return null;
}
public static void setEpicenter(@NonNull Transition transition, @Nullable View view) {
if (view != null) {
final Rect epicenter = new Rect();
getBoundsOnScreen(view, epicenter);
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
return epicenter;
}
});
}
}
public static void getBoundsOnScreen(@NonNull View view, @NonNull Rect epicenter) {
int[] loc = new int[2];
view.getLocationOnScreen(loc);
epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
}
public static void setTargets(@NonNull Transition transition, @NonNull View nonExistentView, @NonNull List<View> sharedViews) {
final List<View> views = transition.getTargets();
views.clear();
final int count = sharedViews.size();
for (int i = 0; i < count; i++) {
final View view = sharedViews.get(i);
bfsAddViewChildren(views, view);
}
views.add(nonExistentView);
sharedViews.add(nonExistentView);
addTargets(transition, sharedViews);
}
public static void addTargets(@Nullable Transition transition, @NonNull List<View> views) {
if (transition == null) {
return;
}
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
addTargets(child, views);
}
} else if (!hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (isNullOrEmpty(targets)) {
int numViews = views.size();
for (int i = 0; i < numViews; i++) {
transition.addTarget(views.get(i));
}
}
}
}
public static void replaceTargets(@NonNull Transition transition, @NonNull List<View> oldTargets, @Nullable List<View> newTargets) {
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
replaceTargets(child, oldTargets, newTargets);
}
} else if (!TransitionUtils.hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (targets != null && targets.size() == oldTargets.size() && targets.containsAll(oldTargets)) {
final int targetCount = newTargets == null ? 0 : newTargets.size();
for (int i = 0; i < targetCount; i++) {
transition.addTarget(newTargets.get(i));
}
for (int i = oldTargets.size() - 1; i >= 0; i--) {
transition.removeTarget(oldTargets.get(i));
}
}
}
}
private static void bfsAddViewChildren(@NonNull final List<View> views, @NonNull final View startView) {
final int startIndex = views.size();
if (containedBeforeIndex(views, startView, startIndex)) {
return; // This child is already in the list, so all its children are also.
}
views.add(startView);
for (int index = startIndex; index < views.size(); index++) {
final View view = views.get(index);
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
final int childCount = viewGroup.getChildCount();
for (int childIndex = 0; childIndex < childCount; childIndex++) {
final View child = viewGroup.getChildAt(childIndex);
if (!containedBeforeIndex(views, child, startIndex)) {
views.add(child);
}
}
}
}
}
private static boolean containedBeforeIndex(@NonNull List<View> views, View view, int maxIndex) {
for (int i = 0; i < maxIndex; i++) {
if (views.get(i) == view) {
return true;
}
}
return false;
}
public static boolean hasSimpleTarget(@NonNull Transition transition) {
return !isNullOrEmpty(transition.getTargetIds())
|| !isNullOrEmpty(transition.getTargetNames())
|| !isNullOrEmpty(transition.getTargetTypes());
}
private static boolean isNullOrEmpty(@Nullable List list) {
return list == null || list.isEmpty();
}
@NonNull
public static TransitionSet mergeTransitions(int ordering, Transition... transitions) {
TransitionSet transitionSet = new TransitionSet();
for (Transition transition : transitions) {
if (transition != null) {
transitionSet.addTransition(transition);
}
}
transitionSet.setOrdering(ordering);
return transitionSet;
}
}
@@ -23,11 +23,11 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
}
private boolean rootAttached = false;
private boolean childrenAttached = false;
boolean childrenAttached = false;
private boolean activityStopped = false;
private ReportedState reportedState = ReportedState.VIEW_DETACHED;
private ViewAttachListener attachListener;
private OnAttachStateChangeListener childOnAttachStateChangeListener;
OnAttachStateChangeListener childOnAttachStateChangeListener;
public ViewAttachHandler(ViewAttachListener attachListener) {
this.attachListener = attachListener;
@@ -81,7 +81,7 @@ public class ViewAttachHandler implements OnAttachStateChangeListener {
reportDetached(true);
}
private void reportAttached() {
void reportAttached() {
if (rootAttached && childrenAttached && !activityStopped && reportedState != ReportedState.ATTACHED) {
reportedState = ReportedState.ATTACHED;
attachListener.onAttached();
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
@@ -2,11 +2,13 @@ package com.bluelinelabs.conductor;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.Controller.RetainViewMode;
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.CallState;
import com.bluelinelabs.conductor.util.MockChangeHandler;
@@ -21,8 +23,10 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@@ -499,6 +503,38 @@ public class ControllerLifecycleCallbacksTests {
assertCalls(expectedCallState, child);
}
@Test
public void testChildLifecycleOrderingAfterUnexpectedAttach() {
Controller parent = new TestController();
parent.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
router.pushController(RouterTransaction.with(parent)
.pushChangeHandler(MockChangeHandler.defaultHandler())
.popChangeHandler(MockChangeHandler.defaultHandler()));
TestController child = new TestController();
child.setRetainViewMode(RetainViewMode.RETAIN_DETACH);
Router childRouter = parent.getChildRouter((ViewGroup)parent.getView().findViewById(TestController.VIEW_ID));
childRouter
.setRoot(RouterTransaction.with(child)
.pushChangeHandler(new SimpleSwapChangeHandler())
.popChangeHandler(new SimpleSwapChangeHandler()));
assertTrue(parent.isAttached());
assertTrue(child.isAttached());
ViewUtils.reportAttached(parent.getView(), false, true);
assertFalse(parent.isAttached());
assertFalse(child.isAttached());
ViewUtils.reportAttached(child.getView(), true);
assertFalse(parent.isAttached());
assertFalse(child.isAttached());
ViewUtils.reportAttached(parent.getView(), true);
assertTrue(parent.isAttached());
assertTrue(child.isAttached());
}
private MockChangeHandler getPushHandler(final CallState expectedCallState, final TestController controller) {
return MockChangeHandler.listeningChangeHandler(new ChangeHandlerListener() {
@Override
@@ -6,6 +6,7 @@ import android.view.ViewGroup;
import com.bluelinelabs.conductor.util.ActivityProxy;
import com.bluelinelabs.conductor.util.MockChangeHandler;
import com.bluelinelabs.conductor.util.TestController;
import com.bluelinelabs.conductor.util.ViewUtils;
import org.junit.Before;
import org.junit.Test;
@@ -232,6 +233,60 @@ public class ReattachCaseTests {
assertFalse(childController.isAttached());
}
// Attempt to test https://github.com/bluelinelabs/Conductor/issues/367
@Test
public void testViewIsAttachedAfterStartedActivityIsRecreated() {
Controller controller1 = new TestController();
Controller controller2 = new TestController();
router.setRoot(RouterTransaction.with(controller1));
assertTrue(controller1.isAttached());
// Lock screen
Bundle bundle = new Bundle();
activityProxy.pause().saveInstanceState(bundle).stop(false);
// Push a 2nd controller, which will rotate the screen once it unlocked
router.pushController(RouterTransaction.with(controller2));
assertTrue(controller2.isAttached());
assertTrue(controller2.getNeedsAttach());
// Unlock screen and rotate
activityProxy.start();
activityProxy.rotate();
assertTrue(controller2.isAttached());
}
@Test
public void testPopMiddleControllerAttaches() {
Controller controller1 = new TestController();
Controller controller2 = new TestController();
Controller controller3 = new TestController();
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2));
router.pushController(RouterTransaction.with(controller3));
router.popController(controller2);
assertFalse(controller1.isAttached());
assertFalse(controller2.isAttached());
assertTrue(controller3.isAttached());
controller1 = new TestController();
controller2 = new TestController();
controller3 = new TestController();
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2));
router.pushController(RouterTransaction.with(controller3).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler()));
router.popController(controller2);
assertTrue(controller1.isAttached());
assertFalse(controller2.isAttached());
assertTrue(controller3.isAttached());
}
private void sleepWakeDevice() {
activityProxy.saveInstanceState(new Bundle()).pause();
activityProxy.resume();
@@ -193,10 +193,10 @@ public class RouterChangeHandlerTests {
TestController newController1 = new TestController();
TestController newController2 = new TestController();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
MockChangeHandler pushController2Handler = MockChangeHandler.noRemoveViewOnPushHandler("pushController2");
List<RouterTransaction> newBackstack = Arrays.asList(
RouterTransaction.with(newController1),
RouterTransaction.with(newController2).pushChangeHandler(MockChangeHandler.noRemoveViewOnPushHandler())
RouterTransaction.with(newController2).pushChangeHandler(pushController2Handler)
);
router.setBackstack(newBackstack, setBackstackHandler);
@@ -226,14 +226,129 @@ public class RouterChangeHandlerTests {
assertEquals(newController2.getView(), newController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, newController1.changeHandlerHistory.fromViewAt(0));
assertEquals(newController1.getView(), newController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, newController1.changeHandlerHistory.latestChangeHandler().tag);
assertEquals(setBackstackHandler.tag, newController1.changeHandlerHistory.changeHandlerAt(0).tag);
assertEquals(pushController2Handler.tag, newController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController1.changeHandlerHistory.latestIsPush());
assertNotNull(newController2.changeHandlerHistory.latestToView());
assertEquals(newController2.getView(), newController2.changeHandlerHistory.latestToView());
assertEquals(newController1.getView(), newController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
assertEquals(pushController2Handler.tag, newController2.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(newController2.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackForPushHandlers() {
TestController initialController = new TestController();
MockChangeHandler initialPushHandler = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler = MockChangeHandler.taggedHandler("initialPop1", true);
RouterTransaction initialTransaction = RouterTransaction.with(initialController).pushChangeHandler(initialPushHandler).popChangeHandler(initialPopHandler);
router.setRoot(initialTransaction);
View initialView = initialController.getView();
TestController newController = new TestController();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = Arrays.asList(
initialTransaction,
RouterTransaction.with(newController)
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController.changeHandlerHistory.isValidHistory);
assertTrue(newController.changeHandlerHistory.isValidHistory);
assertEquals(2, initialController.changeHandlerHistory.size());
assertEquals(1, newController.changeHandlerHistory.size());
assertNotNull(initialController.changeHandlerHistory.latestToView());
assertEquals(newController.getView(), initialController.changeHandlerHistory.latestToView());
assertEquals(initialView, initialController.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController.changeHandlerHistory.latestIsPush());
assertTrue(newController.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackForInvertHandlersWithRemovesView() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
RouterTransaction initialTransaction1 = RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1);
router.setRoot(initialTransaction1);
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", true);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", true);
RouterTransaction initialTransaction2 = RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2);
router.pushController(initialTransaction2);
View initialView2 = initialController2.getView();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = Arrays.asList(
initialTransaction2,
initialTransaction1
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertEquals(3, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController1.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertFalse(initialController1.changeHandlerHistory.latestIsPush());
assertNotNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertFalse(initialController2.changeHandlerHistory.latestIsPush());
}
@Test
public void testSetBackstackForInvertHandlersWithoutRemovesView() {
TestController initialController1 = new TestController();
MockChangeHandler initialPushHandler1 = MockChangeHandler.taggedHandler("initialPush1", true);
MockChangeHandler initialPopHandler1 = MockChangeHandler.taggedHandler("initialPop1", true);
RouterTransaction initialTransaction1 = RouterTransaction.with(initialController1).pushChangeHandler(initialPushHandler1).popChangeHandler(initialPopHandler1);
router.setRoot(initialTransaction1);
TestController initialController2 = new TestController();
MockChangeHandler initialPushHandler2 = MockChangeHandler.taggedHandler("initialPush2", false);
MockChangeHandler initialPopHandler2 = MockChangeHandler.taggedHandler("initialPop2", false);
RouterTransaction initialTransaction2 = RouterTransaction.with(initialController2).pushChangeHandler(initialPushHandler2).popChangeHandler(initialPopHandler2);
router.pushController(initialTransaction2);
View initialView1 = initialController1.getView();
View initialView2 = initialController2.getView();
MockChangeHandler setBackstackHandler = MockChangeHandler.taggedHandler("setBackstackHandler", true);
List<RouterTransaction> newBackstack = Arrays.asList(
initialTransaction2,
initialTransaction1
);
router.setBackstack(newBackstack, setBackstackHandler);
assertTrue(initialController1.changeHandlerHistory.isValidHistory);
assertTrue(initialController2.changeHandlerHistory.isValidHistory);
assertEquals(2, initialController1.changeHandlerHistory.size());
assertEquals(2, initialController2.changeHandlerHistory.size());
assertNotNull(initialController1.changeHandlerHistory.latestToView());
assertEquals(initialView1, initialController1.changeHandlerHistory.latestFromView());
assertEquals(initialPushHandler2.tag, initialController1.changeHandlerHistory.latestChangeHandler().tag);
assertTrue(initialController1.changeHandlerHistory.latestIsPush());
assertNull(initialController2.changeHandlerHistory.latestToView());
assertEquals(initialView2, initialController2.changeHandlerHistory.latestFromView());
assertEquals(setBackstackHandler.tag, initialController2.changeHandlerHistory.latestChangeHandler().tag);
assertFalse(initialController2.changeHandlerHistory.latestIsPush());
}
}
@@ -1,7 +1,10 @@
package com.bluelinelabs.conductor;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller.LifecycleListener;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.util.ActivityProxy;
@@ -125,6 +128,31 @@ public class RouterTests {
assertNull(router.getControllerWithTag(controller2Tag));
}
@Test
public void testPopControllerConcurrentModificationException() {
int step = 1;
for (int i = 0; i < 10; i++, step++) {
router.pushController(RouterTransaction.with(new TestController()).tag("1"));
router.pushController(RouterTransaction.with(new TestController()).tag("2"));
router.pushController(RouterTransaction.with(new TestController()).tag("3"));
String tag;
if (step == 1) {
tag = "1";
} else if (step == 2) {
tag = "2";
} else {
tag = "3";
step = 0;
}
Controller controller = router.getControllerWithTag(tag);
if (controller != null) {
router.popController(controller);
}
router.popToRoot();
}
}
@Test
public void testPopToTag() {
String controller1Tag = "controller1";
@@ -431,4 +459,37 @@ public class RouterTests {
assertEquals(0, router.container.getChildCount());
}
@Test
public void testIsBeingDestroyed() {
final LifecycleListener lifecycleListener = new LifecycleListener() {
@Override
public void preDestroyView(@NonNull Controller controller, @NonNull View view) {
assertTrue(controller.isBeingDestroyed());
}
};
Controller controller1 = new TestController();
Controller controller2 = new TestController();
controller2.addLifecycleListener(lifecycleListener);
router.setRoot(RouterTransaction.with(controller1));
router.pushController(RouterTransaction.with(controller2));
assertFalse(controller1.isBeingDestroyed());
assertFalse(controller2.isBeingDestroyed());
router.popCurrentController();
assertFalse(controller1.isBeingDestroyed());
assertTrue(controller2.isBeingDestroyed());
Controller controller3 = new TestController();
controller3.addLifecycleListener(lifecycleListener);
router.pushController(RouterTransaction.with(controller3));
assertFalse(controller1.isBeingDestroyed());
assertFalse(controller3.isBeingDestroyed());
router.popToRoot();
assertFalse(controller1.isBeingDestroyed());
assertTrue(controller3.isBeingDestroyed());
}
}
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -1,10 +1,10 @@
package com.bluelinelabs.conductor.util;
import android.os.Bundle;
import android.support.annotation.IdRes;
import androidx.annotation.IdRes;
import org.robolectric.Robolectric;
import org.robolectric.util.ActivityController;
import org.robolectric.android.controller.ActivityController;
public class ActivityProxy {
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.util;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -34,6 +34,10 @@ public class MockChangeHandler extends ControllerChangeHandler {
return new MockChangeHandler(false, null, null);
}
public static MockChangeHandler noRemoveViewOnPushHandler(String tag) {
return new MockChangeHandler(false, tag, null);
}
public static MockChangeHandler listeningChangeHandler(@NonNull ChangeHandlerListener listener) {
return new MockChangeHandler(true, null, listener);
}
@@ -48,6 +52,7 @@ public class MockChangeHandler extends ControllerChangeHandler {
private MockChangeHandler(boolean removesFromViewOnPush, String tag, ChangeHandlerListener listener) {
this.removesFromViewOnPush = removesFromViewOnPush;
this.tag = tag;
if (listener == null) {
this.listener = new ChangeHandlerListener() { };
@@ -3,8 +3,8 @@ package com.bluelinelabs.conductor.util;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -16,7 +16,7 @@ public class ViewUtils {
public static void reportAttached(View view, boolean attached, boolean propogateToChildren) {
if (view instanceof AttachFakingFrameLayout) {
((AttachFakingFrameLayout)view).setAttached(attached, false);
((AttachFakingFrameLayout) view).setAttached(attached, false);
}
List<OnAttachStateChangeListener> listeners = getAttachStateListeners(view);
@@ -44,7 +44,7 @@ public class ViewUtils {
}
if (propogateToChildren && view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)view;
ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
reportAttached(viewGroup.getChildAt(i), attached, true);
+15 -13
View File
@@ -2,7 +2,6 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
@@ -31,20 +30,23 @@ android {
}
dependencies {
compile rootProject.ext.supportV4
compile rootProject.ext.supportDesign
implementation rootProject.ext.androidxAppCompat
implementation rootProject.ext.material
implementation rootProject.ext.archComponentsLiveDataCore // Fix duplicate classes
annotationProcessor rootProject.ext.butterknifeCompiler
compile rootProject.ext.butterknife
compile rootProject.ext.picasso
implementation rootProject.ext.butterknife
implementation rootProject.ext.picasso
compile project(':conductor-modules:support')
compile project(':conductor-modules:rxlifecycle')
compile project(':conductor-modules:rxlifecycle2')
compile project(':conductor-modules:autodispose')
compile project(':conductor-modules:arch-components-lifecycle')
implementation project(':conductor')
implementation project(':conductor-modules:support')
implementation project(':conductor-modules:rxlifecycle')
implementation project(':conductor-modules:rxlifecycle2')
implementation project(':conductor-modules:autodispose')
implementation project(':conductor-modules:arch-components-lifecycle')
debugCompile rootProject.ext.leakCanary
releaseCompile rootProject.ext.leakCanaryNoOp
testCompile rootProject.ext.leakCanaryNoOp
debugImplementation rootProject.ext.leakCanary
releaseImplementation rootProject.ext.leakCanaryNoOp
testImplementation rootProject.ext.leakCanaryNoOp
}
@@ -1,6 +1,6 @@
package com.bluelinelabs.conductor.demo;
import android.support.v7.app.ActionBar;
import androidx.appcompat.app.ActionBar;
public interface ActionBarProvider {
ActionBar getSupportActionBar();
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.demo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Conductor;
@@ -2,36 +2,110 @@ package com.bluelinelabs.conductor.demo.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.annotation.NonNull;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.transition.ArcMotion;
import android.transition.ChangeBounds;
import android.transition.ChangeClipBounds;
import android.transition.ChangeTransform;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.Transition.TransitionListener;
import android.transition.TransitionSet;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandler;
import com.bluelinelabs.conductor.changehandler.SharedElementTransitionChangeHandler;
import com.bluelinelabs.conductor.internal.TransitionUtils;
import java.util.ArrayList;
import java.util.Collections;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ArcFadeMoveChangeHandler extends TransitionChangeHandler {
public class ArcFadeMoveChangeHandler extends SharedElementTransitionChangeHandler {
private static final String KEY_SHARED_ELEMENT_NAMES = "ArcFadeMoveChangeHandler.sharedElementNames";
private final ArrayList<String> sharedElementNames = new ArrayList<>();
public ArcFadeMoveChangeHandler() { }
@Override
@NonNull
protected Transition getTransition(@NonNull ViewGroup container, View from, View to, boolean isPush) {
TransitionSet transition = new TransitionSet()
.setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
.addTransition(new Fade(Fade.OUT))
.addTransition(new TransitionSet().addTransition(new ChangeBounds()).addTransition(new ChangeClipBounds()).addTransition(new ChangeTransform()))
.addTransition(new Fade(Fade.IN));
public ArcFadeMoveChangeHandler(String... sharedElementNames) {
Collections.addAll(this.sharedElementNames, sharedElementNames);
}
@Override
public void saveToBundle(@NonNull Bundle bundle) {
super.saveToBundle(bundle);
bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames);
}
@Override
public void restoreFromBundle(@NonNull Bundle bundle) {
super.restoreFromBundle(bundle);
sharedElementNames.addAll(bundle.getStringArrayList(KEY_SHARED_ELEMENT_NAMES));
}
@Nullable
@Override
public Transition getExitTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
return new Fade(Fade.OUT);
}
@Nullable
@Override
public Transition getSharedElementTransition(@NonNull ViewGroup container, @Nullable final View from, @Nullable View to, boolean isPush) {
Transition transition = new TransitionSet().addTransition(new ChangeBounds()).addTransition(new ChangeClipBounds()).addTransition(new ChangeTransform());
transition.setPathMotion(new ArcMotion());
// The framework doesn't totally fade out the "from" shared element, so we'll hide it manually once it's safe.
transition.addListener(new TransitionListener() {
@Override
public void onTransitionStart(Transition transition) {
if (from != null) {
for (String name : sharedElementNames) {
View namedView = TransitionUtils.findNamedView(from, name);
if (namedView != null) {
namedView.setVisibility(View.INVISIBLE);
}
}
}
}
@Override
public void onTransitionEnd(Transition transition) { }
@Override
public void onTransitionCancel(Transition transition) { }
@Override
public void onTransitionPause(Transition transition) { }
@Override
public void onTransitionResume(Transition transition) { }
});
return transition;
}
@Nullable
@Override
public Transition getEnterTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
return new Fade(Fade.IN);
}
@Override
public void configureSharedElements(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
for (String name : sharedElementNames) {
addSharedElement(name);
}
}
@Override
public boolean allowTransitionOverlap(boolean isPush) {
return false;
}
}
@@ -6,7 +6,11 @@ import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat;
public class ArcFadeMoveChangeHandlerCompat extends TransitionChangeHandlerCompat {
public ArcFadeMoveChangeHandlerCompat() {
super(new ArcFadeMoveChangeHandler(), new FadeChangeHandler());
super();
}
public ArcFadeMoveChangeHandlerCompat(String... transitionNames) {
super(new ArcFadeMoveChangeHandler(transitionNames), new FadeChangeHandler());
}
}
@@ -4,7 +4,7 @@ import android.animation.Animator;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
@@ -4,7 +4,7 @@ import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.os.Build;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
@@ -0,0 +1,85 @@
package com.bluelinelabs.conductor.demo.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.transition.ChangeBounds;
import android.transition.ChangeClipBounds;
import android.transition.ChangeTransform;
import android.transition.Explode;
import android.transition.Slide;
import android.transition.Transition;
import android.transition.TransitionSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.changehandler.SharedElementTransitionChangeHandler;
import java.util.ArrayList;
import java.util.List;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class CityGridSharedElementTransitionChangeHandler extends SharedElementTransitionChangeHandler {
private static final String KEY_WAIT_FOR_TRANSITION_NAMES = "CityGridSharedElementTransitionChangeHandler.names";
private final ArrayList<String> names;
public CityGridSharedElementTransitionChangeHandler() {
names = new ArrayList<>();
}
public CityGridSharedElementTransitionChangeHandler(@NonNull List<String> waitForTransitionNames) {
names = new ArrayList<>(waitForTransitionNames);
}
@Override
public void saveToBundle(@NonNull Bundle bundle) {
bundle.putStringArrayList(KEY_WAIT_FOR_TRANSITION_NAMES, names);
}
@Override
public void restoreFromBundle(@NonNull Bundle bundle) {
List<String> savedNames = bundle.getStringArrayList(KEY_WAIT_FOR_TRANSITION_NAMES);
if (savedNames != null) {
names.addAll(savedNames);
}
}
@Nullable
public Transition getExitTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
if (isPush) {
return new Explode();
} else {
return new Slide(Gravity.BOTTOM);
}
}
@Override
@Nullable
public Transition getSharedElementTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
return new TransitionSet().addTransition(new ChangeBounds()).addTransition(new ChangeClipBounds()).addTransition(new ChangeTransform());
}
@Override
@Nullable
public Transition getEnterTransition(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
if (isPush) {
return new Slide(Gravity.BOTTOM);
} else {
return new Explode();
}
}
@Override
public void configureSharedElements(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush) {
for (String name : names) {
addSharedElement(name);
waitOnSharedElementNamed(name);
}
}
}
@@ -2,9 +2,9 @@ package com.bluelinelabs.conductor.demo.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionSet;
@@ -23,7 +23,7 @@ public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler
private View dialogBackground;
private ViewGroup fabParent;
@NonNull @Override
@Override @NonNull
protected Transition getTransition(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, boolean isPush) {
Transition backgroundFade = new Fade();
backgroundFade.addTarget(R.id.dialog_background);
@@ -3,7 +3,7 @@ package com.bluelinelabs.conductor.demo.changehandler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
@@ -3,7 +3,7 @@ package com.bluelinelabs.conductor.demo.changehandler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
@@ -1,164 +0,0 @@
package com.bluelinelabs.conductor.demo.changehandler;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.transition.Transition;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import java.util.ArrayList;
import java.util.List;
/**
* A TransitionChangeHandler that will wait for views with the passed transition names to be fully laid out
* before executing. An OnPreDrawListener will be added to the "to" view, then to all of its subviews that
* match the transaction names we're interested in. Once all of the views are fully ready, the "to" view
* is set to invisible so that it'll fade in nicely, and the views that we want to use as shared elements
* are removed from their containers, then immediately re-added within the beginDelayedTransition call so
* the system picks them up as shared elements.
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class SharedElementDelayingChangeHandler extends ArcFadeMoveChangeHandler {
private static final String KEY_WAIT_FOR_TRANSITION_NAMES = "SharedElementDelayingChangeHandler.waitForTransitionNames";
private final ArrayList<String> waitForTransitionNames;
private final ArrayList<ViewParentPair> removedViews = new ArrayList<>();
private OnPreDrawListener onPreDrawListener;
public SharedElementDelayingChangeHandler() {
waitForTransitionNames = new ArrayList<>();
}
public SharedElementDelayingChangeHandler(@NonNull List<String> waitForTransitionNames) {
this.waitForTransitionNames = new ArrayList<>(waitForTransitionNames);
}
@Override
public void prepareForTransition(@NonNull final ViewGroup container, @Nullable View from, @Nullable final View to, @NonNull Transition transition, boolean isPush, @NonNull final OnTransitionPreparedListener onTransitionPreparedListener) {
if (to != null && to.getParent() == null && waitForTransitionNames.size() > 0) {
onPreDrawListener = new OnPreDrawListener() {
boolean addedSubviewListeners;
@Override
public boolean onPreDraw() {
List<View> foundViews = new ArrayList<>();
for (String transitionName : waitForTransitionNames) {
foundViews.add(getViewWithTransitionName(to, transitionName));
}
if (!foundViews.contains(null) && !addedSubviewListeners) {
addedSubviewListeners = true;
for (final View view : foundViews) {
view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
waitForTransitionNames.remove(view.getTransitionName());
ViewGroup parent = (ViewGroup)view.getParent();
removedViews.add(new ViewParentPair(view, parent));
parent.removeView(view);
if (waitForTransitionNames.size() == 0) {
to.getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener);
onPreDrawListener = null;
to.setVisibility(View.INVISIBLE);
onTransitionPreparedListener.onPrepared();
}
return true;
}
});
}
}
return false;
}
};
to.getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);
container.addView(to);
} else {
onTransitionPreparedListener.onPrepared();
}
}
@Override
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
if (to != null) {
to.setVisibility(View.VISIBLE);
for (ViewParentPair removedView : removedViews) {
removedView.parent.addView(removedView.view);
}
removedViews.clear();
}
super.executePropertyChanges(container, from, to, transition, isPush);
}
@Override
public void saveToBundle(@NonNull Bundle bundle) {
bundle.putStringArrayList(KEY_WAIT_FOR_TRANSITION_NAMES, waitForTransitionNames);
}
@Override
public void restoreFromBundle(@NonNull Bundle bundle) {
List<String> savedNames = bundle.getStringArrayList(KEY_WAIT_FOR_TRANSITION_NAMES);
if (savedNames != null) {
waitForTransitionNames.addAll(savedNames);
}
}
@Override
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
super.onAbortPush(newHandler, newTop);
removedViews.clear();
}
@Nullable
View getViewWithTransitionName(@NonNull View view, @NonNull String transitionName) {
if (transitionName.equals(view.getTransitionName())) {
return view;
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)view;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View viewWithTransitionName = getViewWithTransitionName(viewGroup.getChildAt(i), transitionName);
if (viewWithTransitionName != null) {
return viewWithTransitionName;
}
}
}
return null;
}
private static class ViewParentPair {
View view;
ViewGroup parent;
public ViewParentPair(View view, ViewGroup parent) {
this.view = view;
this.parent = parent;
}
}
}
@@ -31,9 +31,9 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.core.content.ContextCompat;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.view.View;
@@ -1,10 +1,12 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.OnLifecycleEvent;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import androidx.lifecycle.Lifecycle.Event;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -35,47 +37,28 @@ public class ArchLifecycleController extends LifecycleController {
private boolean hasExited;
public ArchLifecycleController() {
LifecycleObserver lifecycleObserver = new LifecycleObserver() {
@OnLifecycleEvent(Event.ON_CREATE)
void onCreate() {
Log.d(TAG, "LifecycleObserver onCreate() called");
Log.i(TAG, "Conductor: Constructor called");
getLifecycle().addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Event.ON_ANY)
void onLifecycleEvent(@NonNull LifecycleOwner source, @NonNull Event event) {
Log.d(TAG, "Lifecycle: " + source.getClass().getSimpleName() + " emitted event " + event + " and is now in state " + source.getLifecycle().getCurrentState());
}
});
@OnLifecycleEvent(Event.ON_START)
void onStart() {
Log.d(TAG, "LifecycleObserver onStart() called");
}
Log.d(TAG, "Lifecycle: " + getClass().getSimpleName() + " is now in state " + getLifecycle().getCurrentState());
}
@OnLifecycleEvent(Event.ON_RESUME)
void onResume() {
Log.d(TAG, "LifecycleObserver onResume() called");
}
@OnLifecycleEvent(Event.ON_PAUSE)
void onPause() {
Log.d(TAG, "LifecycleObserver onPause() called");
}
@OnLifecycleEvent(Event.ON_STOP)
void onStop() {
Log.d(TAG, "LifecycleObserver onStop() called");
}
@OnLifecycleEvent(Event.ON_DESTROY)
void onDestroy() {
Log.d(TAG, "LifecycleObserver onDestroy() called");
}
};
Log.i(TAG, "constructor called");
getLifecycle().addObserver(lifecycleObserver);
@Override
protected void onContextAvailable(@NonNull Context context) {
Log.i(TAG, "Conductor: onContextAvailable() called");
super.onContextAvailable(context);
}
@NonNull
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
Log.i(TAG, "onCreateView() called");
Log.i(TAG, "Conductor: onCreateView() called");
View view = inflater.inflate(R.layout.controller_lifecycle, container, false);
view.setBackgroundColor(ContextCompat.getColor(container.getContext(), R.color.orange_300));
@@ -88,36 +71,38 @@ public class ArchLifecycleController extends LifecycleController {
@Override
protected void onAttach(@NonNull View view) {
Log.i(TAG, "Conductor: onAttach() called");
super.onAttach(view);
Log.i(TAG, "onAttach() called");
(((ActionBarProvider) getActivity()).getSupportActionBar()).setTitle("Arch Components Lifecycle Demo");
}
@Override
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
protected void onDetach(@NonNull View view) {
Log.i(TAG, "Conductor: onDetach() called");
super.onDetach(view);
}
Log.i(TAG, "onDestroyView() called");
@Override
protected void onDestroyView(@NonNull View view) {
Log.i(TAG, "Conductor: onDestroyView() called");
super.onDestroyView(view);
unbinder.unbind();
unbinder = null;
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
Log.i(TAG, "onDetach() called");
protected void onContextUnavailable() {
Log.i(TAG, "Conductor: onContextUnavailable() called");
super.onContextUnavailable();
}
@Override
public void onDestroy() {
Log.i(TAG, "Conductor: onDestroy() called");
super.onDestroy();
Log.i(TAG, "onDestroy() called");
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -17,8 +17,8 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.ActionBarProvider;
import com.bluelinelabs.conductor.demo.DemoApplication;
import com.bluelinelabs.conductor.demo.R;
import com.uber.autodispose.LifecycleScopeProvider;
import com.uber.autodispose.ObservableScoper;
import com.uber.autodispose.AutoDispose;
import com.uber.autodispose.lifecycle.LifecycleScopeProvider;
import java.util.concurrent.TimeUnit;
@@ -50,7 +50,7 @@ public class AutodisposeController extends Controller {
Log.i(TAG, "Disposing from constructor");
}
})
.to(new ObservableScoper<Long>(scopeProvider))
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) {
@@ -77,7 +77,7 @@ public class AutodisposeController extends Controller {
Log.i(TAG, "Disposing from onCreateView()");
}
})
.to(new ObservableScoper<Long>(scopeProvider))
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) {
@@ -103,7 +103,7 @@ public class AutodisposeController extends Controller {
Log.i(TAG, "Disposing from onAttach()");
}
})
.to(new ObservableScoper<Long>(scopeProvider))
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) {
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -1,11 +1,11 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -145,10 +145,8 @@ public class CityDetailController extends BaseController {
imageView.setImageResource(imageDrawableRes);
textView.setText(title);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
imageView.setTransitionName(imageTransitionName);
textView.setTransitionName(textViewTransitionName);
}
ViewCompat.setTransitionName(imageView, imageTransitionName);
ViewCompat.setTransitionName(textView, textViewTransitionName);
}
}
@@ -1,13 +1,13 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.graphics.PorterDuff.Mode;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -18,7 +18,7 @@ import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.SharedElementDelayingChangeHandler;
import com.bluelinelabs.conductor.demo.changehandler.CityGridSharedElementTransitionChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
@@ -79,10 +79,8 @@ public class CityGridController extends BaseController {
tvTitle.setText(title);
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), dotColor), Mode.SRC_ATOP);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tvTitle.setTransitionName(getResources().getString(R.string.transition_tag_title_indexed, fromPosition));
imgDot.setTransitionName(getResources().getString(R.string.transition_tag_dot_indexed, fromPosition));
}
ViewCompat.setTransitionName(tvTitle, getResources().getString(R.string.transition_tag_title_indexed, fromPosition));
ViewCompat.setTransitionName(imgDot, getResources().getString(R.string.transition_tag_dot_indexed, fromPosition));
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new GridLayoutManager(view.getContext(), 2));
@@ -103,8 +101,8 @@ public class CityGridController extends BaseController {
names.add(titleTransitionName);
getRouter().pushController(RouterTransaction.with(new CityDetailController(model.drawableRes, model.title))
.pushChangeHandler(new TransitionChangeHandlerCompat(new SharedElementDelayingChangeHandler(names), new FadeChangeHandler()))
.popChangeHandler(new TransitionChangeHandlerCompat(new SharedElementDelayingChangeHandler(names), new FadeChangeHandler())));
.pushChangeHandler(new TransitionChangeHandlerCompat(new CityGridSharedElementTransitionChangeHandler(names), new FadeChangeHandler()))
.popChangeHandler(new TransitionChangeHandlerCompat(new CityGridSharedElementTransitionChangeHandler(names), new FadeChangeHandler())));
}
class CityGridAdapter extends RecyclerView.Adapter<CityGridAdapter.ViewHolder> {
@@ -148,10 +146,8 @@ public class CityGridController extends BaseController {
imageView.setImageResource(item.drawableRes);
textView.setText(item.title);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
textView.setTransitionName(getResources().getString(R.string.transition_tag_title_named, model.title));
imageView.setTransitionName(getResources().getString(R.string.transition_tag_image_named, model.title));
}
ViewCompat.setTransitionName(textView, getResources().getString(R.string.transition_tag_title_named, model.title));
ViewCompat.setTransitionName(imageView, getResources().getString(R.string.transition_tag_image_named, model.title));
}
@OnClick(R.id.row_root)
@@ -2,7 +2,7 @@ package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
@@ -2,7 +2,7 @@ package com.bluelinelabs.conductor.demo.controllers;
import android.annotation.TargetApi;
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -1,11 +1,11 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.graphics.PorterDuff.Mode;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -3,13 +3,13 @@ package com.bluelinelabs.conductor.demo.controllers;
import android.content.Intent;
import android.graphics.PorterDuff.Mode;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -31,7 +31,7 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandler;
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.changehandler.FabToDialogTransitionChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
@@ -192,9 +192,12 @@ public class HomeController extends BaseController {
.popChangeHandler(new FadeChangeHandler()));
break;
case SHARED_ELEMENT_TRANSITIONS:
String titleSharedElementName = getResources().getString(R.string.transition_tag_title_indexed, position);
String dotSharedElementName = getResources().getString(R.string.transition_tag_dot_indexed, position);
getRouter().pushController(RouterTransaction.with(new CityGridController(model.title, model.color, position))
.pushChangeHandler(new TransitionChangeHandlerCompat(new ArcFadeMoveChangeHandler(), new FadeChangeHandler()))
.popChangeHandler(new TransitionChangeHandlerCompat(new ArcFadeMoveChangeHandler(), new FadeChangeHandler())));
.pushChangeHandler(new ArcFadeMoveChangeHandlerCompat(titleSharedElementName, dotSharedElementName))
.popChangeHandler(new ArcFadeMoveChangeHandlerCompat(titleSharedElementName, dotSharedElementName)));
break;
case DRAG_DISMISS:
getRouter().pushController(RouterTransaction.with(new DragDismissController())
@@ -262,10 +265,8 @@ public class HomeController extends BaseController {
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), item.color), Mode.SRC_ATOP);
this.position = position;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tvTitle.setTransitionName(getResources().getString(R.string.transition_tag_title_indexed, position));
imgDot.setTransitionName(getResources().getString(R.string.transition_tag_dot_indexed, position));
}
ViewCompat.setTransitionName(tvTitle, getResources().getString(R.string.transition_tag_title_indexed, position));
ViewCompat.setTransitionName(imgDot, getResources().getString(R.string.transition_tag_dot_indexed, position));
}
@OnClick(R.id.row_root)
@@ -1,11 +1,11 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -1,6 +1,6 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -1,8 +1,8 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import androidx.annotation.NonNull;
import com.google.android.material.tabs.TabLayout;
import androidx.viewpager.widget.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -1,7 +1,7 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

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