Compare commits

...

6 Commits

Author SHA1 Message Date
EricKuck 6a87a4140e minSdk 19 2024-01-17 11:52:24 -05:00
EricKuck ad81c4819b jdk 17 in CI 2024-01-17 11:38:55 -05:00
EricKuck 49018ed84c Fix a few early bugs when disabled 2024-01-17 10:59:50 -05:00
EricKuck fbfe3ce2e9 WIP predictive back change handlers 2024-01-16 18:36:13 -05:00
Steven Schoen 5a1746b2d3 Fix doc mistake in ControllerChangeHandler (#691) 2024-01-11 13:28:13 -05:00
Steven Schoen 2ffafaee79 Add kdoc to ControllerChangeHandler::removesFromViewOnPush (#688) 2023-09-29 23:19:21 -04:00
20 changed files with 342 additions and 82 deletions
+4 -4
View File
@@ -8,11 +8,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: set up JDK 11
- name: set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
java-version: '17'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
@@ -24,11 +24,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: set up JDK 11
- name: set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
java-version: '17'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Release with Gradle
@@ -4,7 +4,12 @@ plugins {
}
android {
compileSdkVersion libs.versions.compilesdk.get() as Integer
compileSdk libs.versions.compilesdk.get() as Integer
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
defaultConfig {
minSdkVersion libs.versions.minsdk.get()
@@ -12,6 +17,8 @@ android {
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
namespace "com.bluelinelabs.conductor.androidxtransition"
}
dependencies {
@@ -1,3 +0,0 @@
<manifest package="com.bluelinelabs.conductor.androidxtransition">
<application />
</manifest>
+12 -1
View File
@@ -5,7 +5,16 @@ plugins {
}
android {
compileSdkVersion libs.versions.compilesdk.get() as Integer
compileSdk libs.versions.compilesdk.get() as Integer
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
defaultConfig {
minSdkVersion libs.versions.minsdk.get()
@@ -13,6 +22,8 @@ android {
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION_NAME
}
namespace "com.bluelinelabs.conductor.viewpager"
}
dependencies {
@@ -1,3 +0,0 @@
<manifest package="com.bluelinelabs.conductor.viewpager">
<application />
</manifest>
+12 -1
View File
@@ -6,7 +6,16 @@ plugins {
}
android {
compileSdkVersion libs.versions.compilesdk.get() as Integer
compileSdk libs.versions.compilesdk.get() as Integer
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
defaultConfig {
minSdkVersion libs.versions.minsdk.get()
@@ -20,6 +29,8 @@ android {
includeAndroidResources = true
}
}
namespace "com.bluelinelabs.conductor.viewpager2"
}
dependencies {
@@ -1,3 +0,0 @@
<manifest package="com.bluelinelabs.conductor.viewpager2">
<application />
</manifest>
+12 -1
View File
@@ -6,7 +6,16 @@ plugins {
}
android {
compileSdkVersion libs.versions.compilesdk.get() as Integer
compileSdk libs.versions.compilesdk.get() as Integer
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
defaultConfig {
minSdkVersion libs.versions.minsdk.get()
@@ -15,6 +24,8 @@ android {
versionName project.VERSION_NAME
consumerProguardFiles 'proguard-rules.txt'
}
namespace "com.bluelinelabs.conductor"
}
dependencies {
-3
View File
@@ -1,3 +0,0 @@
<manifest package="com.bluelinelabs.conductor">
<application />
</manifest>
@@ -8,6 +8,7 @@ import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -16,6 +17,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.BackEventCompat;
import androidx.activity.ComponentActivity;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
@@ -24,6 +26,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import com.bluelinelabs.conductor.internal.BackGestureControllerView;
import com.bluelinelabs.conductor.internal.BackGestureViewState;
import com.bluelinelabs.conductor.internal.ClassUtils;
import com.bluelinelabs.conductor.internal.ControllerLifecycleOwner;
import com.bluelinelabs.conductor.internal.OwnViewTreeLifecycleAndRegistry;
@@ -97,6 +101,9 @@ public abstract class Controller {
private boolean isContextAvailable;
final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
ControllerChangeHandler.ChangeTransaction popTransaction = null;
BackGestureViewState backGestureViewState = null;
@Override
public void handleOnBackPressed() {
// Root-level routers should have PopRootControllerMode.NEVER, and so should never return false here.
@@ -111,6 +118,116 @@ public abstract class Controller {
}
}
}
@Override
public void handleOnBackStarted(@NonNull BackEventCompat backEvent) {
Log.d("KUCK", "it has begun");
ControllerChangeHandler.ChangeTransaction transaction = getPopTransaction();
if (transaction != null && transaction.changeHandler != null && transaction.changeHandler.getEnableOnBackGestureCallbacks()) {
List<BackGestureControllerView> toViews;
if (transaction.to != null && transaction.to.view == null) {
toViews = toViewsForGesture(transaction, new ArrayList<>());
} else {
toViews = Collections.emptyList();
}
for (int i = toViews.size() - 1; i > 0; i--) {
transaction.container.addView(toViews.get(i).getView());
}
backGestureViewState = new BackGestureViewState(transaction.from.view, toViews);
transaction.changeHandler.handleOnBackStarted(
transaction.container,
getToView(),
backGestureViewState.getFromView(),
backEvent
);
}
}
@Override
public void handleOnBackProgressed(@NonNull BackEventCompat backEvent) {
Log.d("KUCK", "it has progressed");
ControllerChangeHandler.ChangeTransaction transaction = getPopTransaction();
if (transaction != null && transaction.changeHandler != null && transaction.changeHandler.getEnableOnBackGestureCallbacks()) {
transaction.changeHandler.handleOnBackProgressed(
transaction.container,
getToView(),
backGestureViewState.getFromView(),
backEvent
);
}
}
@Override
public void handleOnBackCancelled() {
Log.d("KUCK", "it has canceled");
ControllerChangeHandler.ChangeTransaction transaction = getPopTransaction();
if (transaction != null && transaction.changeHandler != null && transaction.changeHandler.getEnableOnBackGestureCallbacks()) {
transaction.changeHandler.handleOnBackCancelled(
transaction.container,
getToView(),
backGestureViewState.getFromView()
);
for (int i = 0; i < backGestureViewState.getToViews().size(); i++) {
BackGestureControllerView state = backGestureViewState.getToViews().get(i);
ViewGroup parent = (ViewGroup) state.getView().getParent();
if (parent != null) {
parent.removeView(state.getView());
}
if (state.getInflatedForGesture()) {
state.getController().removeViewReference(state.getView().getContext());
}
}
}
backGestureViewState = null;
}
private ControllerChangeHandler.ChangeTransaction getPopTransaction() {
if (popTransaction == null) {
popTransaction = router.popTransaction(Controller.this);
}
return popTransaction;
}
private List<BackGestureControllerView> toViewsForGesture(
ControllerChangeHandler.ChangeTransaction transaction,
List<BackGestureControllerView> aggregator
) {
if (transaction.to != null) {
View view = transaction.to.view;
boolean inflated = false;
if (view == null) {
view = transaction.to.inflate(transaction.container);
inflated = true;
} else if (view.getParent() != null) {
return aggregator;
}
aggregator.add(new BackGestureControllerView(transaction.to, view, inflated));
if (!transaction.changeHandler.getRemovesFromViewOnPush()) {
toViewsForGesture(router.popTransaction(transaction.to), aggregator);
}
}
return aggregator;
}
@Nullable
private View getToView() {
if (backGestureViewState.getToViews().size() == 0) {
return null;
} else {
return backGestureViewState.getToViews().get(0).getView();
}
}
};
public final LifecycleOwner lifecycleOwner = new ControllerLifecycleOwner(this);
@@ -3,6 +3,7 @@ package com.bluelinelabs.conductor
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.activity.BackEventCompat
import androidx.annotation.RestrictTo
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
import com.bluelinelabs.conductor.internal.ClassUtils
@@ -23,10 +24,35 @@ abstract class ControllerChangeHandler {
*/
open val isReusable: Boolean = false
/**
* Returns whether or not this handler removes the `from` view from the container when performing a push.
*
* If this is true:
* - This handler's implementation of [performChange] should remove `from` from `container`
* before calling `changeListener.onChangeCompleted()`
* - When a controller is pushed, the previous controller will be detached and its view will be destroyed
*
* If this is false:
* - This handler's implementation of [performChange] should only remove `from` from `container`
* when `isPush` is false
* - When a controller is pushed, the previous controller will stay attached and its view will remain created
* - When a view is recreated (e.g. after a configuration change), any controllers underneath a transaction
* using this handler will have their view recreated and attached, even though they're not the top-most
* controller
*
* If a controller pushed onto the backstack will completely cover the previous controller,
* using a change handler with [removesFromViewOnPush] true should result in no visual interruption
* to the user, while allowing the previous controller's view to be destroyed to reclaim resources.
* If instead, the previous controller should still be visible after the new controller is pushed,
* using a change handler with [removesFromViewOnPush] false will keep the previous controller's
* view in the view hierarchy, where it can still be seen (and even interacted with).
*/
open val removesFromViewOnPush: Boolean = true
private var hasBeenUsed = false
open val enableOnBackGestureCallbacks = false
init {
try {
javaClass.getConstructor()
@@ -89,9 +115,9 @@ abstract class ControllerChangeHandler {
*/
open fun copy(): ControllerChangeHandler = fromBundle(toBundle())!!
open fun handleOnBackStarted(container: ViewGroup, to: View?, from: View, swipeEdge: Int) {}
open fun handleOnBackStarted(container: ViewGroup, to: View?, from: View, event: BackEventCompat) {}
open fun handleOnBackProgressed(container: ViewGroup, to: View?, from: View, progress: Float, swipeEdge: Int) {}
open fun handleOnBackProgressed(container: ViewGroup, to: View?, from: View, event: BackEventCompat) {}
open fun handleOnBackCancelled(container: ViewGroup, to: View?, from: View) {}
@@ -804,6 +804,37 @@ public abstract class Router {
}
}
@Nullable
ChangeTransaction popTransaction(@NonNull Controller controller) {
RouterTransaction from = null;
RouterTransaction to = null;
Iterator<RouterTransaction> iterator = backstack.iterator();
while (iterator.hasNext()) {
RouterTransaction transaction = iterator.next();
if (transaction.controller() == controller) {
from = transaction;
if (iterator.hasNext()) {
to = iterator.next();
}
break;
}
}
if (from == null) {
return null;
}
return new ChangeTransaction(
to != null ? to.controller() : null,
from.controller(),
false,
container,
from.popChangeHandler(),
Collections.emptyList()
);
}
void watchContainerAttach() {
container.post(new Runnable() {
@Override
@@ -0,0 +1,15 @@
package com.bluelinelabs.conductor.internal
import android.view.View
import com.bluelinelabs.conductor.Controller
class BackGestureViewState(
val fromView: View,
val toViews: List<BackGestureControllerView>,
)
class BackGestureControllerView(
val controller: Controller,
val view: View,
val inflatedForGesture: Boolean,
)
@@ -14,7 +14,6 @@ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.R
/**
* This class sets the [ViewTreeLifecycleOwner] and [ViewTreeSavedStateRegistryOwner] which is
@@ -56,8 +55,8 @@ internal class OwnViewTreeLifecycleAndRegistry private constructor(
* it on purpose.
*/
if (
view.getTag(R.id.view_tree_lifecycle_owner) == null &&
view.getTag(R.id.view_tree_saved_state_registry_owner) == null
view.getTag(androidx.lifecycle.runtime.R.id.view_tree_lifecycle_owner) == null &&
view.getTag(androidx.savedstate.R.id.view_tree_saved_state_registry_owner) == null
) {
view.setViewTreeLifecycleOwner(this@OwnViewTreeLifecycleAndRegistry)
view.setViewTreeSavedStateRegistryOwner(this@OwnViewTreeLifecycleAndRegistry)
+6 -4
View File
@@ -27,20 +27,22 @@ android {
compose = true
}
compileSdkVersion 33
compileSdk 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
}
composeOptions {
kotlinCompilerExtensionVersion libs.versions.compose.compiler.get()
}
namespace "com.bluelinelabs.conductor.demo"
}
dependencies {
+23 -23
View File
@@ -1,31 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.bluelinelabs.conductor.demo">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<application
android:allowBackup="true"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="com.bluelinelabs.conductor.demo.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.bluelinelabs.conductor.demo.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</application>
</manifest>
@@ -10,6 +10,7 @@ import android.text.style.URLSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.BackEventCompat
import androidx.annotation.ColorRes
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
@@ -18,6 +19,7 @@ import androidx.core.graphics.BlendModeCompat
import androidx.core.text.buildSpannedString
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.asTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
@@ -76,12 +78,13 @@ class HomeController : BaseController(R.layout.controller_home) {
}
private fun onModelRowClick(model: DemoModel, position: Int) {
println("onModelRowClick")
when (model) {
DemoModel.NAVIGATION -> {
router.pushController(
RouterTransaction.with(NavigationDemoController(0, DisplayUpMode.SHOW_FOR_CHILDREN_ONLY))
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(WhateverChangeHandler())
.popChangeHandler(WhateverChangeHandler())
.tag(NavigationDemoController.TAG_UP_TRANSACTION)
)
}
@@ -233,3 +236,47 @@ private class HomeAdapter(
}
}
}
class WhateverChangeHandler : ControllerChangeHandler() {
override val enableOnBackGestureCallbacks = true
override fun handleOnBackStarted(container: ViewGroup, to: View?, from: View, event: BackEventCompat) {
println("handleOnBackStarted")
to?.let { container.addView(it) }
}
override fun handleOnBackProgressed(container: ViewGroup, to: View?, from: View, event: BackEventCompat) {
if (event.swipeEdge == BackEventCompat.EDGE_LEFT) {
from.translationX = event.progress * container.width
to?.translationX = -container.width + event.progress * container.width
} else {
from.translationX = -event.progress * container.width
to?.translationX = container.width + -event.progress * container.width
}
}
override fun handleOnBackCancelled(container: ViewGroup, to: View?, from: View) {
to?.let { container.removeView(it) }
}
override fun performChange(
container: ViewGroup,
from: View?,
to: View?,
isPush: Boolean,
changeListener: ControllerChangeCompletedListener,
) {
from?.translationX = 0f
to?.translationX = 0f
println("perform change called")
if (isPush) {
to?.let { container.addView(it) }
from?.let { container.removeView(it) }
} else {
to?.let { if (it.parent == null) container.addView(it) }
from?.let { container.removeView(it) }
}
changeListener.onChangeCompleted()
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
VERSION_CODE=4
VERSION_NAME=4.0.0-SNAPSHOT
VERSION_NAME=4.0.0-SNAPSHOT-PREDICTIVE
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
+21 -26
View File
@@ -1,32 +1,30 @@
[versions]
minsdk = "16"
compilesdk = "33"
minsdk = "19"
compilesdk = "34"
targetsdk = "28"
agp = "7.4.2" # Note: update lint version whenever this is updated
androidx-activity = "1.6.1"
androidx-annotation = "1.1.0"
androidx-appcompat = "1.4.2"
androidx-collection = "1.1.0"
androidx-core = "1.3.2"
androidx-lifecycle = "2.6.0"
androidx-savedstate = "1.2.0"
androidx-transition = "1.3.1"
agp = "8.1.4" # Note: update lint version whenever this is updated
androidx-activity = "1.8.2"
androidx-annotation = "1.7.1"
androidx-appcompat = "1.6.1"
androidx-collection = "1.3.0"
androidx-core = "1.12.0"
androidx-lifecycle = "2.7.0"
androidx-savedstate = "1.2.1"
androidx-transition = "1.4.1"
androidx-viewpager2 = "1.0.0"
autodispose = "1.0.0"
compose = "1.3.1"
compose-compiler = "1.4.3"
dokka = "1.8.10"
junit = "4.13"
compose = "1.5.4"
compose-compiler = "1.5.3"
dokka = "1.9.0"
junit = "4.13.2"
kotest = "4.6.0"
kotlin = "1.8.10"
leakCanary = "2.7"
lint = "30.4.2" # Should always be agp + 23
material = "1.2.1"
mvnpublish = "0.23.2"
kotlin = "1.9.10"
leakCanary = "2.12"
lint = "31.1.4" # Should always be agp + 23
material = "1.11.0"
mvnpublish = "0.27.0"
picasso = "2.5.2"
robolectric = "4.5.1"
rxjava2 = "2.1.14"
robolectric = "4.11.1"
[libraries]
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
@@ -37,14 +35,12 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a
androidx-collection = { module = "androidx.collection:collection", version.ref = "androidx-collection" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidx-lifecycle" }
androidx-lifecycle-livedata-core = { module = "androidx.lifecycle:lifecycle-livedata-core", version.ref = "androidx-lifecycle" }
androidx-savedstate-ktx = { module = "androidx.savedstate:savedstate-ktx", version.ref = "androidx-savedstate" }
androidx-transition = { module = "androidx.transition:transition", version.ref = "androidx-transition" }
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "androidx-viewpager2" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
junit = { module = "junit:junit", version.ref = "junit" }
@@ -59,7 +55,6 @@ lint-tests = { module = "com.android.tools.lint:lint-tests", version.ref = "lint
material = { module = "com.google.android.material:material", version.ref = "material" }
picasso = { module = "com.squareup.picasso:picasso", version.ref = "picasso" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
rxjava2 = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava2" }
[plugins]
mvnpublish = { id = "com.vanniktech.maven.publish", version.ref = "mvnpublish" }
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists