Compare commits

...

8 Commits

Author SHA1 Message Date
EricKuck 8a8622c261 Fixed issue with VP2 routers potentially restoring incorrectly 2021-02-05 13:04:37 -06:00
Eric Kuck 6820aa7d6a Convert demo app to Kotlin w/ ViewBinding (#635) 2020-12-22 13:09:57 -06:00
EricKuck 9ce27e4dee Added proguard rules to keep empty constructors 2020-12-22 11:49:40 -06:00
Eric Kuck 3c8ad0a833 Switch to GitHub Actions for CI 2020-12-21 14:53:11 -06:00
EricKuck a720ac57e8 Move env vars out of travis config 2020-12-21 11:28:31 -06:00
EricKuck 7d6901389b Always attempt to restore child controllers, even if the views might not exist anymore
Likely fixes #631, #632
2020-11-30 22:43:19 -06:00
EricKuck e54e88bf0d Ensure VP2 adapter saves visible controller instance states
Fixes #634
2020-11-30 22:37:00 -06:00
EricKuck 010117603c Ensure all views are removed when VP2 pages are detached 2020-11-06 10:25:49 -06:00
82 changed files with 2169 additions and 2985 deletions
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
#
# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo.
#
# Adapted from https://coderwall.com/p/9b_lfq and
# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
SLUG="bluelinelabs/Conductor"
JDK="oraclejdk8"
BRANCH="develop"
set -e
if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then
echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'."
elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then
echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'."
elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
echo "Skipping snapshot deployment: was pull request."
elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'."
else
echo "Deploying snapshot..."
./gradlew clean uploadArchives
echo "Snapshot deployed!"
fi
+36
View File
@@ -0,0 +1,36 @@
name: Test & Publish
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
run: ./gradlew test
publish:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
needs: test
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
run: ./gradlew clean uploadArchives
env:
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
-32
View File
@@ -1,32 +0,0 @@
language: android
before_install:
- mkdir "$ANDROID_HOME/licenses" || true
- echo "\n24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
- echo "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
- yes | sdkmanager --update
script:
- ./gradlew test
jdk:
- oraclejdk8
branches:
only:
- develop
- master
after_success:
- .buildscript/deploy_snapshot.sh
cache:
directories:
- $HOME/.gradle
sudo: false
env:
global:
- 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=
+3 -1
View File
@@ -2,13 +2,15 @@ buildscript {
apply from: rootProject.file('dependencies.gradle')
repositories {
jcenter()
mavenCentral()
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$agpVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.vanniktech:gradle-maven-publish-plugin:$mvnPublishVersion"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
}
}
@@ -32,9 +32,9 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
private final Controller host;
private int maxPagesToStateSave = Integer.MAX_VALUE;
private Map<Integer, String> tags = new HashMap<>();
private final Map<Integer, String> tags = new HashMap<>();
private SparseArray<Bundle> savedPages = new SparseArray<>();
private SparseArray<Router> visibleRouters = new SparseArray<>();
private final SparseArray<Router> visibleRouters = new SparseArray<>();
private ArrayList<Integer> savedPageHistory = new ArrayList<>();
private Router currentPrimaryRouter;
@@ -103,7 +103,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Router router = (Router)object;
Bundle savedState = new Bundle();
@@ -121,8 +121,8 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Router router = (Router)object;
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Router router = (Router) object;
if (router != currentPrimaryRouter) {
if (currentPrimaryRouter != null) {
for (RouterTransaction transaction : currentPrimaryRouter.getBackstack()) {
@@ -139,7 +139,7 @@ public abstract class RouterPagerAdapter extends PagerAdapter {
}
@Override
public boolean isViewFromObject(View view, Object object) {
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
Router router = (Router)object;
final List<RouterTransaction> backstack = router.getBackstack();
for (RouterTransaction transaction : backstack) {
@@ -48,8 +48,7 @@ abstract class RouterStateAdapter(private val host: Controller) :
}
private fun inferViewPager(recyclerView: RecyclerView): ViewPager2 {
return recyclerView.parent as? ViewPager2 ?:
error("Expected ViewPager2 instance. Got: ${recyclerView.parent}")
return recyclerView.parent as? ViewPager2 ?: error("Expected ViewPager2 instance. Got: ${recyclerView.parent}")
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
@@ -89,6 +88,10 @@ abstract class RouterStateAdapter(private val host: Controller) :
super.onViewDetachedFromWindow(holder)
detachRouter(holder)
// Controller has fully detached and destroyed its view reference by now. Remove the leftover
// view from the container.
holder.container.removeAllViews()
}
override fun onViewRecycled(holder: RouterViewHolder) {
@@ -115,6 +118,18 @@ abstract class RouterStateAdapter(private val host: Controller) :
}
override fun saveState(): Parcelable {
// Ensure all visible pages are saved, starting at the outermost pages and working our way in
val visiblePositions = (0 until visibleRouters.size()).map { visibleRouters.keyAt(it) }.toMutableList()
while (visiblePositions.isNotEmpty()) {
val lastPosition = visiblePositions.removeAt(visiblePositions.lastIndex)
savePage(getItemId(lastPosition), visibleRouters[lastPosition])
if (visiblePositions.isNotEmpty()) {
val firstPosition = visiblePositions.removeAt(0)
savePage(getItemId(firstPosition), visibleRouters[firstPosition])
}
}
return SavedState(
savedPagesKeys = (0 until savedPages.size()).map { savedPages.keyAt(it) },
savedPagesValues = (0 until savedPages.size()).map { savedPages.valueAt(it) },
@@ -138,11 +153,18 @@ abstract class RouterStateAdapter(private val host: Controller) :
private fun attachRouter(holder: RouterViewHolder, position: Int) {
val itemId = getItemId(position)
val router = host.getChildRouter(holder.container, "$itemId")
// This should have already been handled by onViewRecycled, but it seems like this wasn't
// always reliably called
if (router != holder.currentRouter) {
holder.currentRouter?.let { host.removeChildRouter(it) }
}
holder.currentRouter = router
holder.currentItemId = itemId
if (!router.hasRootController()) {
val routerSavedState = savedPages[position.toLong()]
val routerSavedState = savedPages[itemId]
if (routerSavedState != null) {
router.restoreInstanceState(routerSavedState)
savedPages.remove(itemId)
@@ -172,14 +194,7 @@ abstract class RouterStateAdapter(private val host: Controller) :
holder.currentRouter?.let { router ->
router.prepareForHostDetach()
val savedState = Bundle()
router.saveInstanceState(savedState)
savedPages.put(holder.currentItemId, savedState)
savedPageHistory.remove(holder.currentItemId)
savedPageHistory.add(holder.currentItemId)
ensurePagesSaved()
savePage(holder.currentItemId, router)
if (visibleRouters[holder.currentItemPosition] == router) {
visibleRouters.remove(holder.currentItemPosition)
@@ -189,10 +204,21 @@ abstract class RouterStateAdapter(private val host: Controller) :
holder.attached = false
}
private fun savePage(itemId: Long, router: Router) {
val savedState = Bundle()
router.saveInstanceState(savedState)
savedPages.put(itemId, savedState)
savedPageHistory.remove(itemId)
savedPageHistory.add(itemId)
ensurePagesSaved()
}
private fun ensurePagesSaved() {
while (savedPages.size() > maxPagesToStateSave) {
val positionToRemove = savedPageHistory.removeAt(0)
savedPages.remove(positionToRemove)
val routerIdToRemove = savedPageHistory.removeAt(0)
savedPages.remove(routerIdToRemove)
}
}
@@ -1099,7 +1099,7 @@ public abstract class Controller {
});
viewAttachHandler.listenForAttach(view);
}
} else if (retainViewMode == RetainViewMode.RETAIN_DETACH) {
} else {
restoreChildControllerHosts();
}
@@ -1111,7 +1111,7 @@ public abstract class Controller {
if (!childRouter.hasHost()) {
View containerView = view.findViewById(childRouter.getHostId());
if (containerView != null && containerView instanceof ViewGroup) {
if (containerView instanceof ViewGroup) {
childRouter.setHostContainer(this, (ViewGroup) containerView);
childRouter.rebindIfNeeded();
}
@@ -0,0 +1,9 @@
-keepclassmembers public class * extends com.bluelinelabs.conductor.Controller {
public <init>();
public <init>(android.os.Bundle);
}
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
public <init>();
}
+19 -9
View File
@@ -1,13 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "com.bluelinelabs.conductor.demo"
minSdkVersion 21
@@ -27,19 +21,35 @@ android {
packagingOptions {
exclude 'META-INF/rxjava.properties'
}
buildFeatures {
viewBinding = true
}
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation rootProject.ext.androidxAppCompat
implementation rootProject.ext.androidxViewPager2
implementation rootProject.ext.material
implementation rootProject.ext.androidxCoreKtx
implementation rootProject.ext.archComponentsLiveDataCore // Fix duplicate classes
annotationProcessor rootProject.ext.butterknifeCompiler
implementation rootProject.ext.butterknife
implementation rootProject.ext.picasso
implementation rootProject.ext.autodisposeKtx
implementation project(':conductor')
implementation project(':conductor-modules:viewpager')
implementation project(':conductor-modules:viewpager2')
@@ -1,7 +0,0 @@
package com.bluelinelabs.conductor.demo;
import androidx.appcompat.app.ActionBar;
public interface ActionBarProvider {
ActionBar getSupportActionBar();
}
@@ -1,18 +0,0 @@
package com.bluelinelabs.conductor.demo;
import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
public class DemoApplication extends Application {
public static RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
}
@@ -0,0 +1,16 @@
package com.bluelinelabs.conductor.demo
import android.app.Application
import com.squareup.leakcanary.LeakCanary
import com.squareup.leakcanary.RefWatcher
class DemoApplication : Application() {
override fun onCreate() {
super.onCreate()
refWatcher = LeakCanary.install(this)
}
companion object {
lateinit var refWatcher: RefWatcher
}
}
@@ -1,45 +0,0 @@
package com.bluelinelabs.conductor.demo;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Conductor;
import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.demo.controllers.HomeController;
import butterknife.BindView;
import butterknife.ButterKnife;
public final class MainActivity extends AppCompatActivity implements ActionBarProvider {
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.controller_container) ViewGroup container;
private Router router;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
router = Conductor.attachRouter(this, container, savedInstanceState);
if (!router.hasRootController()) {
router.setRoot(RouterTransaction.with(new HomeController()));
}
}
@Override
public void onBackPressed() {
if (!router.handleBack()) {
super.onBackPressed();
}
}
}
@@ -0,0 +1,36 @@
package com.bluelinelabs.conductor.demo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.bluelinelabs.conductor.Conductor.attachRouter
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.demo.controllers.HomeController
import com.bluelinelabs.conductor.demo.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity(), ToolbarProvider {
private lateinit var binding: ActivityMainBinding
private lateinit var router: Router
override val toolbar: Toolbar
get() = binding.toolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
router = attachRouter(this, binding.controllerContainer, savedInstanceState)
if (!router.hasRootController()) {
router.setRoot(with(HomeController()))
}
}
override fun onBackPressed() {
if (!router.handleBack()) {
super.onBackPressed()
}
}
}
@@ -0,0 +1,7 @@
package com.bluelinelabs.conductor.demo
import androidx.appcompat.widget.Toolbar
interface ToolbarProvider {
val toolbar: Toolbar
}
@@ -1,140 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Lifecycle.Event;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.archlifecycle.LifecycleController;
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 butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
public class ArchLifecycleController extends LifecycleController {
private static final String TAG = "ArchLifecycleController";
@BindView(R.id.tv_title) TextView tvTitle;
private Unbinder unbinder;
private boolean hasExited;
public ArchLifecycleController() {
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());
}
});
Log.d(TAG, "Lifecycle: " + getClass().getSimpleName() + " is now in state " + getLifecycle().getCurrentState());
}
@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, @Nullable Bundle savedViewState) {
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));
unbinder = ButterKnife.bind(this, view);
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
return view;
}
@Override
protected void onAttach(@NonNull View view) {
Log.i(TAG, "Conductor: onAttach() called");
super.onAttach(view);
(((ActionBarProvider) getActivity()).getSupportActionBar()).setTitle("Arch Components Lifecycle Demo");
}
@Override
protected void onDetach(@NonNull View view) {
Log.i(TAG, "Conductor: onDetach() called");
super.onDetach(view);
}
@Override
protected void onDestroyView(@NonNull View view) {
Log.i(TAG, "Conductor: onDestroyView() called");
super.onDestroyView(view);
unbinder.unbind();
unbinder = null;
}
@Override
protected void onContextUnavailable() {
Log.i(TAG, "Conductor: onContextUnavailable() called");
super.onContextUnavailable();
}
@Override
public void onDestroy() {
Log.i(TAG, "Conductor: onDestroy() called");
super.onDestroy();
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
@OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() {
setRetainViewMode(RetainViewMode.RELEASE_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the Controller's onDetach() and LifecycleObserver's onPause() methods were called, followed by the Controller's onDestroyView() and LifecycleObserver's onStop()."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
@OnClick(R.id.btn_next_retain_view) void onNextWithRetainClicked() {
setRetainViewMode(RetainViewMode.RETAIN_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the Controller's onDetach() and LifecycleObserver's onPause() methods were called."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
@@ -0,0 +1,127 @@
package com.bluelinelabs.conductor.demo.controllers
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.archlifecycle.LifecycleController
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.DemoApplication
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.ToolbarProvider
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
class ArchLifecycleController : LifecycleController() {
private var hasExited = false
init {
Log.i(TAG, "Conductor: Constructor called")
lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
fun onLifecycleEvent(source: LifecycleOwner, event: Lifecycle.Event) {
Log.d(
TAG,
"Lifecycle: " + source.javaClass.simpleName + " emitted event " + event + " and is now in state " + source.lifecycle.currentState
)
}
})
Log.d(TAG, "Lifecycle: " + javaClass.simpleName + " is now in state " + lifecycle.currentState)
}
override fun onContextAvailable(context: Context) {
Log.i(TAG, "Conductor: onContextAvailable() called")
super.onContextAvailable(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
Log.i(TAG, "Conductor: onCreateView() called")
val binding = ControllerLifecycleBinding.inflate(inflater, container, false)
binding.root.setBackgroundColor(ContextCompat.getColor(container.context, R.color.orange_300))
binding.title.text = binding.root.resources.getString(R.string.rxlifecycle_title, TAG)
binding.nextReleaseView.setOnClickListener {
retainViewMode = RetainViewMode.RELEASE_DETACH
router.pushController(
RouterTransaction.with(TextController("Logcat should now report that the Controller's onDetach() and LifecycleObserver's onPause() methods were called, followed by the Controller's onDestroyView() and LifecycleObserver's onStop()."))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
binding.nextRetainView.setOnClickListener {
retainViewMode = RetainViewMode.RETAIN_DETACH
router.pushController(
RouterTransaction.with(TextController("Logcat should now report that the Controller's onDetach() and LifecycleObserver's onPause() methods were called."))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
return binding.root
}
override fun onAttach(view: View) {
Log.i(TAG, "Conductor: onAttach() called")
super.onAttach(view)
(activity as ToolbarProvider).toolbar.title = "Arch Components Lifecycle Demo"
}
override fun onDetach(view: View) {
Log.i(TAG, "Conductor: onDetach() called")
super.onDetach(view)
}
override fun onDestroyView(view: View) {
Log.i(TAG, "Conductor: onDestroyView() called")
super.onDestroyView(view)
}
override fun onContextUnavailable() {
Log.i(TAG, "Conductor: onContextUnavailable() called")
super.onContextUnavailable()
}
override fun onDestroy() {
Log.i(TAG, "Conductor: onDestroy() called")
super.onDestroy()
if (hasExited) {
DemoApplication.refWatcher.watch(this)
}
}
override fun onChangeEnded(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeEnded(changeHandler, changeType)
hasExited = !changeType.isEnter
if (isDestroyed) {
DemoApplication.refWatcher.watch(this)
}
}
companion object {
private const val TAG = "ArchLifecycleController"
}
}
@@ -1,143 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider;
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.AutoDispose;
import com.uber.autodispose.lifecycle.LifecycleScopeProvider;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.Observable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
public class AutodisposeController extends Controller {
private static final String TAG = "AutodisposeController";
@BindView(R.id.tv_title) TextView tvTitle;
private Unbinder unbinder;
private boolean hasExited;
private final LifecycleScopeProvider scopeProvider = ControllerScopeProvider.from(this);
public AutodisposeController() {
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(() -> Log.i(TAG, "Disposing from constructor"))
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
.subscribe(num -> Log.i(TAG, "Started in constructor, running until onDestroy(): " + num));
}
@NonNull
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
Log.i(TAG, "onCreateView() called");
View view = inflater.inflate(R.layout.controller_lifecycle, container, false);
view.setBackgroundColor(ContextCompat.getColor(container.getContext(), R.color.purple_300));
unbinder = ButterKnife.bind(this, view);
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(() -> Log.i(TAG, "Disposing from onCreateView()"))
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
.subscribe(num -> Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num));
return view;
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
Log.i(TAG, "onAttach() called");
(((ActionBarProvider) getActivity()).getSupportActionBar()).setTitle("Autodispose Demo");
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(() -> Log.i(TAG, "Disposing from onAttach()"))
.as(AutoDispose.<Long>autoDisposable((scopeProvider)))
.subscribe(num -> Log.i(TAG, "Started in onAttach(), running until onDetach(): " + num));
}
@Override
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
Log.i(TAG, "onDestroyView() called");
unbinder.unbind();
unbinder = null;
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
Log.i(TAG, "onDetach() called");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy() called");
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
@OnClick(R.id.btn_next_release_view)
void onNextWithReleaseClicked() {
setRetainViewMode(RetainViewMode.RELEASE_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
@OnClick(R.id.btn_next_retain_view)
void onNextWithRetainClicked() {
setRetainViewMode(RetainViewMode.RETAIN_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
@@ -0,0 +1,120 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.DemoApplication
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.ToolbarProvider
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
import com.uber.autodispose.autoDisposable
import io.reactivex.Observable
import java.util.concurrent.TimeUnit
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
class AutodisposeController : Controller() {
private var hasExited = false
private val scopeProvider = ControllerScopeProvider.from(this)
init {
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose { Log.i(TAG, "Disposing from constructor") }
.autoDisposable(scopeProvider)
.subscribe { num: Long ->
Log.i(TAG, "Started in constructor, running until onDestroy(): $num")
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
Log.i(TAG, "onCreateView() called")
val binding = ControllerLifecycleBinding.inflate(inflater, container, false)
binding.title.text = binding.root.resources.getString(R.string.rxlifecycle_title, TAG)
binding.nextReleaseView.setOnClickListener {
retainViewMode = RetainViewMode.RELEASE_DETACH
router.pushController(
with(TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running."))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
binding.nextRetainView.setOnClickListener {
retainViewMode = RetainViewMode.RETAIN_DETACH
router.pushController(
with(TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running."))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose { Log.i(TAG, "Disposing from onCreateView()") }
.autoDisposable(scopeProvider)
.subscribe { num: Long ->
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): $num")
}
return binding.root
}
override fun onAttach(view: View) {
super.onAttach(view)
Log.i(TAG, "onAttach() called")
(activity as ToolbarProvider).toolbar.title = "Autodispose Demo"
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose { Log.i(TAG, "Disposing from onAttach()") }
.autoDisposable(scopeProvider)
.subscribe { num: Long ->
Log.i(TAG, "Started in onAttach(), running until onDetach(): $num")
}
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
Log.i(TAG, "onDestroyView() called")
}
override fun onDetach(view: View) {
super.onDetach(view)
Log.i(TAG, "onDetach() called")
}
public override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "onDestroy() called")
if (hasExited) {
DemoApplication.refWatcher.watch(this)
}
}
override fun onChangeEnded(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeEnded(changeHandler, changeType)
hasExited = !changeType.isEnter
if (isDestroyed) {
DemoApplication.refWatcher.watch(this)
}
}
companion object {
private const val TAG = "AutodisposeController"
}
}
@@ -1,56 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.BindView;
public class ChildController extends BaseController {
private static final String KEY_TITLE = "ChildController.title";
private static final String KEY_BG_COLOR = "ChildController.bgColor";
private static final String KEY_COLOR_IS_RES = "ChildController.colorIsResId";
@BindView(R.id.tv_title) TextView tvTitle;
public ChildController(String title, int backgroundColor, boolean colorIsResId) {
this(new BundleBuilder(new Bundle())
.putString(KEY_TITLE, title)
.putInt(KEY_BG_COLOR, backgroundColor)
.putBoolean(KEY_COLOR_IS_RES, colorIsResId)
.build());
}
public ChildController(Bundle args) {
super(args);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_child, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
tvTitle.setText(getArgs().getString(KEY_TITLE));
int bgColor = getArgs().getInt(KEY_BG_COLOR);
if (getArgs().getBoolean(KEY_COLOR_IS_RES)) {
bgColor = ContextCompat.getColor(getActivity(), bgColor);
}
view.setBackgroundColor(bgColor);
}
}
@@ -0,0 +1,39 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerChildBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class ChildController(args: Bundle) : BaseController(R.layout.controller_child, args) {
private val binding: ControllerChildBinding by viewBinding(ControllerChildBinding::bind)
constructor(title: String, backgroundColor: Int, colorIsResId: Boolean) : this(
bundleOf(
KEY_TITLE to title,
KEY_BG_COLOR to backgroundColor,
KEY_COLOR_IS_RES to colorIsResId
)
)
override fun onViewCreated(view: View) {
binding.title.text = args.getString(KEY_TITLE)
val bgColor = args.getInt(KEY_BG_COLOR)
if (args.getBoolean(KEY_COLOR_IS_RES)) {
view.setBackgroundColor(ContextCompat.getColor(view.context, bgColor))
} else {
view.setBackgroundColor(bgColor)
}
}
companion object {
private const val KEY_TITLE = "ChildController.title"
private const val KEY_BG_COLOR = "ChildController.bgColor"
private const val KEY_COLOR_IS_RES = "ChildController.colorIsResId"
}
}
@@ -1,167 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
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;
import android.widget.ImageView;
import android.widget.TextView;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.BindView;
import butterknife.ButterKnife;
public class CityDetailController extends BaseController {
private static final String KEY_TITLE = "CityDetailController.title";
private static final String KEY_IMAGE = "CityDetailController.image";
private static final String[] LIST_ROWS = new String[] {
"• This is a city.",
"• There's some cool stuff about it.",
"• But really this is just a demo, not a city guide app.",
"• This demo is meant to show some nice transitions.",
"• You should have seen some sweet shared element transitions using the ImageView and the TextView in the \"header\" above.",
"• This transition utilized some callbacks to ensure all the necessary rows in the RecyclerView were laid about before the transition occurred.",
"• Just adding some more lines so it scrolls now...\n\n\n\n\n\n\nThe end."
};
@BindView(R.id.recycler_view) RecyclerView recyclerView;
@DrawableRes private int imageDrawableRes;
private String title;
public CityDetailController(@DrawableRes int imageDrawableRes, String title) {
this(new BundleBuilder(new Bundle())
.putInt(KEY_IMAGE, imageDrawableRes)
.putString(KEY_TITLE, title)
.build());
}
public CityDetailController(Bundle args) {
super(args);
imageDrawableRes = getArgs().getInt(KEY_IMAGE);
title = getArgs().getString(KEY_TITLE);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_city_detail, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
recyclerView.setAdapter(new CityDetailAdapter(LayoutInflater.from(view.getContext()), title, imageDrawableRes, LIST_ROWS, title));
}
@Override
protected String getTitle() {
return title;
}
static class CityDetailAdapter extends RecyclerView.Adapter<CityDetailAdapter.ViewHolder> {
private static final int VIEW_TYPE_HEADER = 0;
private static final int VIEW_TYPE_DETAIL = 1;
private final LayoutInflater inflater;
private final String title;
@DrawableRes private final int imageDrawableRes;
private final String imageViewTransitionName;
private final String textViewTransitionName;
private final String[] details;
public CityDetailAdapter(LayoutInflater inflater, String title, @DrawableRes int imageDrawableRes, String[] details, String transitionNameBase) {
this.inflater = inflater;
this.title = title;
this.imageDrawableRes = imageDrawableRes;
this.details = details;
imageViewTransitionName = inflater.getContext().getResources().getString(R.string.transition_tag_image_named, transitionNameBase);
textViewTransitionName = inflater.getContext().getResources().getString(R.string.transition_tag_title_named, transitionNameBase);
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return VIEW_TYPE_HEADER;
} else {
return VIEW_TYPE_DETAIL;
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_HEADER) {
return new HeaderViewHolder(inflater.inflate(R.layout.row_city_header, parent, false));
} else {
return new DetailViewHolder(inflater.inflate(R.layout.row_city_detail, parent, false));
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (getItemViewType(position) == VIEW_TYPE_HEADER) {
((HeaderViewHolder)holder).bind(imageDrawableRes, title, imageViewTransitionName, textViewTransitionName);
} else {
((DetailViewHolder)holder).bind(details[position - 1]);
}
}
@Override
public int getItemCount() {
return 1 + details.length;
}
static class ViewHolder extends RecyclerView.ViewHolder {
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
static class HeaderViewHolder extends ViewHolder {
@BindView(R.id.image_view) ImageView imageView;
@BindView(R.id.text_view) TextView textView;
public HeaderViewHolder(View itemView) {
super(itemView);
}
void bind(@DrawableRes int imageDrawableRes, String title, String imageTransitionName, String textViewTransitionName) {
imageView.setImageResource(imageDrawableRes);
textView.setText(title);
ViewCompat.setTransitionName(imageView, imageTransitionName);
ViewCompat.setTransitionName(textView, textViewTransitionName);
}
}
static class DetailViewHolder extends ViewHolder {
@BindView(R.id.text_view) TextView textView;
public DetailViewHolder(View itemView) {
super(itemView);
}
void bind(String detail) {
textView.setText(detail);
}
}
}
}
@@ -0,0 +1,121 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.DrawableRes
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerCityDetailBinding
import com.bluelinelabs.conductor.demo.databinding.RowCityDetailBinding
import com.bluelinelabs.conductor.demo.databinding.RowCityHeaderBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class CityDetailController(args: Bundle) : BaseController(R.layout.controller_city_detail, args) {
private val binding: ControllerCityDetailBinding by viewBinding(ControllerCityDetailBinding::bind)
override val title = args.getString(KEY_TITLE)!!
constructor(@DrawableRes image: Int, title: String) : this(
bundleOf(
KEY_TITLE to title,
KEY_IMAGE to image
)
)
override fun onViewCreated(view: View) {
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
binding.recyclerView.adapter = CityDetailAdapter(
inflater = LayoutInflater.from(view.context),
title = title,
imageDrawableRes = args.getInt(KEY_IMAGE),
details = LIST_ROWS,
transitionNameBase = title
)
}
companion object {
private const val KEY_TITLE = "CityDetailController.title"
private const val KEY_IMAGE = "CityDetailController.image"
private val LIST_ROWS = listOf(
"• This is a city.",
"• There's some cool stuff about it.",
"• But really this is just a demo, not a city guide app.",
"• This demo is meant to show some nice transitions.",
"• You should have seen some sweet shared element transitions using the ImageView and the TextView in the \"header\" above.",
"• This transition utilized some callbacks to ensure all the necessary rows in the RecyclerView were laid about before the transition occurred.",
"• Just adding some more lines so it scrolls now...\n\n\n\n\n\n\nThe end."
)
}
class CityDetailAdapter(
private val inflater: LayoutInflater,
private val title: String,
@DrawableRes private val imageDrawableRes: Int,
private val details: List<String>,
private val transitionNameBase: String
) : RecyclerView.Adapter<ViewHolder<*>>() {
override fun getItemViewType(position: Int): Int {
return if (position == 0) VIEW_TYPE_HEADER else VIEW_TYPE_DETAIL
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> {
return if (viewType == VIEW_TYPE_HEADER) {
HeaderViewHolder(RowCityHeaderBinding.inflate(inflater, parent, false))
} else {
DetailViewHolder(RowCityDetailBinding.inflate(inflater, parent, false))
}
}
override fun onBindViewHolder(holder: ViewHolder<*>, position: Int) {
when (holder) {
is HeaderViewHolder -> {
holder.bind(
imageDrawableRes = imageDrawableRes,
title = title,
imageTransitionName = inflater.context.resources.getString(R.string.transition_tag_image_named, transitionNameBase),
textViewTransitionName = inflater.context.resources.getString(R.string.transition_tag_title_named, transitionNameBase)
)
}
is DetailViewHolder -> {
holder.bind(details[position - 1])
}
else -> {
throw IllegalStateException("Invalid view holder class ${holder.javaClass.canonicalName}")
}
}
}
override fun getItemCount() = details.size + 1
companion object {
private const val VIEW_TYPE_HEADER = 0
private const val VIEW_TYPE_DETAIL = 1
}
}
open class ViewHolder<Binding : ViewBinding>(binding: Binding) : RecyclerView.ViewHolder(binding.root)
class HeaderViewHolder(private val binding: RowCityHeaderBinding) : ViewHolder<RowCityHeaderBinding>(binding) {
fun bind(@DrawableRes imageDrawableRes: Int, title: String?, imageTransitionName: String?, textViewTransitionName: String?) {
binding.imageView.setImageResource(imageDrawableRes)
binding.textView.text = title
binding.imageView.transitionName = imageTransitionName
binding.textView.transitionName = textViewTransitionName
}
}
class DetailViewHolder(private val binding: RowCityDetailBinding) : ViewHolder<RowCityDetailBinding>(binding) {
fun bind(detail: String) {
binding.textView.text = detail
}
}
}
@@ -1,168 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.graphics.PorterDuff.Mode;
import android.os.Bundle;
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;
import android.widget.ImageView;
import android.widget.TextView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.CityGridSharedElementTransitionChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class CityGridController extends BaseController {
private static final String KEY_TITLE = "CityGridController.title";
private static final String KEY_DOT_COLOR = "CityGridController.dotColor";
private static final String KEY_FROM_POSITION = "CityGridController.position";
private static final CityModel[] CITY_MODELS = new CityModel[] {
new CityModel(R.drawable.chicago, "Chicago"),
new CityModel(R.drawable.jakarta, "Jakarta"),
new CityModel(R.drawable.london, "London"),
new CityModel(R.drawable.sao_paulo, "Sao Paulo"),
new CityModel(R.drawable.tokyo, "Tokyo")
};
@BindView(R.id.tv_title) TextView tvTitle;
@BindView(R.id.img_dot) ImageView imgDot;
@BindView(R.id.recycler_view) RecyclerView recyclerView;
private String title;
private int dotColor;
private int fromPosition;
public CityGridController(String title, int dotColor, int fromPosition) {
this(new BundleBuilder(new Bundle())
.putString(KEY_TITLE, title)
.putInt(KEY_DOT_COLOR, dotColor)
.putInt(KEY_FROM_POSITION, fromPosition)
.build());
}
public CityGridController(Bundle args) {
super(args);
title = getArgs().getString(KEY_TITLE);
dotColor = getArgs().getInt(KEY_DOT_COLOR);
fromPosition = getArgs().getInt(KEY_FROM_POSITION);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_city_grid, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
tvTitle.setText(title);
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), dotColor), Mode.SRC_ATOP);
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));
recyclerView.setAdapter(new CityGridAdapter(LayoutInflater.from(view.getContext()), CITY_MODELS));
}
@Override
protected String getTitle() {
return "Shared Element Demos";
}
void onModelRowClick(CityModel model) {
String imageTransitionName = getResources().getString(R.string.transition_tag_image_named, model.title);
String titleTransitionName = getResources().getString(R.string.transition_tag_title_named, model.title);
List<String> names = new ArrayList<>();
names.add(imageTransitionName);
names.add(titleTransitionName);
getRouter().pushController(RouterTransaction.with(new CityDetailController(model.drawableRes, model.title))
.pushChangeHandler(new CityGridSharedElementTransitionChangeHandler(names))
.popChangeHandler(new CityGridSharedElementTransitionChangeHandler(names)));
}
class CityGridAdapter extends RecyclerView.Adapter<CityGridAdapter.ViewHolder> {
private final LayoutInflater inflater;
private final CityModel[] items;
public CityGridAdapter(LayoutInflater inflater, CityModel[] items) {
this.inflater = inflater;
this.items = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.row_city_grid, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items[position]);
}
@Override
public int getItemCount() {
return items.length;
}
class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.tv_title) TextView textView;
@BindView(R.id.img_city) ImageView imageView;
private CityModel model;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void bind(CityModel item) {
model = item;
imageView.setImageResource(item.drawableRes);
textView.setText(item.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)
void onRowClick() {
onModelRowClick(model);
}
}
}
private static class CityModel {
@DrawableRes int drawableRes;
String title;
public CityModel(@DrawableRes int drawableRes, String title) {
this.drawableRes = drawableRes;
this.title = title;
}
}
}
@@ -0,0 +1,112 @@
package com.bluelinelabs.conductor.demo.controllers
import android.graphics.PorterDuff
import android.os.Bundle
import android.support.annotation.DrawableRes
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.changehandler.CityGridSharedElementTransitionChangeHandler
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerCityGridBinding
import com.bluelinelabs.conductor.demo.databinding.RowCityGridBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class CityGridController(args: Bundle) : BaseController(R.layout.controller_city_grid, args) {
private val binding: ControllerCityGridBinding by viewBinding(ControllerCityGridBinding::bind)
override val title = "Shared Element Demos"
constructor(title: String?, dotColor: Int, fromPosition: Int) : this(
bundleOf(
KEY_TITLE to title,
KEY_DOT_COLOR to dotColor,
KEY_FROM_POSITION to fromPosition
)
)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.title.text = args.getString(KEY_TITLE)!!
binding.dot.drawable.setColorFilter(ContextCompat.getColor(view.context, args.getInt(KEY_DOT_COLOR)), PorterDuff.Mode.SRC_ATOP)
binding.title.transitionName = view.resources.getString(R.string.transition_tag_title_indexed, args.getInt(KEY_FROM_POSITION))
binding.dot.transitionName = view.resources.getString(R.string.transition_tag_dot_indexed, args.getInt(KEY_FROM_POSITION))
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = GridLayoutManager(view.context, 2)
binding.recyclerView.adapter = CityGridAdapter(LayoutInflater.from(view.context), CITY_MODELS, ::onModelRowClick)
}
private fun onModelRowClick(model: CityModel) {
val names = listOf(
resources!!.getString(R.string.transition_tag_title_named, model.title),
resources!!.getString(R.string.transition_tag_image_named, model.title)
)
router.pushController(
RouterTransaction.with(CityDetailController(model.drawableRes, model.title))
.pushChangeHandler(CityGridSharedElementTransitionChangeHandler(names))
.popChangeHandler(CityGridSharedElementTransitionChangeHandler(names))
)
}
companion object {
private const val KEY_TITLE = "CityGridController.title"
private const val KEY_DOT_COLOR = "CityGridController.dotColor"
private const val KEY_FROM_POSITION = "CityGridController.position"
private val CITY_MODELS = arrayOf(
CityModel(R.drawable.chicago, "Chicago"),
CityModel(R.drawable.jakarta, "Jakarta"),
CityModel(R.drawable.london, "London"),
CityModel(R.drawable.sao_paulo, "Sao Paulo"),
CityModel(R.drawable.tokyo, "Tokyo")
)
}
}
data class CityModel(@DrawableRes val drawableRes: Int, val title: String)
private class CityGridAdapter(
private val inflater: LayoutInflater,
private val items: Array<CityModel>,
private val modelClickListener: (CityModel) -> Unit
) : RecyclerView.Adapter<CityGridAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
RowCityGridBinding.inflate(inflater, parent, false),
modelClickListener
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.size
class ViewHolder(
private val binding: RowCityGridBinding,
private val modelClickListener: (CityModel) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: CityModel) {
binding.image.setImageResource(item.drawableRes)
binding.title.text = item.title
binding.image.transitionName = itemView.resources.getString(R.string.transition_tag_image_named, item.title)
binding.image.transitionName = itemView.resources.getString(R.string.transition_tag_title_named, item.title)
itemView.setOnClickListener { modelClickListener(item) }
}
}
}
@@ -1,60 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.BindView;
import butterknife.OnClick;
public class DialogController extends BaseController {
private static final String KEY_TITLE = "DialogController.title";
private static final String KEY_DESCRIPTION = "DialogController.description";
@BindView(R.id.tv_title) TextView tvTitle;
@BindView(R.id.tv_description) TextView tvDescription;
public DialogController(CharSequence title, CharSequence description) {
this(new BundleBuilder(new Bundle())
.putCharSequence(KEY_TITLE, title)
.putCharSequence(KEY_DESCRIPTION, description)
.build());
}
public DialogController(Bundle args) {
super(args);
}
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_dialog, container, false);
}
@Override
public void onViewBound(@NonNull View view) {
super.onViewBound(view);
tvTitle.setText(getArgs().getCharSequence(KEY_TITLE));
tvDescription.setText(getArgs().getCharSequence(KEY_DESCRIPTION));
tvDescription.setMovementMethod(LinkMovementMethod.getInstance());
}
@OnClick({R.id.dismiss, R.id.dialog_window})
public void dismissDialog() {
getRouter().popController(this);
}
@Override
public boolean handleBack() {
return super.handleBack();
}
}
@@ -0,0 +1,35 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.os.bundleOf
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerDialogBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class DialogController(args: Bundle) : BaseController(R.layout.controller_dialog, args) {
private val binding: ControllerDialogBinding by viewBinding(ControllerDialogBinding::bind)
constructor(title: CharSequence, description: CharSequence) : this(
bundleOf(
KEY_TITLE to title,
KEY_DESCRIPTION to description
)
)
override fun onViewCreated(view: View) {
binding.title.text = args.getCharSequence(KEY_TITLE)
binding.description.text = args.getCharSequence(KEY_DESCRIPTION)
binding.description.movementMethod = LinkMovementMethod.getInstance()
binding.dismiss.setOnClickListener { router.popController(this) }
binding.dialogBackground.setOnClickListener { router.popController(this) }
}
companion object {
private const val KEY_TITLE = "DialogController.title"
private const val KEY_DESCRIPTION = "DialogController.description"
}
}
@@ -1,49 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.annotation.TargetApi;
import android.os.Build.VERSION_CODES;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ScaleFadeChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout;
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout.ElasticDragDismissCallback;
@TargetApi(VERSION_CODES.LOLLIPOP)
public class DragDismissController extends BaseController {
private final ElasticDragDismissCallback dragDismissListener = new ElasticDragDismissCallback() {
@Override
public void onDragDismissed() {
overridePopHandler(new ScaleFadeChangeHandler());
getRouter().popController(DragDismissController.this);
}
};
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_drag_dismiss, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
((ElasticDragDismissFrameLayout)view).addListener(dragDismissListener);
}
@Override
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
((ElasticDragDismissFrameLayout)view).removeListener(dragDismissListener);
}
@Override
protected String getTitle() {
return "Drag to Dismiss";
}
}
@@ -0,0 +1,28 @@
package com.bluelinelabs.conductor.demo.controllers
import android.view.View
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.changehandler.ScaleFadeChangeHandler
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout
import com.bluelinelabs.conductor.demo.widget.ElasticDragDismissFrameLayout.ElasticDragDismissCallback
class DragDismissController : BaseController(R.layout.controller_drag_dismiss) {
override val title = "Drag to Dismiss"
private val dragDismissListener: ElasticDragDismissCallback = object : ElasticDragDismissCallback() {
override fun onDragDismissed() {
overridePopHandler(ScaleFadeChangeHandler())
router.popController(this@DragDismissController)
}
}
override fun onViewCreated(view: View) {
(view as ElasticDragDismissFrameLayout).addListener(dragDismissListener)
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
(view as ElasticDragDismissFrameLayout).removeListener(dragDismissListener)
}
}
@@ -1,147 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.graphics.PorterDuff.Mode;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class ExternalModulesController extends BaseController {
private enum DemoModel {
AUTODISPOSE("Autodispose", R.color.purple_300),
RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.blue_grey_300),
ARCH_LIFECYCLE("Arch Components Lifecycle", R.color.orange_300);
String title;
@ColorRes int color;
DemoModel(String title, @ColorRes int color) {
this.title = title;
this.color = color;
}
}
@BindView(R.id.recycler_view) RecyclerView recyclerView;
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_additional_modules, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
recyclerView.setAdapter(new AdditionalModulesAdapter(LayoutInflater.from(view.getContext()), DemoModel.values()));
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
if (changeType.isEnter) {
setTitle();
}
}
@Override
protected String getTitle() {
return "External Module Demos";
}
void onModelRowClick(DemoModel model) {
switch (model) {
case AUTODISPOSE:
getRouter().pushController(RouterTransaction.with(new AutodisposeController())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case RX_LIFECYCLE_2:
getRouter().pushController(RouterTransaction.with(new RxLifecycle2Controller())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case ARCH_LIFECYCLE:
getRouter().pushController(RouterTransaction.with(new ArchLifecycleController())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
}
}
class AdditionalModulesAdapter extends RecyclerView.Adapter<AdditionalModulesAdapter.ViewHolder> {
private final LayoutInflater inflater;
private final DemoModel[] items;
public AdditionalModulesAdapter(LayoutInflater inflater, DemoModel[] items) {
this.inflater = inflater;
this.items = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.row_home, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items[position]);
}
@Override
public int getItemCount() {
return items.length;
}
class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.tv_title) TextView tvTitle;
@BindView(R.id.img_dot) ImageView imgDot;
private DemoModel model;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void bind(DemoModel item) {
model = item;
tvTitle.setText(item.title);
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), item.color), Mode.SRC_ATOP);
}
@OnClick(R.id.row_root)
void onRowClick() {
onModelRowClick(model);
}
}
}
}
@@ -0,0 +1,90 @@
package com.bluelinelabs.conductor.demo.controllers
import android.graphics.PorterDuff
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerAdditionalModulesBinding
import com.bluelinelabs.conductor.demo.databinding.RowHomeBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class ExternalModulesController : BaseController(R.layout.controller_additional_modules) {
private val binding: ControllerAdditionalModulesBinding by viewBinding(ControllerAdditionalModulesBinding::bind)
override val title = "External Module Demos"
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
binding.recyclerView.adapter = AdditionalModulesAdapter(
LayoutInflater.from(view.context),
ModuleModel.values(),
::onModelRowClick
)
}
private fun onModelRowClick(model: ModuleModel) {
when (model) {
ModuleModel.AUTODISPOSE -> router.pushController(
with(AutodisposeController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
ModuleModel.RX_LIFECYCLE_2 -> router.pushController(
with(RxLifecycle2Controller())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
ModuleModel.ARCH_LIFECYCLE -> router.pushController(
with(ArchLifecycleController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
}
}
private enum class ModuleModel(val title: String, @ColorRes val color: Int) {
AUTODISPOSE("Autodispose", R.color.purple_300),
RX_LIFECYCLE_2("Rx Lifecycle 2", R.color.blue_grey_300),
ARCH_LIFECYCLE("Arch Components Lifecycle", R.color.orange_300);
}
private class AdditionalModulesAdapter(
private val inflater: LayoutInflater,
private val items: Array<ModuleModel>,
private val modelClickListener: (ModuleModel) -> Unit
) : RecyclerView.Adapter<AdditionalModulesAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(RowHomeBinding.inflate(inflater, parent, false), modelClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount(): Int {
return items.size
}
class ViewHolder(
private val binding: RowHomeBinding,
private val modelClickListener: (ModuleModel) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ModuleModel) {
binding.title.text = item.title
binding.dot.drawable.setColorFilter(ContextCompat.getColor(itemView.context, item.color), PorterDuff.Mode.SRC_ATOP)
itemView.setOnClickListener { modelClickListener(item) }
}
}
}
@@ -1,286 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.content.Intent;
import android.graphics.PorterDuff.Mode;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
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 com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandler;
import com.bluelinelabs.conductor.demo.changehandler.FabToDialogTransitionChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class HomeController extends BaseController {
private enum DemoModel {
NAVIGATION("Navigation Demos", R.color.red_300),
TRANSITIONS("Transition Demos", R.color.blue_grey_300),
SHARED_ELEMENT_TRANSITIONS("Shared Element Demos", R.color.purple_300),
CHILD_CONTROLLERS("Child Controllers", R.color.orange_300),
VIEW_PAGER("ViewPager", R.color.green_300),
VIEW_PAGER_2("ViewPager2", R.color.pink_300),
TARGET_CONTROLLER("Target Controller", R.color.deep_orange_300),
MULTIPLE_CHILD_ROUTERS("Multiple Child Routers", R.color.grey_300),
MASTER_DETAIL("Master Detail", R.color.lime_300),
DRAG_DISMISS("Drag Dismiss", R.color.teal_300),
EXTERNAL_MODULES("Bonus Modules", R.color.deep_purple_300);
String title;
@ColorRes int color;
DemoModel(String title, @ColorRes int color) {
this.title = title;
this.color = color;
}
}
private static final String KEY_FAB_VISIBILITY = "HomeController.fabVisibility";
@BindView(R.id.recycler_view) RecyclerView recyclerView;
@BindView(R.id.fab) View fab;
public HomeController() {
setHasOptionsMenu(true);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_home, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
recyclerView.setAdapter(new HomeAdapter(LayoutInflater.from(view.getContext()), DemoModel.values()));
}
@Override
protected void onSaveViewState(@NonNull View view, @NonNull Bundle outState) {
super.onSaveViewState(view, outState);
outState.putInt(KEY_FAB_VISIBILITY, fab.getVisibility());
}
@Override
protected void onRestoreViewState(@NonNull View view, @NonNull Bundle savedViewState) {
super.onRestoreViewState(view, savedViewState);
//noinspection WrongConstant
fab.setVisibility(savedViewState.getInt(KEY_FAB_VISIBILITY));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.home, menu);
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
setOptionsMenuHidden(!changeType.isEnter);
if (changeType.isEnter) {
setTitle();
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.about) {
onFabClicked(false);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected String getTitle() {
return "Conductor Demos";
}
@OnClick(R.id.fab)
public void onFabClicked() {
onFabClicked(true);
}
private void onFabClicked(boolean fromFab) {
SpannableString details = new SpannableString("A small, yet full-featured framework that allows building View-based Android applications");
details.setSpan(new AbsoluteSizeSpan(16, true), 0, details.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
final String url = "https://github.com/bluelinelabs/Conductor";
SpannableString link = new SpannableString(url);
link.setSpan(new URLSpan(url) {
@Override
public void onClick(View widget) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
}, 0, link.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
SpannableStringBuilder description = new SpannableStringBuilder();
description.append(details);
description.append("\n\n");
description.append(link);
ControllerChangeHandler pushHandler = fromFab ? new FabToDialogTransitionChangeHandler() : new FadeChangeHandler(false);
ControllerChangeHandler popHandler = fromFab ? new FabToDialogTransitionChangeHandler() : new FadeChangeHandler();
getRouter()
.pushController(RouterTransaction.with(new DialogController("Conductor", description))
.pushChangeHandler(pushHandler)
.popChangeHandler(popHandler));
}
void onModelRowClick(DemoModel model, int position) {
switch (model) {
case NAVIGATION:
getRouter().pushController(RouterTransaction.with(new NavigationDemoController(0, DisplayUpMode.SHOW_FOR_CHILDREN_ONLY))
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler())
.tag(NavigationDemoController.TAG_UP_TRANSACTION)
);
break;
case TRANSITIONS:
getRouter().pushController(TransitionDemoController.getRouterTransaction(0, this));
break;
case TARGET_CONTROLLER:
getRouter().pushController(
RouterTransaction.with(new TargetDisplayController())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case VIEW_PAGER:
getRouter().pushController(RouterTransaction.with(new ViewPagerController())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case VIEW_PAGER_2:
getRouter().pushController(RouterTransaction.with(new ViewPager2Controller())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case CHILD_CONTROLLERS:
getRouter().pushController(RouterTransaction.with(new ParentController())
.pushChangeHandler(new FadeChangeHandler())
.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 ArcFadeMoveChangeHandler(titleSharedElementName, dotSharedElementName))
.popChangeHandler(new ArcFadeMoveChangeHandler(titleSharedElementName, dotSharedElementName)));
break;
case DRAG_DISMISS:
getRouter().pushController(RouterTransaction.with(new DragDismissController())
.pushChangeHandler(new FadeChangeHandler(false))
.popChangeHandler(new FadeChangeHandler()));
break;
case EXTERNAL_MODULES:
getRouter().pushController(RouterTransaction.with(new ExternalModulesController())
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
break;
case MULTIPLE_CHILD_ROUTERS:
getRouter().pushController(RouterTransaction.with(new MultipleChildRouterController())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
case MASTER_DETAIL:
getRouter().pushController(RouterTransaction.with(new MasterDetailListController())
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
break;
}
}
class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.ViewHolder> {
private final LayoutInflater inflater;
private final DemoModel[] items;
public HomeAdapter(LayoutInflater inflater, DemoModel[] items) {
this.inflater = inflater;
this.items = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.row_home, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(position, items[position]);
}
@Override
public int getItemCount() {
return items.length;
}
class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.tv_title) TextView tvTitle;
@BindView(R.id.img_dot) ImageView imgDot;
private DemoModel model;
private int position;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void bind(int position, DemoModel item) {
model = item;
tvTitle.setText(item.title);
imgDot.getDrawable().setColorFilter(ContextCompat.getColor(getActivity(), item.color), Mode.SRC_ATOP);
this.position = 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)
void onRowClick() {
onModelRowClick(model, position);
}
}
}
}
@@ -0,0 +1,245 @@
package com.bluelinelabs.conductor.demo.controllers
import android.content.Intent
import android.graphics.PorterDuff
import android.net.Uri
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import android.text.style.URLSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.text.buildSpannedString
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandler
import com.bluelinelabs.conductor.demo.changehandler.FabToDialogTransitionChangeHandler
import com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerHomeBinding
import com.bluelinelabs.conductor.demo.databinding.RowHomeBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class HomeController : BaseController(R.layout.controller_home) {
private val binding: ControllerHomeBinding by viewBinding(ControllerHomeBinding::bind)
override val title = "Conductor Demos"
override fun onViewCreated(view: View) {
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
binding.recyclerView.adapter = HomeAdapter(
LayoutInflater.from(view.context),
DemoModel.values(),
::onModelRowClick
)
binding.fab.setOnClickListener { showAboutDialog(fromFab = true) }
}
override fun configureToolbar(toolbar: Toolbar) {
super.configureToolbar(toolbar)
toolbar.setOnMenuItemClickListener {
if (it.itemId == R.id.about) {
showAboutDialog(fromFab = false)
true
} else {
false
}
}
}
override fun configureMenu(toolbar: Toolbar) {
super.configureMenu(toolbar)
toolbar.inflateMenu(R.menu.home)
}
override fun onSaveViewState(view: View, outState: Bundle) {
super.onSaveViewState(view, outState)
outState.putInt(KEY_FAB_VISIBILITY, binding.fab.visibility)
}
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
super.onRestoreViewState(view, savedViewState)
binding.fab.visibility = savedViewState.getInt(KEY_FAB_VISIBILITY)
}
private fun onModelRowClick(model: DemoModel, position: Int) {
when (model) {
DemoModel.NAVIGATION -> {
router.pushController(
RouterTransaction.with(NavigationDemoController(0, DisplayUpMode.SHOW_FOR_CHILDREN_ONLY))
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
.tag(NavigationDemoController.TAG_UP_TRANSACTION)
)
}
DemoModel.TRANSITIONS -> {
router.pushController(TransitionDemoController.getRouterTransaction(0, this))
}
DemoModel.TARGET_CONTROLLER -> {
router.pushController(
RouterTransaction.with(TargetDisplayController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
DemoModel.VIEW_PAGER -> {
router.pushController(
RouterTransaction.with(ViewPagerController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
DemoModel.VIEW_PAGER_2 -> {
router.pushController(
RouterTransaction.with(ViewPager2Controller())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
DemoModel.CHILD_CONTROLLERS -> {
router.pushController(
RouterTransaction.with(ParentController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
DemoModel.SHARED_ELEMENT_TRANSITIONS -> {
val titleSharedElementName = resources!!.getString(R.string.transition_tag_title_indexed, position)
val dotSharedElementName = resources!!.getString(R.string.transition_tag_dot_indexed, position)
router.pushController(
RouterTransaction.with(CityGridController(model.title, model.color, position))
.pushChangeHandler(ArcFadeMoveChangeHandler(titleSharedElementName, dotSharedElementName))
.popChangeHandler(ArcFadeMoveChangeHandler(titleSharedElementName, dotSharedElementName))
)
}
DemoModel.DRAG_DISMISS -> {
router.pushController(
RouterTransaction.with(DragDismissController())
.pushChangeHandler(FadeChangeHandler(false))
.popChangeHandler(FadeChangeHandler())
)
}
DemoModel.EXTERNAL_MODULES -> {
router.pushController(
RouterTransaction.with(ExternalModulesController())
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
DemoModel.MULTIPLE_CHILD_ROUTERS -> {
router.pushController(
RouterTransaction.with(MultipleChildRouterController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
DemoModel.MASTER_DETAIL -> {
router.pushController(
RouterTransaction.with(MasterDetailListController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
}
}
private fun showAboutDialog(fromFab: Boolean) {
val details = SpannableString("A small, yet full-featured framework that allows building View-based Android applications").apply {
setSpan(AbsoluteSizeSpan(16, true), 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
val link = SpannableString(CONDUCTOR_URL).apply {
setSpan(object : URLSpan(CONDUCTOR_URL) {
override fun onClick(widget: View) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}, 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
val description = buildSpannedString {
append(details)
append("\n\n")
append(link)
}
val pushHandler = if (fromFab) FabToDialogTransitionChangeHandler() else FadeChangeHandler(false)
val popHandler = if (fromFab) FabToDialogTransitionChangeHandler() else FadeChangeHandler()
router.pushController(
RouterTransaction.with(DialogController("Conductor", description))
.pushChangeHandler(pushHandler)
.popChangeHandler(popHandler)
)
}
companion object {
private const val KEY_FAB_VISIBILITY = "HomeController.fabVisibility"
private const val CONDUCTOR_URL = "https://github.com/bluelinelabs/Conductor"
}
}
private enum class DemoModel(val title: String, @ColorRes val color: Int) {
NAVIGATION("Navigation Demos", R.color.red_300),
TRANSITIONS("Transition Demos", R.color.blue_grey_300),
SHARED_ELEMENT_TRANSITIONS("Shared Element Demos", R.color.purple_300),
CHILD_CONTROLLERS("Child Controllers", R.color.orange_300),
VIEW_PAGER("ViewPager", R.color.green_300),
VIEW_PAGER_2("ViewPager2", R.color.pink_300),
TARGET_CONTROLLER("Target Controller", R.color.deep_orange_300),
MULTIPLE_CHILD_ROUTERS("Multiple Child Routers", R.color.grey_300),
MASTER_DETAIL("Master Detail", R.color.lime_300),
DRAG_DISMISS("Drag Dismiss", R.color.teal_300),
EXTERNAL_MODULES("Bonus Modules", R.color.deep_purple_300)
}
private class HomeAdapter(
private val inflater: LayoutInflater,
private val items: Array<DemoModel>,
private val modelClickListener: (DemoModel, Int) -> Unit
) : RecyclerView.Adapter<HomeAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
RowHomeBinding.inflate(inflater, parent, false),
modelClickListener
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position, items[position])
}
override fun getItemCount() = items.size
class ViewHolder(
private val binding: RowHomeBinding,
private val modelClickListener: (DemoModel, Int) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int, item: DemoModel) {
binding.title.text = item.title
binding.dot.drawable.setColorFilter(
ContextCompat.getColor(binding.root.context, item.color),
PorterDuff.Mode.SRC_ATOP
)
binding.root.setOnClickListener { modelClickListener(item, position) }
binding.title.transitionName =
binding.root.resources.getString(R.string.transition_tag_title_indexed, position)
binding.dot.transitionName =
binding.root.resources.getString(R.string.transition_tag_dot_indexed, position)
}
}
}
@@ -1,158 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
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;
import android.widget.TextView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MasterDetailListController extends BaseController {
private static final String KEY_SELECTED_INDEX = "MasterDetailListController.selectedIndex";
public enum DetailItemModel {
ONE("Item 1", "This is a quick demo of master/detail flow using Conductor. In portrait mode you'll see a standard list. In landscape, you'll see a two-pane layout.", R.color.green_300),
TWO("Item 2", "This is another item.", R.color.cyan_300),
THREE("Item 3", "Wow, a 3rd item!", R.color.deep_purple_300);
String title;
String detail;
int backgroundColor;
DetailItemModel(String title, String detail, int backgroundColor) {
this.title = title;
this.detail = detail;
this.backgroundColor = backgroundColor;
}
}
@BindView(R.id.recycler_view) RecyclerView recyclerView;
@Nullable @BindView(R.id.detail_container) ViewGroup detailContainer;
private int selectedIndex;
private boolean twoPaneView;
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_master_detail_list, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext()));
recyclerView.setAdapter(new DetailItemAdapter(LayoutInflater.from(view.getContext()), DetailItemModel.values()));
twoPaneView = (detailContainer != null);
if (twoPaneView) {
onRowSelected(selectedIndex);
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_SELECTED_INDEX, selectedIndex);
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
selectedIndex = savedInstanceState.getInt(KEY_SELECTED_INDEX);
}
@Override
protected String getTitle() {
return "Master/Detail Flow";
}
void onRowSelected(int index) {
selectedIndex = index;
DetailItemModel model = DetailItemModel.values()[index];
ChildController controller = new ChildController(model.detail, model.backgroundColor, true);
if (twoPaneView) {
getChildRouter(detailContainer).setRoot(RouterTransaction.with(controller));
} else {
getRouter().pushController(RouterTransaction.with(controller)
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
class DetailItemAdapter extends RecyclerView.Adapter<DetailItemAdapter.ViewHolder> {
private final LayoutInflater inflater;
private final DetailItemModel[] items;
public DetailItemAdapter(LayoutInflater inflater, DetailItemModel[] items) {
this.inflater = inflater;
this.items = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.row_detail_item, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(items[position], position);
}
@Override
public int getItemCount() {
return items.length;
}
class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.row_root) View root;
@BindView(R.id.tv_title) TextView tvTitle;
private int position;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void bind(DetailItemModel item, int position) {
tvTitle.setText(item.title);
this.position = position;
if (twoPaneView && position == selectedIndex) {
root.setBackgroundColor(ContextCompat.getColor(root.getContext(), R.color.grey_400));
} else {
root.setBackgroundColor(ContextCompat.getColor(root.getContext(), android.R.color.transparent));
}
}
@OnClick(R.id.row_root)
void onRowClick() {
onRowSelected(position);
notifyDataSetChanged();
}
}
}
}
@@ -0,0 +1,128 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerMasterDetailListBinding
import com.bluelinelabs.conductor.demo.databinding.RowDetailItemBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class MasterDetailListController : BaseController(R.layout.controller_master_detail_list) {
private val binding: ControllerMasterDetailListBinding by viewBinding(ControllerMasterDetailListBinding::bind)
override val title = "Master/Detail Flow"
private lateinit var adapter: DetailItemAdapter
private var selectedIndex = 0
private var twoPaneView = false
private val dataModels = mutableListOf(
DetailItemModel(
"Item 1",
"This is a quick demo of master/detail flow using Conductor. In portrait mode you'll see a standard list. In landscape, you'll see a two-pane layout.",
R.color.green_300
),
DetailItemModel(
"Item 2",
"This is another item.",
R.color.cyan_300
),
DetailItemModel(
"Item 3",
"Wow, a 3rd item!",
R.color.deep_purple_300
)
)
override fun onViewCreated(view: View) {
adapter = DetailItemAdapter(LayoutInflater.from(view.context), dataModels, ::onRowSelected)
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(view.context)
binding.recyclerView.adapter = adapter
twoPaneView = binding.detailContainer != null
if (twoPaneView) {
onRowSelected(selectedIndex)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_SELECTED_INDEX, selectedIndex)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
selectedIndex = savedInstanceState.getInt(KEY_SELECTED_INDEX)
}
private fun onRowSelected(index: Int) {
dataModels[selectedIndex] = dataModels[selectedIndex].copy(selected = false)
selectedIndex = index
dataModels[selectedIndex] = dataModels[selectedIndex].copy(selected = true)
val model = dataModels[selectedIndex]
val controller = ChildController(model.detail, model.backgroundColor, true)
if (twoPaneView) {
getChildRouter(binding.detailContainer!!).setRoot(with(controller))
} else {
router.pushController(
with(controller)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
}
companion object {
private const val KEY_SELECTED_INDEX = "MasterDetailListController.selectedIndex"
}
}
data class DetailItemModel(val title: String, val detail: String, val backgroundColor: Int, val selected: Boolean = false)
class DetailItemAdapter(
private val inflater: LayoutInflater,
private val items: List<DetailItemModel>,
private val rowClickListener: (Int) -> Unit
) : RecyclerView.Adapter<DetailItemAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(RowDetailItemBinding.inflate(inflater, parent, false)) { position ->
rowClickListener(position)
notifyDataSetChanged()
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position], position)
}
override fun getItemCount() = items.size
class ViewHolder(
private val binding: RowDetailItemBinding,
private val rowClickListener: (Int) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: DetailItemModel, position: Int) {
binding.title.text = item.title
if (item.selected) {
itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.grey_400))
} else {
itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, android.R.color.transparent))
}
itemView.setOnClickListener { rowClickListener(position) }
}
}
}
@@ -1,42 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.NavigationDemoController.DisplayUpMode;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.BindViews;
public class MultipleChildRouterController extends BaseController {
@BindViews({R.id.container_0, R.id.container_1, R.id.container_2}) ViewGroup[] childContainers;
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_multiple_child_routers, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
for (ViewGroup childContainer : childContainers) {
Router childRouter = getChildRouter(childContainer).setPopsLastView(false);
if (!childRouter.hasRootController()) {
childRouter.setRoot(RouterTransaction.with(new NavigationDemoController(0, DisplayUpMode.HIDE)));
}
}
}
@Override
protected String getTitle() {
return "Child Router Demo";
}
}
@@ -0,0 +1,27 @@
package com.bluelinelabs.conductor.demo.controllers
import android.view.View
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerMultipleChildRoutersBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class MultipleChildRouterController : BaseController(R.layout.controller_multiple_child_routers) {
private val binding: ControllerMultipleChildRoutersBinding by viewBinding(ControllerMultipleChildRoutersBinding::bind)
override val title = "Child Router Demo"
override fun onViewCreated(view: View) {
super.onViewCreated(view)
val childContainers = listOf(binding.container0, binding.container1, binding.container2)
childContainers.forEach { container ->
val childRouter = getChildRouter(container).setPopsLastView(false)
if (!childRouter.hasRootController()) {
childRouter.setRoot(RouterTransaction.with(NavigationDemoController(0, NavigationDemoController.DisplayUpMode.HIDE)))
}
}
}
}
@@ -1,121 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
import butterknife.BindView;
import butterknife.OnClick;
public class NavigationDemoController extends BaseController {
public enum DisplayUpMode {
SHOW,
SHOW_FOR_CHILDREN_ONLY,
HIDE;
private DisplayUpMode getDisplayUpModeForChild() {
switch (this) {
case HIDE:
return HIDE;
default:
return SHOW;
}
}
}
public static final String TAG_UP_TRANSACTION = "NavigationDemoController.up";
private static final String KEY_INDEX = "NavigationDemoController.index";
private static final String KEY_DISPLAY_UP_MODE = "NavigationDemoController.displayUpMode";
@BindView(R.id.tv_title) TextView tvTitle;
private int index;
private DisplayUpMode displayUpMode;
public NavigationDemoController(int index, DisplayUpMode displayUpMode) {
this(new BundleBuilder(new Bundle())
.putInt(KEY_INDEX, index)
.putInt(KEY_DISPLAY_UP_MODE, displayUpMode.ordinal())
.build());
}
public NavigationDemoController(Bundle args) {
super(args);
index = args.getInt(KEY_INDEX);
displayUpMode = DisplayUpMode.values()[args.getInt(KEY_DISPLAY_UP_MODE)];
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_navigation_demo, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
if (displayUpMode != DisplayUpMode.SHOW) {
view.findViewById(R.id.btn_up).setVisibility(View.GONE);
}
view.setBackgroundColor(ColorUtil.getMaterialColor(getResources(), index));
tvTitle.setText(getResources().getString(R.string.navigation_title, index));
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
setButtonsEnabled(true);
}
@Override
protected void onChangeStarted(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeStarted(changeHandler, changeType);
setButtonsEnabled(false);
}
@Override
protected String getTitle() {
return "Navigation Demos";
}
private void setButtonsEnabled(boolean enabled) {
final View view = getView();
if (view != null) {
view.findViewById(R.id.btn_next).setEnabled(enabled);
view.findViewById(R.id.btn_up).setEnabled(enabled);
view.findViewById(R.id.btn_pop_to_root).setEnabled(enabled);
}
}
@OnClick(R.id.btn_next) void onNextClicked() {
getRouter().pushController(RouterTransaction.with(new NavigationDemoController(index + 1, displayUpMode.getDisplayUpModeForChild()))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
@OnClick(R.id.btn_up) void onUpClicked() {
getRouter().popToTag(TAG_UP_TRANSACTION);
}
@OnClick(R.id.btn_pop_to_root) void onPopToRootClicked() {
getRouter().popToRoot();
}
}
@@ -0,0 +1,88 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerNavigationDemoBinding
import com.bluelinelabs.conductor.demo.util.getMaterialColor
import com.bluelinelabs.conductor.demo.util.viewBinding
class NavigationDemoController(args: Bundle) : BaseController(R.layout.controller_navigation_demo, args) {
private val binding: ControllerNavigationDemoBinding by viewBinding(ControllerNavigationDemoBinding::bind)
private val index = args.getInt(KEY_INDEX)
private val displayUpMode = DisplayUpMode.values()[args.getInt(KEY_DISPLAY_UP_MODE)]
override val title = "Navigation Demos"
constructor(index: Int, displayUpMode: DisplayUpMode) : this(
bundleOf(
KEY_INDEX to index,
KEY_DISPLAY_UP_MODE to displayUpMode.ordinal
)
)
override fun onViewCreated(view: View) {
binding.root.setBackgroundColor(view.resources.getMaterialColor(index))
binding.goUp.isVisible = displayUpMode == DisplayUpMode.SHOW
binding.title.text = view.resources.getString(R.string.navigation_title, index)
binding.popToRoot.setOnClickListener { router.popToRoot() }
binding.goUp.setOnClickListener { router.popToTag(TAG_UP_TRANSACTION) }
binding.goToNext.setOnClickListener {
router.pushController(
RouterTransaction.with(NavigationDemoController(index + 1, displayUpMode.displayUpModeForChild))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
}
override fun onChangeEnded(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeEnded(changeHandler, changeType)
if (changeType.isEnter) {
setButtonsEnabled(true)
}
}
override fun onChangeStarted(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeStarted(changeHandler, changeType)
setButtonsEnabled(false)
}
private fun setButtonsEnabled(enabled: Boolean) {
binding.goToNext.isEnabled = enabled
binding.goUp.isEnabled = enabled
binding.popToRoot.isEnabled = enabled
}
companion object {
const val TAG_UP_TRANSACTION = "NavigationDemoController.up"
private const val KEY_INDEX = "NavigationDemoController.index"
private const val KEY_DISPLAY_UP_MODE = "NavigationDemoController.displayUpMode"
}
enum class DisplayUpMode {
SHOW, SHOW_FOR_CHILDREN_ONLY, HIDE;
val displayUpModeForChild: DisplayUpMode
get() = when (this) {
HIDE -> HIDE
else -> SHOW
}
}
}
@@ -1,106 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.ColorUtil;
import java.util.List;
public class ParentController extends BaseController {
private static final int NUMBER_OF_CHILDREN = 5;
private boolean finishing;
private boolean hasShownAll;
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_parent, container, false);
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
if (changeType == ControllerChangeType.PUSH_ENTER) {
addChild(0);
}
}
private void addChild(final int index) {
@IdRes final int frameId = getResources().getIdentifier("child_content_" + (index + 1), "id", getActivity().getPackageName());
final ViewGroup container = (ViewGroup)getView().findViewById(frameId);
final Router childRouter = getChildRouter(container).setPopsLastView(true);
if (!childRouter.hasRootController()) {
ChildController childController = new ChildController("Child Controller #" + index, ColorUtil.getMaterialColor(getResources(), index), false);
childController.addLifecycleListener(new LifecycleListener() {
@Override
public void onChangeEnd(@NonNull Controller controller, @NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
if (!isBeingDestroyed()) {
if (changeType == ControllerChangeType.PUSH_ENTER && !hasShownAll) {
if (index < NUMBER_OF_CHILDREN - 1) {
addChild(index + 1);
} else {
hasShownAll = true;
}
} else if (changeType == ControllerChangeType.POP_EXIT) {
if (index > 0) {
removeChild(index - 1);
} else {
getRouter().popController(ParentController.this);
}
}
}
}
});
childRouter.setRoot(RouterTransaction.with(childController)
.pushChangeHandler(new FadeChangeHandler())
.popChangeHandler(new FadeChangeHandler()));
}
}
private void removeChild(int index) {
List<Router> childRouters = getChildRouters();
if (index < childRouters.size()) {
removeChildRouter(childRouters.get(index));
}
}
@Override
public boolean handleBack() {
int childControllers = 0;
for (Router childRouter : getChildRouters()) {
if (childRouter.hasRootController()) {
childControllers++;
}
}
if (childControllers != NUMBER_OF_CHILDREN || finishing) {
return true;
} else {
finishing = true;
return super.handleBack();
}
}
@Override
protected String getTitle() {
return "Parent/Child Demo";
}
}
@@ -0,0 +1,102 @@
package com.bluelinelabs.conductor.demo.controllers
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerParentBinding
import com.bluelinelabs.conductor.demo.util.getMaterialColor
import com.bluelinelabs.conductor.demo.util.viewBinding
class ParentController : BaseController(R.layout.controller_parent) {
private val binding: ControllerParentBinding by viewBinding(ControllerParentBinding::bind)
override val title = "Parent/Child Demo"
private var hasShownAll = false
private var animatingOut = false
override fun onChangeEnded(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
if (changeType == ControllerChangeType.PUSH_ENTER) {
addChild(0)
}
}
override fun handleBack(): Boolean {
val childControllers = childRouters.filter { it.hasRootController() }.size
return if (childControllers != NUMBER_OF_CHILDREN || animatingOut) {
true
} else {
animatingOut = true
super.handleBack()
}
}
private fun addChild(index: Int) {
val container = when (index) {
0 -> binding.childContent1
1 -> binding.childContent2
2 -> binding.childContent3
3 -> binding.childContent4
4 -> binding.childContent5
else -> throw IllegalStateException("Invalid child index $index")
}
val childRouter = getChildRouter(container).setPopsLastView(true)
if (!childRouter.hasRootController()) {
val child = ChildController(
title = "Child Controller #$index",
backgroundColor = resources!!.getMaterialColor(index),
colorIsResId = false
)
child.addLifecycleListener(object : LifecycleListener() {
override fun onChangeEnd(
controller: Controller,
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
if (!isBeingDestroyed) {
if (changeType == ControllerChangeType.PUSH_ENTER && !hasShownAll) {
if (index < NUMBER_OF_CHILDREN - 1) {
addChild(index + 1)
} else {
hasShownAll = true
}
} else if (changeType == ControllerChangeType.POP_EXIT) {
if (index > 0) {
removeChild(index - 1)
} else {
router.popController(this@ParentController)
}
}
}
}
})
childRouter.setRoot(
RouterTransaction.with(child)
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
}
private fun removeChild(index: Int) {
val childRouters = childRouters
if (index < childRouters.size) {
removeChildRouter(childRouters[index])
}
}
companion object {
private const val NUMBER_OF_CHILDREN = 5
}
}
@@ -1,138 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.RouterTransaction;
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.bluelinelabs.conductor.rxlifecycle2.ControllerEvent;
import com.bluelinelabs.conductor.rxlifecycle2.RxController;
import java.util.concurrent.TimeUnit;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import io.reactivex.Observable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
public class RxLifecycle2Controller extends RxController {
private static final String TAG = "RxLifecycleController";
@BindView(R.id.tv_title) TextView tvTitle;
private Unbinder unbinder;
private boolean hasExited;
public RxLifecycle2Controller() {
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(() -> Log.i(TAG, "Disposing from constructor"))
.compose(this.<Long>bindUntilEvent(ControllerEvent.DESTROY))
.subscribe(num -> Log.i(TAG, "Started in constructor, running until onDestroy(): " + num));
}
@NonNull
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
Log.i(TAG, "onCreateView() called");
View view = inflater.inflate(R.layout.controller_lifecycle, container, false);
view.setBackgroundColor(ContextCompat.getColor(container.getContext(), R.color.blue_grey_300));
unbinder = ButterKnife.bind(this, view);
tvTitle.setText(getResources().getString(R.string.rxlifecycle_title, TAG));
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(() -> Log.i(TAG, "Disposing from onCreateView()"))
.compose(this.<Long>bindUntilEvent(ControllerEvent.DESTROY_VIEW))
.subscribe(num -> Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): " + num));
return view;
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
Log.i(TAG, "onAttach() called");
(((ActionBarProvider)getActivity()).getSupportActionBar()).setTitle("RxLifecycle2 Demo");
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose(() -> Log.i(TAG, "Disposing from onAttach()"))
.compose(this.<Long>bindUntilEvent(ControllerEvent.DETACH))
.subscribe(num -> Log.i(TAG, "Started in onAttach(), running until onDetach(): " + num));
}
@Override
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
Log.i(TAG, "onDestroyView() called");
unbinder.unbind();
unbinder = null;
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
Log.i(TAG, "onDetach() called");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy() called");
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
@OnClick(R.id.btn_next_release_view) void onNextWithReleaseClicked() {
setRetainViewMode(RetainViewMode.RELEASE_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
@OnClick(R.id.btn_next_retain_view) void onNextWithRetainClicked() {
setRetainViewMode(RetainViewMode.RETAIN_DETACH);
getRouter().pushController(RouterTransaction.with(new TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running."))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
}
@@ -0,0 +1,119 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.DemoApplication
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.ToolbarProvider
import com.bluelinelabs.conductor.demo.databinding.ControllerLifecycleBinding
import com.bluelinelabs.conductor.rxlifecycle2.ControllerEvent
import com.bluelinelabs.conductor.rxlifecycle2.RxController
import io.reactivex.Observable
import java.util.concurrent.TimeUnit
// Shamelessly borrowed from the official RxLifecycle demo by Trello and adapted for Conductor Controllers
// instead of Activities or Fragments.
class RxLifecycle2Controller : RxController() {
private var hasExited = false
init {
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose { Log.i(TAG, "Disposing from constructor") }
.compose(bindUntilEvent(ControllerEvent.DESTROY))
.subscribe { num: Long ->
Log.i(TAG, "Started in constructor, running until onDestroy(): $num")
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
Log.i(TAG, "onCreateView() called")
val binding = ControllerLifecycleBinding.inflate(inflater, container, false)
binding.title.text = binding.root.resources.getString(R.string.rxlifecycle_title, TAG)
binding.nextReleaseView.setOnClickListener {
retainViewMode = RetainViewMode.RELEASE_DETACH
router.pushController(
with(TextController("Logcat should now report that the observables from onAttach() and onViewBound() have been disposed of, while the constructor observable is still running."))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
binding.nextRetainView.setOnClickListener {
retainViewMode = RetainViewMode.RETAIN_DETACH
router.pushController(
with(TextController("Logcat should now report that the observables from onAttach() has been disposed of, while the constructor and onViewBound() observables are still running."))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose { Log.i(TAG, "Disposing from onCreateView()") }
.compose(bindUntilEvent(ControllerEvent.DESTROY_VIEW))
.subscribe { num: Long ->
Log.i(TAG, "Started in onCreateView(), running until onDestroyView(): $num")
}
return binding.root
}
override fun onAttach(view: View) {
super.onAttach(view)
Log.i(TAG, "onAttach() called")
(activity as ToolbarProvider).toolbar.title = "RxLifecycle2 Demo"
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose { Log.i(TAG, "Disposing from onAttach()") }
.compose(bindUntilEvent(ControllerEvent.DETACH))
.subscribe { num: Long ->
Log.i(TAG, "Started in onAttach(), running until onDetach(): $num")
}
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
Log.i(TAG, "onDestroyView() called")
}
override fun onDetach(view: View) {
super.onDetach(view)
Log.i(TAG, "onDetach() called")
}
public override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "onDestroy() called")
if (hasExited) {
DemoApplication.refWatcher.watch(this)
}
}
override fun onChangeEnded(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeEnded(changeHandler, changeType)
hasExited = !changeType.isEnter
if (isDestroyed) {
DemoApplication.refWatcher.watch(this)
}
}
companion object {
private const val TAG = "RxLifecycleController"
}
}
@@ -1,119 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.TargetTitleEntryController.TargetTitleEntryControllerListener;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.squareup.picasso.Picasso;
import butterknife.BindView;
import butterknife.OnClick;
public class TargetDisplayController extends BaseController implements TargetTitleEntryControllerListener {
private static final int REQUEST_SELECT_IMAGE = 126;
private static final String KEY_SELECTED_TEXT = "TargetDisplayController.selectedText";
private static final String KEY_SELECTED_IMAGE = "TargetDisplayController.selectedImage";
@BindView(R.id.tv_selection) TextView tvSelection;
@BindView(R.id.image_view) ImageView imageView;
private String selectedText;
private Uri imageUri;
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_target_display, container, false);
}
@OnClick(R.id.btn_pick_title) void launchTitlePicker() {
getRouter().pushController(RouterTransaction.with(new TargetTitleEntryController(this))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
}
@OnClick(R.id.btn_pick_image) void launchImagePicker() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
startActivityForResult(Intent.createChooser(intent, "Select Image"), REQUEST_SELECT_IMAGE);
}
@Override
public void onTitlePicked(String option) {
selectedText = option;
setTextView();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SELECT_IMAGE && resultCode == Activity.RESULT_OK) {
imageUri = data.getData();
setImageView();
}
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
setTextView();
setImageView();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_SELECTED_TEXT, selectedText);
outState.putString(KEY_SELECTED_IMAGE, imageUri != null ? imageUri.toString() : null);
}
@Override
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
selectedText = savedInstanceState.getString(KEY_SELECTED_TEXT);
String uriString = savedInstanceState.getString(KEY_SELECTED_IMAGE);
if (!TextUtils.isEmpty(uriString)) {
imageUri = Uri.parse(uriString);
}
}
@Override
protected String getTitle() {
return "Target Controller Demo";
}
private void setImageView() {
Picasso.with(getActivity())
.load(imageUri)
.fit()
.centerCrop()
.into(imageView);
}
private void setTextView() {
if (tvSelection != null) {
if (!TextUtils.isEmpty(selectedText)) {
tvSelection.setText(selectedText);
} else {
tvSelection.setText("Press pick title to set this title, or pick image to fill in the image view.");
}
}
}
}
@@ -0,0 +1,100 @@
package com.bluelinelabs.conductor.demo.controllers
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.view.View
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.TargetTitleEntryController.TargetTitleEntryControllerListener
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerTargetDisplayBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
import com.squareup.picasso.Picasso
class TargetDisplayController : BaseController(R.layout.controller_target_display), TargetTitleEntryControllerListener {
private val binding: ControllerTargetDisplayBinding by viewBinding(ControllerTargetDisplayBinding::bind)
override val title = "Target Controller Demo"
private var selectedText: String? = null
private var imageUri: Uri? = null
override fun onViewCreated(view: View) {
super.onViewCreated(view)
setTextView()
setImageView()
binding.pickTitleButton.setOnClickListener {
router.pushController(
with(TargetTitleEntryController(this))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
binding.pickImageButton.setOnClickListener {
val intent = Intent(Intent.ACTION_GET_CONTENT, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true)
startActivityForResult(Intent.createChooser(intent, "Select Image"), REQUEST_SELECT_IMAGE)
}
}
override fun onTitlePicked(option: String?) {
selectedText = option
setTextView()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_SELECT_IMAGE && resultCode == Activity.RESULT_OK) {
imageUri = data?.data
setImageView()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(KEY_SELECTED_TEXT, selectedText)
outState.putString(KEY_SELECTED_IMAGE, if (imageUri != null) imageUri.toString() else null)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
selectedText = savedInstanceState.getString(KEY_SELECTED_TEXT)
val uriString = savedInstanceState.getString(KEY_SELECTED_IMAGE)
if (!uriString.isNullOrEmpty()) {
imageUri = Uri.parse(uriString)
}
}
private fun setImageView() {
view ?: return
Picasso.with(activity)
.load(imageUri)
.fit()
.centerCrop()
.into(binding.imageView)
}
private fun setTextView() {
view ?: return
if (!selectedText.isNullOrEmpty()) {
binding.selection.text = selectedText
} else {
binding.selection.text = "Press pick title to set this title, or pick image to fill in the image view."
}
}
companion object {
private const val REQUEST_SELECT_IMAGE = 126
private const val KEY_SELECTED_TEXT = "TargetDisplayController.selectedText"
private const val KEY_SELECTED_IMAGE = "TargetDisplayController.selectedImage"
}
}
@@ -1,56 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.content.Context;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import butterknife.BindView;
import butterknife.OnClick;
public class TargetTitleEntryController extends BaseController {
public interface TargetTitleEntryControllerListener {
void onTitlePicked(String option);
}
@BindView(R.id.edit_text) EditText editText;
public <T extends Controller & TargetTitleEntryControllerListener> TargetTitleEntryController(T targetController) {
setTargetController(targetController);
}
public TargetTitleEntryController() { }
@Override
protected void onDetach(@NonNull View view) {
InputMethodManager imm = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_target_title_entry, container, false);
}
@Override
protected String getTitle() {
return "Target Controller Demo";
}
@OnClick(R.id.btn_use_title) void optionPicked() {
Controller targetController = getTargetController();
if (targetController != null) {
((TargetTitleEntryControllerListener)targetController).onTitlePicked(editText.getText().toString());
getRouter().popController(this);
}
}
}
@@ -0,0 +1,41 @@
package com.bluelinelabs.conductor.demo.controllers
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerTargetTitleEntryBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class TargetTitleEntryController() : BaseController(R.layout.controller_target_title_entry) {
private val binding: ControllerTargetTitleEntryBinding by viewBinding(ControllerTargetTitleEntryBinding::bind)
override val title = "Target Controller Demo"
constructor(targetController: TargetTitleEntryControllerListener) : this() {
check(targetController is Controller)
setTargetController(targetController)
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.useTitleButton.setOnClickListener {
targetController?.let { listener ->
(listener as TargetTitleEntryControllerListener).onTitlePicked(binding.editText.text.toString())
router.popController(this)
}
}
}
override fun onDetach(view: View) {
val imm = binding.editText.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(binding.editText.windowToken, 0)
}
interface TargetTitleEntryControllerListener {
fun onTitlePicked(option: String?)
}
}
@@ -1,45 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import butterknife.BindView;
public class TextController extends BaseController {
private static final String KEY_TEXT = "TextController.text";
@BindView(R.id.text_view) TextView textView;
public TextController(String text) {
this(new BundleBuilder(new Bundle())
.putString(KEY_TEXT, text)
.build()
);
}
public TextController(Bundle args) {
super(args);
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_text, container, false);
}
@Override
public void onViewBound(@NonNull View view) {
super.onViewBound(view);
textView.setText(getArgs().getString(KEY_TEXT));
}
}
@@ -0,0 +1,23 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerTextBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class TextController(args: Bundle) : BaseController(R.layout.controller_text, args) {
private val binding: ControllerTextBinding by viewBinding(ControllerTextBinding::bind)
constructor(text: String) : this(bundleOf(KEY_TEXT to text))
override fun onViewCreated(view: View) {
binding.textView.text = args.getString(KEY_TEXT)
}
companion object {
private const val KEY_TEXT = "TextController.text"
}
}
@@ -1,146 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.ColorRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import butterknife.BindView;
import butterknife.OnClick;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandler;
import com.bluelinelabs.conductor.demo.changehandler.CircularRevealChangeHandlerCompat;
import com.bluelinelabs.conductor.demo.changehandler.FlipChangeHandler;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.demo.util.BundleBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
public class TransitionDemoController extends BaseController {
private static final String KEY_INDEX = "TransitionDemoController.index";
public enum TransitionDemo {
VERTICAL("Vertical Slide Animation", R.layout.controller_transition_demo, R.color.blue_grey_300),
CIRCULAR("Circular Reveal Animation", R.layout.controller_transition_demo, R.color.red_300),
FADE("Fade Animation", R.layout.controller_transition_demo, R.color.blue_300),
FLIP("Flip Animation", R.layout.controller_transition_demo, R.color.deep_orange_300),
HORIZONTAL("Horizontal Slide Animation", R.layout.controller_transition_demo, R.color.green_300),
ARC_FADE("Arc/Fade Shared Element Transition", R.layout.controller_transition_demo_shared, 0),
ARC_FADE_RESET("Arc/Fade Shared Element Transition", R.layout.controller_transition_demo, R.color.pink_300);
String title;
int layoutId;
int colorId;
TransitionDemo(String title, @LayoutRes int layoutId, @ColorRes int colorId) {
this.title = title;
this.layoutId = layoutId;
this.colorId = colorId;
}
public static TransitionDemo fromIndex(int index) {
return TransitionDemo.values()[index];
}
}
@BindView(R.id.tv_title) TextView tvTitle;
@BindView(R.id.btn_next) FloatingActionButton btnNext;
@BindView(R.id.transition_root) View containerView;
private TransitionDemo transitionDemo;
public TransitionDemoController(int index) {
this(new BundleBuilder(new Bundle())
.putInt(KEY_INDEX, index)
.build());
}
public TransitionDemoController(Bundle args) {
super(args);
transitionDemo = TransitionDemo.fromIndex(args.getInt(KEY_INDEX));
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(transitionDemo.layoutId, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
View bgView = view.findViewById(R.id.bg_view);
if (transitionDemo.colorId != 0 && bgView != null) {
bgView.setBackgroundColor(ContextCompat.getColor(getActivity(), transitionDemo.colorId));
}
final int nextIndex = transitionDemo.ordinal() + 1;
int buttonColor = 0;
if (nextIndex < TransitionDemo.values().length) {
buttonColor = TransitionDemo.fromIndex(nextIndex).colorId;
}
if (buttonColor == 0) {
buttonColor = TransitionDemo.fromIndex(0).colorId;
}
btnNext.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(getActivity(), buttonColor)));
tvTitle.setText(transitionDemo.title);
}
@Override
protected String getTitle() {
return "Transition Demos";
}
@OnClick(R.id.btn_next) void onNextClicked() {
final int nextIndex = transitionDemo.ordinal() + 1;
if (nextIndex < TransitionDemo.values().length) {
getRouter().pushController(getRouterTransaction(nextIndex, this));
} else {
getRouter().popToRoot();
}
}
public ControllerChangeHandler getChangeHandler(Controller from) {
switch (transitionDemo) {
case VERTICAL:
return new VerticalChangeHandler();
case CIRCULAR:
TransitionDemoController demoController = (TransitionDemoController) from;
return new CircularRevealChangeHandlerCompat(demoController.btnNext, demoController.containerView);
case FADE:
return new FadeChangeHandler();
case FLIP:
return new FlipChangeHandler();
case ARC_FADE:
return new ArcFadeMoveChangeHandler(from.getResources().getString(R.string.transition_tag_dot), from.getResources().getString(R.string.transition_tag_title));
case ARC_FADE_RESET:
return new ArcFadeMoveChangeHandler(from.getResources().getString(R.string.transition_tag_dot), from.getResources().getString(R.string.transition_tag_title));
case HORIZONTAL:
return new HorizontalChangeHandler();
default:
return null;
}
}
public static RouterTransaction getRouterTransaction(int index, Controller fromController) {
TransitionDemoController toController = new TransitionDemoController(index);
return RouterTransaction.with(toController)
.pushChangeHandler(toController.getChangeHandler(fromController))
.popChangeHandler(toController.getChangeHandler(fromController));
}
}
@@ -0,0 +1,141 @@
package com.bluelinelabs.conductor.demo.controllers
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.ColorRes
import androidx.annotation.DimenRes
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.isGone
import androidx.core.view.updateLayoutParams
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.changehandler.ArcFadeMoveChangeHandler
import com.bluelinelabs.conductor.demo.changehandler.CircularRevealChangeHandlerCompat
import com.bluelinelabs.conductor.demo.changehandler.FlipChangeHandler
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerTransitionDemoBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
class TransitionDemoController(args: Bundle) : BaseController(R.layout.controller_transition_demo, args) {
private val binding: ControllerTransitionDemoBinding by viewBinding(ControllerTransitionDemoBinding::bind)
override val title = "Transition Demos"
private val demo = TransitionDemo.values()[args.getInt(KEY_INDEX)]
constructor(index: Int) : this(bundleOf(KEY_INDEX to index))
override fun onViewCreated(view: View) {
super.onViewCreated(view)
if (demo.layout == TransactionDemoLayout.SHARED) {
binding.bgView.isGone = true
}
binding.title.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin = view.resources.getDimension(demo.layout.topMargin).toInt() }
binding.next.updateLayoutParams<FrameLayout.LayoutParams> { gravity = demo.layout.fabGravity }
if (demo.colorId != 0) {
binding.bgView.setBackgroundColor(ContextCompat.getColor(activity!!, demo.colorId))
}
val nextIndex = demo.ordinal + 1
val buttonColor = if (nextIndex < TransitionDemo.values().size) {
TransitionDemo.values()[nextIndex].colorId
} else {
R.color.blue_grey_300
}
binding.next.backgroundTintList = ContextCompat.getColorStateList(view.context, buttonColor)
binding.title.text = demo.title
binding.next.setOnClickListener {
if (nextIndex < TransitionDemo.values().size) {
router.pushController(getRouterTransaction(nextIndex, this))
} else {
router.popToRoot()
}
}
}
private fun getChangeHandler(from: Controller): ControllerChangeHandler {
return when (demo) {
TransitionDemo.VERTICAL -> VerticalChangeHandler()
TransitionDemo.CIRCULAR -> {
val demoController = from as TransitionDemoController
CircularRevealChangeHandlerCompat(demoController.binding.next, demoController.binding.transitionRoot)
}
TransitionDemo.FADE -> FadeChangeHandler()
TransitionDemo.FLIP -> FlipChangeHandler()
TransitionDemo.ARC_FADE -> ArcFadeMoveChangeHandler(
from.resources!!.getString(R.string.transition_tag_dot), from.resources!!.getString(R.string.transition_tag_title)
)
TransitionDemo.ARC_FADE_RESET -> ArcFadeMoveChangeHandler(
from.resources!!.getString(R.string.transition_tag_dot), from.resources!!.getString(R.string.transition_tag_title)
)
TransitionDemo.HORIZONTAL -> HorizontalChangeHandler()
}
}
companion object {
private const val KEY_INDEX = "TransitionDemoController.index"
fun getRouterTransaction(index: Int, fromController: Controller): RouterTransaction {
val toController = TransitionDemoController(index)
return RouterTransaction.with(toController)
.pushChangeHandler(toController.getChangeHandler(fromController))
.popChangeHandler(toController.getChangeHandler(fromController))
}
}
}
enum class TransactionDemoLayout(@DimenRes val topMargin: Int, val fabGravity: Int) {
STANDARD(R.dimen.transition_margin_top_standard, Gravity.BOTTOM or Gravity.END),
SHARED(R.dimen.transition_margin_top_shared, Gravity.CENTER)
}
enum class TransitionDemo(val title: String, val layout: TransactionDemoLayout, @ColorRes val colorId: Int) {
VERTICAL(
"Vertical Slide Animation",
TransactionDemoLayout.STANDARD,
R.color.blue_grey_300
),
CIRCULAR(
"Circular Reveal Animation",
TransactionDemoLayout.STANDARD,
R.color.red_300
),
FADE("Fade Animation",
TransactionDemoLayout.STANDARD,
R.color.blue_300
),
FLIP(
"Flip Animation",
TransactionDemoLayout.STANDARD,
R.color.deep_orange_300
),
HORIZONTAL(
"Horizontal Slide Animation",
TransactionDemoLayout.STANDARD,
R.color.green_300
),
ARC_FADE(
"Arc/Fade Shared Element Transition",
TransactionDemoLayout.SHARED,
R.color.blue_grey_300
),
ARC_FADE_RESET(
"Arc/Fade Shared Element Transition",
TransactionDemoLayout.STANDARD,
R.color.pink_300
)
}
@@ -1,76 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.viewpager2.widget.ViewPager2;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.viewpager2.RouterStateAdapter;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.Locale;
import butterknife.BindView;
public class ViewPager2Controller extends BaseController {
private int[] PAGE_COLORS = new int[]{R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300};
@BindView(R.id.tab_layout) TabLayout tabLayout;
@BindView(R.id.view_pager) ViewPager2 viewPager;
private final RouterStateAdapter pagerAdapter;
public ViewPager2Controller() {
pagerAdapter = new RouterStateAdapter(this) {
@Override
public void configureRouter(@NonNull Router router, int position) {
if (!router.hasRootController()) {
Controller page = new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
router.setRoot(RouterTransaction.with(page));
}
}
@Override
public int getItemCount() {
return PAGE_COLORS.length;
}
};
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_view_pager2, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
viewPager.setAdapter(pagerAdapter);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> tab.setText("Page " + position)).attach();
}
@Override
protected void onDestroyView(@NonNull View view) {
if (!getActivity().isChangingConfigurations()) {
viewPager.setAdapter(null);
}
tabLayout.setupWithViewPager(null);
super.onDestroyView(view);
}
@Override
protected String getTitle() {
return "ViewPager2 Demo";
}
}
@@ -0,0 +1,54 @@
package com.bluelinelabs.conductor.demo.controllers
import android.view.View
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerViewPager2Binding
import com.bluelinelabs.conductor.demo.util.viewBinding
import com.bluelinelabs.conductor.viewpager2.RouterStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import java.util.*
class ViewPager2Controller : BaseController(R.layout.controller_view_pager2) {
private val binding: ControllerViewPager2Binding by viewBinding(ControllerViewPager2Binding::bind)
override val title = "ViewPager2 Demo"
private val pageColors = intArrayOf(R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300)
private lateinit var tabLayoutMediator: TabLayoutMediator
private val pagerAdapter = object : RouterStateAdapter(this) {
override fun configureRouter(router: Router, position: Int) {
if (!router.hasRootController()) {
val page: Controller = ChildController(
title = String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position),
backgroundColor = pageColors[position],
colorIsResId = true
)
router.setRoot(with(page))
}
}
override fun getItemCount() = pageColors.size
}
override fun onViewCreated(view: View) {
binding.viewPager.adapter = pagerAdapter
tabLayoutMediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = "Page $position"
}
tabLayoutMediator.attach()
}
override fun onDestroyView(view: View) {
if (!activity!!.isChangingConfigurations) {
binding.viewPager.adapter = null
}
tabLayoutMediator.detach()
super.onDestroyView(view)
}
}
@@ -1,80 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.ViewPager;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.demo.R;
import com.bluelinelabs.conductor.demo.controllers.base.BaseController;
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter;
import com.google.android.material.tabs.TabLayout;
import java.util.Locale;
import butterknife.BindView;
public class ViewPagerController extends BaseController {
private int[] PAGE_COLORS = new int[]{R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300};
@BindView(R.id.tab_layout) TabLayout tabLayout;
@BindView(R.id.view_pager) ViewPager viewPager;
private final RouterPagerAdapter pagerAdapter;
public ViewPagerController() {
pagerAdapter = new RouterPagerAdapter(this) {
@Override
public void configureRouter(@NonNull Router router, int position) {
if (!router.hasRootController()) {
Controller page = new ChildController(String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position), PAGE_COLORS[position], true);
router.setRoot(RouterTransaction.with(page));
}
}
@Override
public int getCount() {
return PAGE_COLORS.length;
}
@Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
};
}
@NonNull
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_view_pager, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
viewPager.setAdapter(pagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
@Override
protected void onDestroyView(@NonNull View view) {
if (!getActivity().isChangingConfigurations()) {
viewPager.setAdapter(null);
}
tabLayout.setupWithViewPager(null);
super.onDestroyView(view);
}
@Override
protected String getTitle() {
return "ViewPager Demo";
}
}
@@ -0,0 +1,50 @@
package com.bluelinelabs.conductor.demo.controllers
import android.view.View
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction.Companion.with
import com.bluelinelabs.conductor.demo.R
import com.bluelinelabs.conductor.demo.controllers.base.BaseController
import com.bluelinelabs.conductor.demo.databinding.ControllerViewPagerBinding
import com.bluelinelabs.conductor.demo.util.viewBinding
import com.bluelinelabs.conductor.viewpager.RouterPagerAdapter
import java.util.*
class ViewPagerController : BaseController(R.layout.controller_view_pager) {
private val binding: ControllerViewPagerBinding by viewBinding(ControllerViewPagerBinding::bind)
override val title = "ViewPager Demo"
private val pageColors = intArrayOf(R.color.green_300, R.color.cyan_300, R.color.deep_purple_300, R.color.lime_300, R.color.red_300)
private val pagerAdapter = object : RouterPagerAdapter(this) {
override fun configureRouter(router: Router, position: Int) {
if (!router.hasRootController()) {
val page: Controller = ChildController(
title = String.format(Locale.getDefault(), "Child #%d (Swipe to see more)", position),
backgroundColor = pageColors[position],
colorIsResId = true
)
router.setRoot(with(page))
}
}
override fun getCount() = pageColors.size
override fun getPageTitle(position: Int) = "Page $position"
}
override fun onViewCreated(view: View) {
binding.viewPager.adapter = pagerAdapter
binding.tabLayout.setupWithViewPager(binding.viewPager)
}
override fun onDestroyView(view: View) {
if (!activity!!.isChangingConfigurations) {
binding.viewPager.adapter = null
}
binding.tabLayout.setupWithViewPager(null)
super.onDestroyView(view)
}
}
@@ -1,51 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers.base;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import android.view.View;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.demo.ActionBarProvider;
public abstract class BaseController extends RefWatchingController {
protected BaseController() { }
protected BaseController(Bundle args) {
super(args);
}
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
// be accessed. In a production app, this would use Dagger instead.
protected ActionBar getActionBar() {
ActionBarProvider actionBarProvider = ((ActionBarProvider)getActivity());
return actionBarProvider != null ? actionBarProvider.getSupportActionBar() : null;
}
@Override
protected void onAttach(@NonNull View view) {
setTitle();
super.onAttach(view);
}
protected void setTitle() {
Controller parentController = getParentController();
while (parentController != null) {
if (parentController instanceof BaseController && ((BaseController)parentController).getTitle() != null) {
return;
}
parentController = parentController.getParentController();
}
String title = getTitle();
ActionBar actionBar = getActionBar();
if (title != null && actionBar != null) {
actionBar.setTitle(title);
}
}
protected String getTitle() {
return null;
}
}
@@ -0,0 +1,80 @@
package com.bluelinelabs.conductor.demo.controllers.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.appcompat.widget.Toolbar
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.demo.ToolbarProvider
abstract class BaseController(
@LayoutRes private val layoutRes: Int,
args: Bundle? = null
) : RefWatchingController(args) {
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
// be accessed. In a production app, this would use Dagger instead.
protected val toolbar: Toolbar?
get() = (activity as? ToolbarProvider)?.toolbar
protected open val title: String? = null
init {
addLifecycleListener(object : LifecycleListener() {
override fun postCreateView(controller: Controller, view: View) {
onViewCreated(view)
toolbar?.let { configureToolbar(it) }
}
})
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
return inflater.inflate(layoutRes, container, false)
}
open fun onViewCreated(view: View) = Unit
override fun onAttach(view: View) {
toolbar?.let { configureToolbar(it) }
super.onAttach(view)
}
override fun onChangeStarted(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeStarted(changeHandler, changeType)
if (changeType.isEnter) {
toolbar?.let { configureMenu(it) }
}
}
open fun configureToolbar(toolbar: Toolbar) {
val title = title ?: return
var parentController = parentController
while (parentController != null) {
if (parentController is BaseController && parentController.title != null) {
return
}
parentController = parentController.parentController
}
toolbar.title = title
}
open fun configureMenu(toolbar: Toolbar) {
toolbar.menu.clear()
}
}
@@ -1,45 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers.base;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.Controller;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public abstract class ButterKnifeController extends Controller {
private Unbinder unbinder;
protected ButterKnifeController() { }
protected ButterKnifeController(Bundle args) {
super(args);
}
protected abstract View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container);
@NonNull
@Override
protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container, @Nullable Bundle savedViewState) {
View view = inflateView(inflater, container);
unbinder = ButterKnife.bind(this, view);
onViewBound(view);
return view;
}
protected void onViewBound(@NonNull View view) { }
@Override
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
unbinder.unbind();
unbinder = null;
}
}
@@ -1,38 +0,0 @@
package com.bluelinelabs.conductor.demo.controllers.base;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.bluelinelabs.conductor.ControllerChangeHandler;
import com.bluelinelabs.conductor.ControllerChangeType;
import com.bluelinelabs.conductor.demo.DemoApplication;
public abstract class RefWatchingController extends ButterKnifeController {
protected RefWatchingController() { }
protected RefWatchingController(Bundle args) {
super(args);
}
private boolean hasExited;
@Override
public void onDestroy() {
super.onDestroy();
if (hasExited) {
DemoApplication.refWatcher.watch(this);
}
}
@Override
protected void onChangeEnded(@NonNull ControllerChangeHandler changeHandler, @NonNull ControllerChangeType changeType) {
super.onChangeEnded(changeHandler, changeType);
hasExited = !changeType.isEnter;
if (isDestroyed()) {
DemoApplication.refWatcher.watch(this);
}
}
}
@@ -0,0 +1,32 @@
package com.bluelinelabs.conductor.demo.controllers.base
import android.os.Bundle
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.demo.DemoApplication
abstract class RefWatchingController(args: Bundle?) : Controller(args) {
private var hasExited = false
public override fun onDestroy() {
super.onDestroy()
if (hasExited) {
DemoApplication.refWatcher.watch(this)
}
}
override fun onChangeEnded(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeEnded(changeHandler, changeType)
hasExited = !changeType.isEnter
if (isDestroyed) {
DemoApplication.refWatcher.watch(this)
}
}
}
@@ -21,20 +21,17 @@ import android.animation.TimeInterpolator;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.ArrayMap;
import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Property;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
import androidx.transition.Transition;
import java.util.ArrayList;
/**
* Utility methods for working with animations.
*/
@@ -79,109 +76,6 @@ public class AnimUtils {
return linear;
}
/**
* Linear interpolate between a and b with parameter t.
*/
public static float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
/**
* A delegate for creating a {@link Property} of <code>int</code> type.
*/
public static abstract class IntProp<T> {
public final String name;
public IntProp(String name) {
this.name = name;
}
public abstract void set(T object, int value);
public abstract int get(T object);
}
/**
* The animation framework has an optimization for <code>Properties</code> of type
* <code>int</code> but it was only made public in API24, so wrap the impl in our own type
* and conditionally create the appropriate type, delegating the implementation.
*/
public static <T> Property<T, Integer> createIntProperty(final IntProp<T> impl) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return new IntProperty<T>(impl.name) {
@Override
public Integer get(T object) {
return impl.get(object);
}
@Override
public void setValue(T object, int value) {
impl.set(object, value);
}
};
} else {
return new Property<T, Integer>(Integer.class, impl.name) {
@Override
public Integer get(T object) {
return impl.get(object);
}
@Override
public void set(T object, Integer value) {
impl.set(object, value);
}
};
}
}
/**
* A delegate for creating a {@link Property} of <code>float</code> type.
*/
public static abstract class FloatProp<T> {
public final String name;
protected FloatProp(String name) {
this.name = name;
}
public abstract void set(T object, float value);
public abstract float get(T object);
}
/**
* The animation framework has an optimization for <code>Properties</code> of type
* <code>float</code> but it was only made public in API24, so wrap the impl in our own type
* and conditionally create the appropriate type, delegating the implementation.
*/
public static <T> Property<T, Float> createFloatProperty(final FloatProp<T> impl) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return new FloatProperty<T>(impl.name) {
@Override
public Float get(T object) {
return impl.get(object);
}
@Override
public void setValue(T object, float value) {
impl.set(object, value);
}
};
} else {
return new Property<T, Float>(Float.class, impl.name) {
@Override
public Float get(T object) {
return impl.get(object);
}
@Override
public void set(T object, Float value) {
impl.set(object, value);
}
};
}
}
/**
* https://halfthought.wordpress.com/2014/11/07/reveal-transition/
* <p/>
@@ -1,172 +0,0 @@
package com.bluelinelabs.conductor.demo.util;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.SparseArray;
import java.io.Serializable;
import java.util.ArrayList;
public class BundleBuilder {
private final Bundle bundle;
public BundleBuilder(Bundle bundle) {
this.bundle = bundle;
}
public BundleBuilder putAll(Bundle bundle) {
this.bundle.putAll(bundle);
return this;
}
public BundleBuilder putBoolean(String key, boolean value) {
bundle.putBoolean(key, value);
return this;
}
public BundleBuilder putBooleanArray(String key, boolean[] value) {
bundle.putBooleanArray(key, value);
return this;
}
public BundleBuilder putDouble(String key, double value) {
bundle.putDouble(key, value);
return this;
}
public BundleBuilder putDoubleArray(String key, double[] value) {
bundle.putDoubleArray(key, value);
return this;
}
public BundleBuilder putLong(String key, long value) {
bundle.putLong(key, value);
return this;
}
public BundleBuilder putLongArray(String key, long[] value) {
bundle.putLongArray(key, value);
return this;
}
public BundleBuilder putString(String key, String value) {
bundle.putString(key, value);
return this;
}
public BundleBuilder putStringArray(String key, String[] value) {
bundle.putStringArray(key, value);
return this;
}
public BundleBuilder putBundle(String key, Bundle value) {
bundle.putBundle(key, value);
return this;
}
public BundleBuilder putByte(String key, byte value) {
bundle.putByte(key, value);
return this;
}
public BundleBuilder putByteArray(String key, byte[] value) {
bundle.putByteArray(key, value);
return this;
}
public BundleBuilder putChar(String key, char value) {
bundle.putChar(key, value);
return this;
}
public BundleBuilder putCharArray(String key, char[] value) {
bundle.putCharArray(key, value);
return this;
}
public BundleBuilder putCharSequence(String key, CharSequence value) {
bundle.putCharSequence(key, value);
return this;
}
public BundleBuilder putCharSequenceArray(String key, CharSequence[] value) {
bundle.putCharSequenceArray(key, value);
return this;
}
public BundleBuilder putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
bundle.putCharSequenceArrayList(key, value);
return this;
}
public BundleBuilder putInt(String key, int value) {
bundle.putInt(key, value);
return this;
}
public BundleBuilder putIntArray(String key, int[] value) {
bundle.putIntArray(key, value);
return this;
}
public BundleBuilder putFloat(String key, float value) {
bundle.putFloat(key, value);
return this;
}
public BundleBuilder putFloatArray(String key, float[] value) {
bundle.putFloatArray(key, value);
return this;
}
public BundleBuilder putIntegerArrayList(String key, ArrayList<Integer> value) {
bundle.putIntegerArrayList(key, value);
return this;
}
public BundleBuilder putParcelable(String key, Parcelable value) {
bundle.putParcelable(key, value);
return this;
}
public BundleBuilder putParcelableArray(String key, Parcelable[] value) {
bundle.putParcelableArray(key, value);
return this;
}
public BundleBuilder putParcelableArrayList(String key, ArrayList<? extends Parcelable> value) {
bundle.putParcelableArrayList(key, value);
return this;
}
public BundleBuilder putSerializable(String key, Serializable value) {
bundle.putSerializable(key, value);
return this;
}
public BundleBuilder putShort(String key, short value) {
bundle.putShort(key, value);
return this;
}
public BundleBuilder putShortArray(String key, short[] value) {
bundle.putShortArray(key, value);
return this;
}
public BundleBuilder putSparseParcelableArray(String key, SparseArray<? extends Parcelable> value) {
bundle.putSparseParcelableArray(key, value);
return this;
}
public BundleBuilder putStringArrayList(String key, ArrayList<String> value) {
bundle.putStringArrayList(key, value);
return this;
}
public Bundle build() {
return bundle;
}
}
@@ -1,20 +0,0 @@
package com.bluelinelabs.conductor.demo.util;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import com.bluelinelabs.conductor.demo.R;
public class ColorUtil {
public static int getMaterialColor(Resources resources, int index) {
TypedArray colors = resources.obtainTypedArray(R.array.mdcolor_300);
final int returnColor = colors.getColor(index % colors.length(), Color.BLACK);
colors.recycle();
return returnColor;
}
}
@@ -0,0 +1,14 @@
package com.bluelinelabs.conductor.demo.util
import android.content.res.Resources
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.core.content.res.use
import com.bluelinelabs.conductor.demo.R
@ColorInt
fun Resources.getMaterialColor(index: Int): Int {
return obtainTypedArray(R.array.mdcolor_300).use {
it.getColor(index % it.length(), Color.BLACK)
}
}
@@ -0,0 +1,31 @@
package com.bluelinelabs.conductor.demo.util
import android.view.View
import androidx.lifecycle.LifecycleObserver
import androidx.viewbinding.ViewBinding
import com.bluelinelabs.conductor.Controller
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
fun <T : ViewBinding> Controller.viewBinding(bindingFactory: (View) -> T) =
ControllerViewBindingDelegate(this, bindingFactory)
class ControllerViewBindingDelegate<T : ViewBinding>(
controller: Controller,
private val viewBinder: (View) -> T
) : ReadOnlyProperty<Controller, T>, LifecycleObserver {
private var binding: T? = null
init {
controller.addLifecycleListener(object : Controller.LifecycleListener() {
override fun postDestroyView(controller: Controller) {
binding = null
}
})
}
override fun getValue(thisRef: Controller, property: KProperty<*>): T {
return binding ?: viewBinder(thisRef.view!!).also { binding = it }
}
}
@@ -5,7 +5,7 @@
android:layout_height="match_parent" >
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
@@ -1,35 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/city_grid_root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingEnd="@dimen/activity_horizontal_margin"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical" >
android:paddingEnd="@dimen/activity_horizontal_margin">
<ImageView
android:id="@+id/img_dot"
android:id="@+id/dot"
android:layout_width="96dp"
android:layout_height="96dp"
android:src="@drawable/circle"
/>
android:importantForAccessibility="no"
android:src="@drawable/circle" />
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
style="@style/Base.TextAppearance.AppCompat.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
/>
android:layout_marginTop="40dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"
/>
android:layout_marginTop="16dp" />
</LinearLayout>
@@ -28,7 +28,7 @@
android:layout_gravity="center">
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginTop="24dp"
@@ -39,7 +39,7 @@
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_description"
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="24dp"
@@ -19,7 +19,6 @@
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/padding_normal"
android:layout_marginEnd="@dimen/padding_normal"
android:layout_marginRight="@dimen/padding_normal"
android:elevation="@dimen/z_fab"
android:scaleType="matrix"
android:stateListAnimator="@animator/raise"
@@ -6,7 +6,7 @@
android:orientation="vertical" >
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
@@ -21,7 +21,7 @@
android:background="@color/white">
<Button
android:id="@+id/btn_next_retain_view"
android:id="@+id/next_retain_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@@ -30,7 +30,7 @@
/>
<Button
android:id="@+id/btn_next_release_view"
android:id="@+id/next_release_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@@ -6,7 +6,7 @@
android:layout_height="match_parent" >
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
@@ -21,7 +21,7 @@
android:background="@color/white">
<Button
android:id="@+id/btn_pop_to_root"
android:id="@+id/popToRoot"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@@ -30,7 +30,7 @@
/>
<Button
android:id="@+id/btn_up"
android:id="@+id/goUp"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@@ -39,7 +39,7 @@
/>
<Button
android:id="@+id/btn_next"
android:id="@+id/goToNext"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@@ -7,7 +7,7 @@
android:background="@color/pink_300" >
<TextView
android:id="@+id/tv_selection"
android:id="@+id/selection"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
@@ -20,6 +20,7 @@
android:layout_height="@dimen/display_target_image_size"
android:layout_gravity="center_horizontal"
android:layout_margin="@dimen/display_target_image_margin"
android:importantForAccessibility="no"
/>
<LinearLayout
@@ -29,7 +30,7 @@
android:background="@color/white">
<Button
android:id="@+id/btn_pick_title"
android:id="@+id/pick_title_button"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
@@ -38,7 +39,7 @@
/>
<Button
android:id="@+id/btn_pick_image"
android:id="@+id/pick_image_button"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
@@ -15,7 +15,7 @@
/>
<Button
android:id="@+id/btn_use_title"
android:id="@+id/use_title_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/use_title"
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/transition_root"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -14,26 +13,22 @@
/>
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginTop="96dp"
android:gravity="center_horizontal"
android:transitionName="@string/transition_tag_title"
tools:targetApi="lollipop"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btn_next"
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="24dp"
app:srcCompat="@drawable/ic_arrow_forward_white_36dp"
app:elevation="0dp"
app:pressedTranslationZ="0dp"
android:transitionName="@string/transition_tag_dot"
tools:targetApi="lollipop"
/>
</FrameLayout>
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/transition_root"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginTop="24dp"
android:gravity="center_horizontal"
android:transitionName="@string/transition_tag_title"
tools:targetApi="lollipop"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:srcCompat="@drawable/ic_arrow_forward_white_36dp"
app:elevation="0dp"
app:pressedTranslationZ="0dp"
android:transitionName="@string/transition_tag_dot"
tools:targetApi="lollipop"
/>
</FrameLayout>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text_view"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/activity_horizontal_margin"
+5 -8
View File
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row_root"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="200dp"
android:clipChildren="false"
@@ -11,19 +9,18 @@
android:padding="8dp">
<ImageView
android:id="@+id/img_city"
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
/>
android:importantForAccessibility="no" />
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
style="@style/Base.TextAppearance.AppCompat.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
/>
android:layout_gravity="center_horizontal" />
</LinearLayout>
+2 -2
View File
@@ -7,7 +7,7 @@
android:paddingBottom="16dp" >
<ImageView
android:id="@+id/image_view"
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
@@ -15,7 +15,7 @@
/>
<TextView
android:id="@+id/text_view"
android:id="@+id/textView"
style="@style/Base.TextAppearance.AppCompat.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+1 -2
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/row_root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -9,7 +8,7 @@
android:padding="24dp">
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
style="@style/Base.TextAppearance.AppCompat.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+3 -4
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/row_root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -11,15 +10,15 @@
android:padding="24dp">
<ImageView
android:id="@+id/img_dot"
android:id="@+id/dot"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:src="@drawable/circle"
/>
<TextView
android:id="@+id/tv_title"
android:id="@+id/title"
style="@style/Base.TextAppearance.AppCompat.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+4
View File
@@ -6,6 +6,10 @@
<dimen name="display_target_image_size">200dp</dimen>
<dimen name="display_target_image_margin">24dp</dimen>
<!-- Transition demo controllers -->
<dimen name="transition_margin_top_standard">96dp</dimen>
<dimen name="transition_margin_top_shared">24dp</dimen>
<!-- Custom transition dimensions -->
<dimen name="padding_normal">16dp</dimen>
<dimen name="z_fab">6dp</dimen>
+4 -5
View File
@@ -3,7 +3,6 @@ ext {
compileSdkVersion = 28
targetSdkVersion = 28
butterknifeVersion = '10.1.0'
picassoVersion = '2.5.2'
leakCanaryVersion = '1.5.4'
rxJavaVersion = '1.3.8'
@@ -13,7 +12,8 @@ ext {
autodisposeVersion = '1.0.0'
archComponentsVersion = '2.0.0'
junitVersion = '4.12'
mvnPublishVersion = '0.11.1'
mvnPublishVersion = '0.13.0'
dokkaVersion = '1.4.10.2'
agpVersion = "4.1.0"
lintVersion = agpVersion.replaceFirst(~/\d*/) { version ->
@@ -30,9 +30,7 @@ ext {
androidxTransition = "androidx.transition:transition:1.3.1"
androidxCollection = "androidx.collection:collection:1.1.0"
androidxViewPager2 = "androidx.viewpager2:viewpager2:1.0.0"
butterknife = "com.jakewharton:butterknife:$butterknifeVersion"
butterknifeCompiler = "com.jakewharton:butterknife-compiler:$butterknifeVersion"
androidxCoreKtx = "androidx.core:core-ktx:1.3.2"
picasso = "com.squareup.picasso:picasso:$picassoVersion"
@@ -47,6 +45,7 @@ ext {
autodispose = "com.uber.autodispose:autodispose:$autodisposeVersion"
autodisposeLifecycle = "com.uber.autodispose:autodispose-lifecycle:$autodisposeVersion"
autodisposeKtx = "com.uber.autodispose:autodispose-ktx:$autodisposeVersion"
archComponentsLifecycle = "androidx.lifecycle:lifecycle-runtime:$archComponentsVersion"
archComponentsLiveDataCore = "androidx.lifecycle:lifecycle-livedata-core:$archComponentsVersion"