[go: nahoru, domu]

Update SwipeDismissableNavHost to support saveable state.

Also, add backgroundKey and contentKey to public API
for SwipeToDismissBox as part of SwipeToDismissDefaults,
so that they can be passed in calling code that may not
always have a non-trivial value available. And also,
set backgroundKey and contentKey in Wear Compose
Integration demo so that state is preserved.

Test: androidx.wear.compose.navigation and
androidx.wear.compose.integration.demos.test

RelNote: "Updated SwipeDismissableNavHost to support
rememberSaveable by setting the key identity for background and content"

Bug: 199054308
Change-Id: I746fd6214b2f58bee625a86fd3f422122a0528ed
diff --git a/wear/compose/compose-material/api/public_plus_experimental_current.txt b/wear/compose/compose-material/api/public_plus_experimental_current.txt
index 8a82a87..33fc439 100644
--- a/wear/compose/compose-material/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material/api/public_plus_experimental_current.txt
@@ -317,7 +317,11 @@
 
   @androidx.wear.compose.material.ExperimentalWearMaterialApi public final class SwipeToDismissBoxDefaults {
     method public androidx.compose.animation.core.SpringSpec<java.lang.Float> getAnimationSpec();
+    method public Object getBackgroundKey();
+    method public Object getContentKey();
     property public final androidx.compose.animation.core.SpringSpec<java.lang.Float> AnimationSpec;
+    property public final Object BackgroundKey;
+    property public final Object ContentKey;
     field public static final androidx.wear.compose.material.SwipeToDismissBoxDefaults INSTANCE;
   }
 
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt
index 0c85c97..d46f990b 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt
@@ -73,8 +73,8 @@
     state: SwipeToDismissBoxState,
     modifier: Modifier = Modifier,
     scrimColor: Color = MaterialTheme.colors.surface,
-    backgroundKey: Any = DefaultBackgroundKey,
-    contentKey: Any = DefaultContentKey,
+    backgroundKey: Any = SwipeToDismissBoxDefaults.BackgroundKey,
+    contentKey: Any = SwipeToDismissBoxDefaults.ContentKey,
     content: @Composable BoxScope.(isBackground: Boolean) -> Unit
 ) {
     // Will be updated in onSizeChanged, initialise to any value other than zero
@@ -92,6 +92,9 @@
             )
     ) {
         val offsetPx = state.offset.value.roundToInt()
+        // This temporary variable added to workaround an error
+        // thrown by the compose runtime - see b/199136503, avoids "Invalid Start Index" exception.
+        val pxModifier = Modifier.offset { IntOffset(offsetPx, 0) }.fillMaxSize()
         repeat(2) {
             val isBackground = it == 0
             // TODO(b/193606660): Add animations that follow after swipe confirmation.
@@ -101,18 +104,19 @@
                 } else {
                     if (isBackground) SwipeConfirmedBackgroundAlpha else SwipeConfirmedContentAlpha
                 }
-            val contentModifier =
-                if (isBackground)
-                    Modifier.fillMaxSize()
-                else
-                    Modifier.offset { IntOffset(offsetPx, 0) }.fillMaxSize()
+            val contentModifier = if (isBackground) Modifier.fillMaxSize() else pxModifier
             key(if (isBackground) backgroundKey else contentKey) {
                 if (!isBackground || offsetPx > 0) {
                     Box(contentModifier) {
+                        // We use the repeat loop above and call content at this location
+                        // for both background and foreground so that any persistence
+                        // within the content composable has the same call stack which is used
+                        // as part of the hash identity for saveable state.
                         content(isBackground)
                         Box(
                             modifier = Modifier
-                                .matchParentSize().background(scrimColor.copy(alpha = scrimAlpha))
+                                .matchParentSize()
+                                .background(scrimColor.copy(alpha = scrimAlpha))
                         )
                     }
                 }
@@ -186,7 +190,24 @@
  */
 @ExperimentalWearMaterialApi
 public object SwipeToDismissBoxDefaults {
+    /**
+     * The default animation that will be used to animate to a new state after the swipe gesture.
+     */
     public val AnimationSpec = SwipeableDefaults.AnimationSpec
+
+    /**
+     * The default background key to identify the content displayed by the content block
+     * when isBackground == true. Specifying a background key instead of using the default
+     * allows remembered state to be correctly moved between background and foreground.
+     */
+    public val BackgroundKey: Any = "background"
+
+    /**
+     * The default content key to identify the content displayed by the content block
+     * when isBackground == false. Specifying a background key instead of using the default
+     * allows remembered state to be correctly moved between background and foreground.
+     */
+    public val ContentKey: Any = "content"
 }
 
 /**
diff --git a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
index 6bf999c..5fa5f8b8 100644
--- a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
+++ b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
@@ -38,7 +38,6 @@
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.ToggleButton
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -113,7 +112,6 @@
     }
 
     @Test
-    @Ignore // TODO(stevebower): reinstate test once aosp/1818154 has been submitted.
     fun destinations_keep_saved_state() {
         val screenId = mutableStateOf(START)
         rule.setContentWithTheme {
diff --git a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
index 9e61241..54054a1 100644
--- a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
+++ b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
@@ -39,6 +39,7 @@
 import androidx.wear.compose.material.ExperimentalWearMaterialApi
 import androidx.wear.compose.material.SwipeDismissTarget
 import androidx.wear.compose.material.SwipeToDismissBox
+import androidx.wear.compose.material.SwipeToDismissBoxDefaults
 import androidx.wear.compose.material.rememberSwipeToDismissBoxState
 
 /**
@@ -141,6 +142,8 @@
     SwipeToDismissBox(
         state = state,
         modifier = Modifier,
+        backgroundKey = previous?.id ?: SwipeToDismissBoxDefaults.BackgroundKey,
+        contentKey = current?.id ?: SwipeToDismissBoxDefaults.ContentKey,
         content = { isBackground ->
             BoxedStackEntryContent(if (isBackground) previous else current, stateHolder, modifier)
         },
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
index a58a4e0..2464302 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
@@ -33,6 +33,7 @@
 import androidx.wear.compose.material.ScalingLazyColumn
 import androidx.wear.compose.material.SwipeDismissTarget
 import androidx.wear.compose.material.SwipeToDismissBox
+import androidx.wear.compose.material.SwipeToDismissBoxDefaults
 import androidx.wear.compose.material.SwipeToDismissBoxState
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.rememberSwipeToDismissBoxState
@@ -63,6 +64,8 @@
         is ComposableDemo -> {
             SwipeToDismissBox(
                 state = swipeDismissStateWithNavigation(onNavigateBack),
+                backgroundKey = parentDemo?.title ?: SwipeToDismissBoxDefaults.BackgroundKey,
+                contentKey = demo.title,
             ) { isBackground ->
                 if (isBackground) {
                     if (parentDemo != null) {
@@ -89,6 +92,8 @@
 ) {
     SwipeToDismissBox(
         state = swipeDismissStateWithNavigation(onNavigateBack),
+        backgroundKey = parentDemo?.title ?: SwipeToDismissBoxDefaults.BackgroundKey,
+        contentKey = category.title,
     ) { isBackground ->
         if (isBackground) {
             if (parentDemo != null) {