1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.compose.animation.scene 18 19 import androidx.annotation.VisibleForTesting 20 import androidx.compose.runtime.Stable 21 22 /** 23 * A base class to create unique keys, associated to an [identity] that is used to check the 24 * equality of two key instances. 25 */ 26 @Stable 27 sealed class Key(val debugName: String, val identity: Any) { equalsnull28 override fun equals(other: Any?): Boolean { 29 if (this === other) return true 30 if (this.javaClass != other?.javaClass) return false 31 return identity == (other as? Key)?.identity 32 } 33 hashCodenull34 override fun hashCode(): Int { 35 return identity.hashCode() 36 } 37 toStringnull38 override fun toString(): String { 39 return "Key(debugName=$debugName)" 40 } 41 } 42 43 /** The key for a content (scene or overlay). */ 44 sealed class ContentKey(debugName: String, identity: Any) : Key(debugName, identity) { 45 @VisibleForTesting 46 // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can 47 // access internal members. 48 abstract val testTag: String 49 } 50 51 /** Key for a scene. */ 52 class SceneKey(debugName: String, identity: Any = Object()) : ContentKey(debugName, identity) { 53 override val testTag: String = "scene:$debugName" 54 55 /** The unique [ElementKey] identifying this scene's root element. */ 56 val rootElementKey = ElementKey(debugName, identity) 57 toStringnull58 override fun toString(): String { 59 return "SceneKey(debugName=$debugName)" 60 } 61 } 62 63 /** Key for an overlay. */ 64 class OverlayKey(debugName: String, identity: Any = Object()) : ContentKey(debugName, identity) { 65 override val testTag: String = "overlay:$debugName" 66 toStringnull67 override fun toString(): String { 68 return "OverlayKey(debugName=$debugName)" 69 } 70 } 71 72 /** Key for an element. */ 73 open class ElementKey( 74 debugName: String, 75 identity: Any = Object(), 76 77 /** 78 * The [ElementContentPicker] to use when deciding in which scene we should draw shared Elements 79 * or compose MovableElements. 80 */ 81 open val contentPicker: ElementContentPicker = DefaultElementContentPicker, 82 83 /** 84 * Whether we should place all copies of this element when it is shared. 85 * 86 * This should usually be false, but it can be useful when sharing a container that has a 87 * different content in different scenes/overlays. That way the container will have the same 88 * size and position in all scenes/overlays but all different contents will be placed and 89 * visible on screen. 90 */ 91 val placeAllCopies: Boolean = false, 92 ) : Key(debugName, identity), ElementMatcher { 93 @VisibleForTesting 94 // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can 95 // access internal members. 96 val testTag: String = "element:$debugName" 97 matchesnull98 override fun matches(key: ElementKey, content: ContentKey): Boolean { 99 return key == this 100 } 101 toStringnull102 override fun toString(): String { 103 return "ElementKey(debugName=$debugName)" 104 } 105 106 companion object { 107 /** Matches any element whose [key identity][ElementKey.identity] matches [predicate]. */ withIdentitynull108 fun withIdentity(predicate: (Any) -> Boolean): ElementMatcher { 109 return object : ElementMatcher { 110 override fun matches(key: ElementKey, content: ContentKey): Boolean { 111 return predicate(key.identity) 112 } 113 } 114 } 115 } 116 } 117 118 /** Key for a movable element. */ 119 class MovableElementKey( 120 debugName: String, 121 122 /** 123 * The [StaticElementContentPicker] to use when deciding in which scene we should draw shared 124 * Elements or compose MovableElements. 125 * 126 * @see DefaultElementContentPicker 127 * @see MovableElementContentPicker 128 */ 129 override val contentPicker: StaticElementContentPicker, 130 identity: Any = Object(), 131 ) : ElementKey(debugName, identity, contentPicker) { 132 constructor( 133 debugName: String, 134 135 /** The exhaustive list of contents (scenes or overlays) that can contain this element. */ 136 contents: Set<ContentKey>, 137 identity: Any = Object(), 138 ) : this(debugName, MovableElementContentPicker(contents), identity) 139 toStringnull140 override fun toString(): String { 141 return "MovableElementKey(debugName=$debugName)" 142 } 143 } 144 145 /** Key for a shared value of an element. */ 146 class ValueKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) { toStringnull147 override fun toString(): String { 148 return "ValueKey(debugName=$debugName)" 149 } 150 } 151 152 /** 153 * Key for a transition. This can be used to specify which transition spec should be used when 154 * starting the transition between two scenes. 155 */ 156 class TransitionKey(debugName: String, identity: Any = Object()) : Key(debugName, identity) { toStringnull157 override fun toString(): String { 158 return "TransitionKey(debugName=$debugName)" 159 } 160 161 companion object { 162 /** 163 * A special transition key indicating that the associated transition should be used for 164 * Predictive Back gestures. 165 * 166 * Use this key when defining a transition that you want to be specifically triggered when 167 * the user performs a Predictive Back gesture. 168 */ 169 val PredictiveBack = TransitionKey("PredictiveBack") 170 } 171 } 172