1 /*
2  * Copyright 2024 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 package androidx.navigation
17 
18 import androidx.annotation.AnimRes
19 import androidx.annotation.AnimatorRes
20 import androidx.annotation.IdRes
21 import androidx.navigation.NavDestination.Companion.createRoute
22 import androidx.navigation.serialization.generateHashCode
23 import kotlin.reflect.KClass
24 import kotlinx.serialization.InternalSerializationApi
25 import kotlinx.serialization.serializer
26 
27 public actual class NavOptions
28 internal constructor(
29     private val singleTop: Boolean,
30     private val restoreState: Boolean,
31     /**
32      * The destination to pop up to before navigating. When set, all non-matching destinations
33      * should be popped from the back stack.
34      *
35      * @return the destinationId to pop up to, clearing all intervening destinations
36      * @see Builder.setPopUpTo
37      * @see isPopUpToInclusive
38      * @see shouldPopUpToSaveState
39      */
40     @field:IdRes @get:IdRes @param:IdRes public actual val popUpToId: Int,
41     private val popUpToInclusive: Boolean,
42     private val popUpToSaveState: Boolean,
43     /**
44      * The custom enter Animation/Animator that should be run.
45      *
46      * @return the resource id of a Animation or Animator or -1 if none.
47      */
48     @get:AnimatorRes @get:AnimRes @param:AnimRes @param:AnimatorRes public val enterAnim: Int,
49     /**
50      * The custom exit Animation/Animator that should be run.
51      *
52      * @return the resource id of a Animation or Animator or -1 if none.
53      */
54     @get:AnimatorRes @get:AnimRes @param:AnimRes @param:AnimatorRes public val exitAnim: Int,
55     /**
56      * The custom enter Animation/Animator that should be run when this destination is popped from
57      * the back stack.
58      *
59      * @return the resource id of a Animation or Animator or -1 if none.
60      */
61     @get:AnimatorRes @get:AnimRes @param:AnimRes @param:AnimatorRes public val popEnterAnim: Int,
62     /**
63      * The custom exit Animation/Animator that should be run when this destination is popped from
64      * the back stack.
65      *
66      * @return the resource id of a Animation or Animator or -1 if none.
67      */
68     @get:AnimatorRes @get:AnimRes @param:AnimRes @param:AnimatorRes public val popExitAnim: Int
69 ) {
70     /**
71      * The destination to pop up to before navigating. When set, all non-matching destinations
72      * should be popped from the back stack.
73      *
74      * @return the destinationId to pop up to, clearing all intervening destinations
75      * @see Builder.setPopUpTo
76      * @see isPopUpToInclusive
77      * @see shouldPopUpToSaveState
78      */
79     @IdRes
80     @Deprecated("Use popUpToId instead.", ReplaceWith("popUpToId"))
getPopUpTonull81     public fun getPopUpTo(): Int = popUpToId
82 
83     public actual var popUpToRoute: String? = null
84         private set
85 
86     public actual var popUpToRouteClass: KClass<*>? = null
87         private set
88 
89     public actual var popUpToRouteObject: Any? = null
90         private set
91 
92     /** NavOptions stores special options for navigate actions */
93     internal constructor(
94         singleTop: Boolean,
95         restoreState: Boolean,
96         popUpToRoute: String?,
97         popUpToInclusive: Boolean,
98         popUpToSaveState: Boolean,
99         enterAnim: Int,
100         exitAnim: Int,
101         popEnterAnim: Int,
102         popExitAnim: Int
103     ) : this(
104         singleTop,
105         restoreState,
106         createRoute(popUpToRoute).hashCode(),
107         popUpToInclusive,
108         popUpToSaveState,
109         enterAnim,
110         exitAnim,
111         popEnterAnim,
112         popExitAnim
113     ) {
114         this.popUpToRoute = popUpToRoute
115     }
116 
117     /** NavOptions stores special options for navigate actions */
118     @OptIn(InternalSerializationApi::class)
119     internal constructor(
120         singleTop: Boolean,
121         restoreState: Boolean,
122         popUpToRouteClass: KClass<*>?,
123         popUpToInclusive: Boolean,
124         popUpToSaveState: Boolean,
125         enterAnim: Int,
126         exitAnim: Int,
127         popEnterAnim: Int,
128         popExitAnim: Int
129     ) : this(
130         singleTop,
131         restoreState,
132         popUpToRouteClass!!.serializer().generateHashCode(),
133         popUpToInclusive,
134         popUpToSaveState,
135         enterAnim,
136         exitAnim,
137         popEnterAnim,
138         popExitAnim
139     ) {
140         this.popUpToRouteClass = popUpToRouteClass
141     }
142 
143     /** NavOptions stores special options for navigate actions */
144     @OptIn(InternalSerializationApi::class)
145     internal constructor(
146         singleTop: Boolean,
147         restoreState: Boolean,
148         popUpToRouteObject: Any,
149         popUpToInclusive: Boolean,
150         popUpToSaveState: Boolean,
151         enterAnim: Int,
152         exitAnim: Int,
153         popEnterAnim: Int,
154         popExitAnim: Int
155     ) : this(
156         singleTop,
157         restoreState,
158         popUpToRouteObject::class.serializer().generateHashCode(),
159         popUpToInclusive,
160         popUpToSaveState,
161         enterAnim,
162         exitAnim,
163         popEnterAnim,
164         popExitAnim
165     ) {
166         this.popUpToRouteObject = popUpToRouteObject
167     }
168 
shouldLaunchSingleTopnull169     public actual fun shouldLaunchSingleTop(): Boolean {
170         return singleTop
171     }
172 
shouldRestoreStatenull173     public actual fun shouldRestoreState(): Boolean {
174         return restoreState
175     }
176 
isPopUpToInclusivenull177     public actual fun isPopUpToInclusive(): Boolean {
178         return popUpToInclusive
179     }
180 
shouldPopUpToSaveStatenull181     public actual fun shouldPopUpToSaveState(): Boolean {
182         return popUpToSaveState
183     }
184 
equalsnull185     override fun equals(other: Any?): Boolean {
186         if (this === other) return true
187         if (other == null || other !is NavOptions) return false
188         return singleTop == other.singleTop &&
189             restoreState == other.restoreState &&
190             popUpToId == other.popUpToId &&
191             popUpToRoute == other.popUpToRoute &&
192             popUpToRouteClass == other.popUpToRouteClass &&
193             popUpToRouteObject == other.popUpToRouteObject &&
194             popUpToInclusive == other.popUpToInclusive &&
195             popUpToSaveState == other.popUpToSaveState &&
196             enterAnim == other.enterAnim &&
197             exitAnim == other.exitAnim &&
198             popEnterAnim == other.popEnterAnim &&
199             popExitAnim == other.popExitAnim
200     }
201 
hashCodenull202     override fun hashCode(): Int {
203         var result = if (shouldLaunchSingleTop()) 1 else 0
204         result = 31 * result + if (shouldRestoreState()) 1 else 0
205         result = 31 * result + popUpToId
206         result = 31 * result + popUpToRoute.hashCode()
207         result = 31 * result + popUpToRouteClass.hashCode()
208         result = 31 * result + popUpToRouteObject.hashCode()
209         result = 31 * result + if (isPopUpToInclusive()) 1 else 0
210         result = 31 * result + if (shouldPopUpToSaveState()) 1 else 0
211         result = 31 * result + enterAnim
212         result = 31 * result + exitAnim
213         result = 31 * result + popEnterAnim
214         result = 31 * result + popExitAnim
215         return result
216     }
217 
toStringnull218     override fun toString(): String {
219         val sb = StringBuilder()
220         sb.append(javaClass.simpleName)
221         sb.append("(")
222         if (singleTop) {
223             sb.append("launchSingleTop ")
224         }
225         if (restoreState) {
226             sb.append("restoreState ")
227         }
228         if (popUpToRoute != null || popUpToId != -1)
229             if (popUpToRoute != null) {
230                 sb.append("popUpTo(")
231                 if (popUpToRoute != null) {
232                     sb.append(popUpToRoute)
233                 } else if (popUpToRouteClass != null) {
234                     sb.append(popUpToRouteClass)
235                 } else if (popUpToRouteObject != null) {
236                     sb.append(popUpToRouteObject)
237                 } else {
238                     sb.append("0x")
239                     sb.append(Integer.toHexString(popUpToId))
240                 }
241                 if (popUpToInclusive) {
242                     sb.append(" inclusive")
243                 }
244                 if (popUpToSaveState) {
245                     sb.append(" saveState")
246                 }
247                 sb.append(")")
248             }
249         if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
250             sb.append("anim(enterAnim=0x")
251             sb.append(Integer.toHexString(enterAnim))
252             sb.append(" exitAnim=0x")
253             sb.append(Integer.toHexString(exitAnim))
254             sb.append(" popEnterAnim=0x")
255             sb.append(Integer.toHexString(popEnterAnim))
256             sb.append(" popExitAnim=0x")
257             sb.append(Integer.toHexString(popExitAnim))
258             sb.append(")")
259         }
260         return sb.toString()
261     }
262 
263     public actual class Builder {
264         private var singleTop = false
265         private var restoreState = false
266 
267         @IdRes private var popUpToId = -1
268         private var popUpToRoute: String? = null
269         private var popUpToRouteClass: KClass<*>? = null
270         private var popUpToRouteObject: Any? = null
271         private var popUpToInclusive = false
272         private var popUpToSaveState = false
273 
274         @AnimRes @AnimatorRes private var enterAnim = -1
275 
276         @AnimRes @AnimatorRes private var exitAnim = -1
277 
278         @AnimRes @AnimatorRes private var popEnterAnim = -1
279 
280         @AnimRes @AnimatorRes private var popExitAnim = -1
281 
setLaunchSingleTopnull282         public actual fun setLaunchSingleTop(singleTop: Boolean): Builder {
283             this.singleTop = singleTop
284             return this
285         }
286 
287         @SuppressWarnings("MissingGetterMatchingBuilder")
setRestoreStatenull288         public actual fun setRestoreState(restoreState: Boolean): Builder {
289             this.restoreState = restoreState
290             return this
291         }
292 
293         /**
294          * Pop up to a given destination before navigating. This pops all non-matching destinations
295          * from the back stack until this destination is found.
296          *
297          * @param destinationId The destination to pop up to, clearing all intervening destinations.
298          * @param inclusive true to also pop the given destination from the back stack.
299          * @param saveState true if the back stack and the state of all destinations between the
300          *   current destination and [destinationId] should be saved for later restoration via
301          *   [setRestoreState] or the `restoreState` attribute using the same ID as [popUpToId]
302          *   (note: this matching ID is true if [inclusive] is true. If [inclusive] is false, this
303          *   matching ID is the id of the last destination that is popped).
304          * @return this Builder
305          * @see NavOptions.popUpToId
306          * @see NavOptions.isPopUpToInclusive
307          */
308         @JvmOverloads
setPopUpTonull309         public fun setPopUpTo(
310             @IdRes destinationId: Int,
311             inclusive: Boolean,
312             saveState: Boolean = false
313         ): Builder {
314             popUpToId = destinationId
315             popUpToRoute = null
316             popUpToInclusive = inclusive
317             popUpToSaveState = saveState
318             return this
319         }
320 
321         @JvmOverloads
setPopUpTonull322         public actual fun setPopUpTo(
323             route: String?,
324             inclusive: Boolean,
325             saveState: Boolean
326         ): Builder {
327             popUpToRoute = route
328             popUpToId = -1
329             popUpToInclusive = inclusive
330             popUpToSaveState = saveState
331             return this
332         }
333 
334         @JvmOverloads
335         @Suppress("MissingGetterMatchingBuilder") // no need for getter
setPopUpTonull336         public actual inline fun <reified T : Any> setPopUpTo(
337             inclusive: Boolean,
338             saveState: Boolean
339         ): Builder {
340             setPopUpTo(T::class, inclusive, saveState)
341             return this
342         }
343 
344         @JvmOverloads
setPopUpTonull345         public actual fun <T : Any> setPopUpTo(
346             route: KClass<T>,
347             inclusive: Boolean,
348             saveState: Boolean
349         ): Builder {
350             popUpToRouteClass = route
351             popUpToId = -1
352             popUpToInclusive = inclusive
353             popUpToSaveState = saveState
354             return this
355         }
356 
357         @JvmOverloads
358         @Suppress("MissingGetterMatchingBuilder")
359         @OptIn(InternalSerializationApi::class)
setPopUpTonull360         public actual fun <T : Any> setPopUpTo(
361             route: T,
362             inclusive: Boolean,
363             saveState: Boolean
364         ): Builder {
365             popUpToRouteObject = route
366             setPopUpTo(route::class.serializer().generateHashCode(), inclusive, saveState)
367             return this
368         }
369 
370         /**
371          * Sets a custom Animation or Animator resource for the enter animation.
372          *
373          * Note: Animator resources are not supported for navigating to a new Activity
374          *
375          * @param enterAnim Custom animation to run
376          * @return this Builder
377          * @see NavOptions.enterAnim
378          */
setEnterAnimnull379         public fun setEnterAnim(@AnimRes @AnimatorRes enterAnim: Int): Builder {
380             this.enterAnim = enterAnim
381             return this
382         }
383 
384         /**
385          * Sets a custom Animation or Animator resource for the exit animation.
386          *
387          * Note: Animator resources are not supported for navigating to a new Activity
388          *
389          * @param exitAnim Custom animation to run
390          * @return this Builder
391          * @see NavOptions.exitAnim
392          */
setExitAnimnull393         public fun setExitAnim(@AnimRes @AnimatorRes exitAnim: Int): Builder {
394             this.exitAnim = exitAnim
395             return this
396         }
397 
398         /**
399          * Sets a custom Animation or Animator resource for the enter animation when popping off the
400          * back stack.
401          *
402          * Note: Animator resources are not supported for navigating to a new Activity
403          *
404          * @param popEnterAnim Custom animation to run
405          * @return this Builder
406          * @see NavOptions.popEnterAnim
407          */
setPopEnterAnimnull408         public fun setPopEnterAnim(@AnimRes @AnimatorRes popEnterAnim: Int): Builder {
409             this.popEnterAnim = popEnterAnim
410             return this
411         }
412 
413         /**
414          * Sets a custom Animation or Animator resource for the exit animation when popping off the
415          * back stack.
416          *
417          * Note: Animator resources are not supported for navigating to a new Activity
418          *
419          * @param popExitAnim Custom animation to run
420          * @return this Builder
421          * @see NavOptions.popExitAnim
422          */
setPopExitAnimnull423         public fun setPopExitAnim(@AnimRes @AnimatorRes popExitAnim: Int): Builder {
424             this.popExitAnim = popExitAnim
425             return this
426         }
427 
buildnull428         public actual fun build(): NavOptions {
429             return if (popUpToRoute != null) {
430                 NavOptions(
431                     singleTop,
432                     restoreState,
433                     popUpToRoute,
434                     popUpToInclusive,
435                     popUpToSaveState,
436                     enterAnim,
437                     exitAnim,
438                     popEnterAnim,
439                     popExitAnim
440                 )
441             } else if (popUpToRouteClass != null) {
442                 NavOptions(
443                     singleTop,
444                     restoreState,
445                     popUpToRouteClass,
446                     popUpToInclusive,
447                     popUpToSaveState,
448                     enterAnim,
449                     exitAnim,
450                     popEnterAnim,
451                     popExitAnim
452                 )
453             } else if (popUpToRouteObject != null) {
454                 NavOptions(
455                     singleTop,
456                     restoreState,
457                     popUpToRouteObject!!,
458                     popUpToInclusive,
459                     popUpToSaveState,
460                     enterAnim,
461                     exitAnim,
462                     popEnterAnim,
463                     popExitAnim
464                 )
465             } else {
466                 NavOptions(
467                     singleTop,
468                     restoreState,
469                     popUpToId,
470                     popUpToInclusive,
471                     popUpToSaveState,
472                     enterAnim,
473                     exitAnim,
474                     popEnterAnim,
475                     popExitAnim
476                 )
477             }
478         }
479     }
480 }
481