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
17 package com.android.photopicker.extensions
18
19 import androidx.navigation.NavController
20 import androidx.navigation.NavOptions
21 import com.android.photopicker.core.navigation.PhotopickerDestinations
22 import com.android.photopicker.core.navigation.PhotopickerDestinations.PHOTO_GRID
23 import com.android.photopicker.core.navigation.PhotopickerDestinations.PREVIEW_MEDIA
24 import com.android.photopicker.core.navigation.PhotopickerDestinations.PREVIEW_SELECTION
25 import com.android.photopicker.data.model.Group
26 import com.android.photopicker.data.model.Media
27 import com.android.photopicker.features.albumgrid.AlbumGridFeature
28 import com.android.photopicker.features.categorygrid.CategoryGridFeature
29 import com.android.photopicker.features.preview.PreviewFeature
30
31 /**
32 * Utility function for navigating to the [PhotopickerDestinations.PHOTO_GRID] route.
33 *
34 * This attempts to reclaim an existing BackStack entry, preserving any previous state that existed.
35 *
36 * If the route is not currently on the BackStack, then this will navigate directly.
37 */
NavControllernull38 fun NavController.navigateToPhotoGrid(navOptions: NavOptions? = null) {
39 // First, check to see if the destination is already the current route.
40 if (this.currentDestination?.route == PHOTO_GRID.route) {
41 // Nothing to do. Return early to prevent navigation animations from triggering.
42 return
43 } else if (
44 // Try to return to the entry that is already on the backstack, so the user's
45 // previous state and scroll position is restored.
46 !this.popBackStack(PHOTO_GRID.route, /* inclusive= */ false, /* saveState= */ false)
47 ) {
48 // Last resort; PHOTO_GRID isn't on the backstack, then navigate directly.
49 this.navigate(PHOTO_GRID.route, navOptions)
50 }
51 }
52
53 /** Utility function for navigating to the [PhotopickerDestinations.PREVIEW_SELECTION] route. */
NavControllernull54 fun NavController.navigateToPreviewSelection(navOptions: NavOptions? = null) {
55 this.navigate(PREVIEW_SELECTION.route, navOptions)
56 }
57
58 /**
59 * Utility function for navigating to the [PhotopickerDestinations.PREVIEW_MEDIA] route.
60 *
61 * Additionally, this adds the relevant media data to the BackStackEntry for the route to use to
62 * avoid refetching it from the provider.
63 *
64 * @param media The media item that should be previewed in full resolution.
65 */
NavControllernull66 fun NavController.navigateToPreviewMedia(media: Media, navOptions: NavOptions? = null) {
67 this.navigate(PREVIEW_MEDIA.route, navOptions)
68 // Media object must be parcellized and passed to the new route so it can be loaded.
69 // This back stack entry is guaranteed to exist since it was just navigated to.
70 this.getBackStackEntry(PREVIEW_MEDIA.route)
71 .savedStateHandle
72 .set(PreviewFeature.PREVIEW_MEDIA_KEY, media)
73 }
74
75 /**
76 * Utility function for navigating to the [PhotopickerDestinations.ALBUM_GRID] route.
77 *
78 * This attempts to reclaim an existing BackStack entry, preserving any previous state that existed.
79 *
80 * If the route is not currently on the BackStack, then this will navigate directly.
81 */
NavControllernull82 fun NavController.navigateToAlbumGrid(navOptions: NavOptions? = null) {
83 // First, check to see if the destination is already the current route.
84 if (this.currentDestination?.route == PhotopickerDestinations.ALBUM_GRID.route) {
85 // Nothing to do. Return early to prevent navigation animations from triggering.
86 return
87 } else if (
88 // Try to return to the entry that is already on the backstack, so the user's
89 // previous state and scroll position is restored.
90 !this.popBackStack(
91 PhotopickerDestinations.ALBUM_GRID.route,
92 /* inclusive= */ false,
93 /* saveState = */ true,
94 )
95 ) {
96 // Last resort; ALBUM_GRID isn't on the backstack, then navigate directly.
97 this.navigate(PhotopickerDestinations.ALBUM_GRID.route, navOptions)
98 }
99 }
100
101 /**
102 * Utility function for navigating to the [PhotopickerDestinations.ALBUM_MEDIA_GRID] route.
103 *
104 * @param album The album for which the media needs to be displayed.
105 */
navigateToAlbumMediaGridnull106 fun NavController.navigateToAlbumMediaGrid(navOptions: NavOptions? = null, album: Group.Album) {
107 this.navigate(PhotopickerDestinations.ALBUM_MEDIA_GRID.route, navOptions)
108
109 // Album object must be parcellized and passed to the new route so it can be loaded.
110 // This back stack entry is guaranteed to exist since it was just navigated to.
111 this.getBackStackEntry(PhotopickerDestinations.ALBUM_MEDIA_GRID.route)
112 .savedStateHandle
113 .set(AlbumGridFeature.ALBUM_KEY, album)
114 }
115
116 /**
117 * Utility function for navigating to the [PhotopickerDestinations.ALBUM_GRID] route.
118 *
119 * This attempts to reclaim an existing BackStack entry, preserving any previous state that existed.
120 *
121 * If the route is not currently on the BackStack, then this will navigate directly.
122 */
navigateToCategoryGridnull123 fun NavController.navigateToCategoryGrid(navOptions: NavOptions? = null) {
124 // First, check to see if the destination is already the current route.
125 if (this.currentDestination?.route == PhotopickerDestinations.ALBUM_GRID.route) {
126 // Nothing to do. Return early to prevent navigation animations from triggering.
127 return
128 } else if (
129 // Try to return to the entry that is already on the backstack, so the user's
130 // previous state and scroll position is restored.
131 !this.popBackStack(
132 PhotopickerDestinations.ALBUM_GRID.route,
133 /* inclusive= */ false,
134 /* saveState = */ true,
135 )
136 ) {
137 // Last resort; ALBUM_GRID for Category isn't on the backstack, then navigate directly.
138 this.navigate(PhotopickerDestinations.ALBUM_GRID.route, navOptions)
139 }
140 }
141
142 /**
143 * Utility function for navigating to the [PhotopickerDestinations.ALBUM_MEDIA_GRID] route for
144 * categories.
145 *
146 * @param album The album for which the media needs to be displayed.
147 */
NavControllernull148 fun NavController.navigateToAlbumMediaGridForCategories(
149 navOptions: NavOptions? = null,
150 album: Group.Album,
151 ) {
152 this.navigate(PhotopickerDestinations.ALBUM_MEDIA_GRID.route, navOptions)
153
154 // Album object must be parcellized and passed to the new route so it can be loaded.
155 // This back stack entry is guaranteed to exist since it was just navigated to.
156 this.getBackStackEntry(PhotopickerDestinations.ALBUM_MEDIA_GRID.route)
157 .savedStateHandle
158 .set(CategoryGridFeature.GROUP_KEY, album)
159 }
160
161 /**
162 * Utility function for navigating to the [PhotopickerDestinations.MEDIA_SET_CONTENT_GRID] route for
163 * categories.
164 *
165 * @param mediaSet The media set for which the media needs to be displayed.
166 */
navigateToMediaSetContentGridnull167 fun NavController.navigateToMediaSetContentGrid(
168 navOptions: NavOptions? = null,
169 mediaSet: Group.MediaSet,
170 ) {
171 this.navigate(PhotopickerDestinations.MEDIA_SET_CONTENT_GRID.route, navOptions)
172
173 // Album object must be parcellized and passed to the new route so it can be loaded.
174 // This back stack entry is guaranteed to exist since it was just navigated to.
175 this.getBackStackEntry(PhotopickerDestinations.MEDIA_SET_CONTENT_GRID.route)
176 .savedStateHandle
177 .set(CategoryGridFeature.GROUP_KEY, mediaSet)
178 }
179
180 /**
181 * Utility function for navigating to the [PhotopickerDestinations.MEDIA_SET_GRID] route for
182 * categories.
183 *
184 * @param category The category for which the media set needs to be displayed.
185 */
navigateToMediaSetGridnull186 fun NavController.navigateToMediaSetGrid(
187 navOptions: NavOptions? = null,
188 category: Group.Category? = null,
189 ) {
190 if (this.currentDestination?.route == PhotopickerDestinations.MEDIA_SET_GRID.route) {
191 // Nothing to do. Return early to prevent navigation animations from triggering.
192 return
193 } else if (
194 // Try to return to the entry that is already on the backstack, so the user's
195 // previous state and scroll position is restored.
196 !this.popBackStack(
197 PhotopickerDestinations.MEDIA_SET_GRID.route,
198 /* inclusive= */ false,
199 /* saveState = */ true,
200 )
201 ) {
202 // Last resort; MEDIA_SET_GRID isn't on the backstack, then navigate directly.
203 this.navigate(PhotopickerDestinations.MEDIA_SET_GRID.route, navOptions)
204 // Category object must be parcellized and passed to the new route so it can be loaded.
205 // This back stack entry is guaranteed to exist since it was just navigated to.
206 this.getBackStackEntry(PhotopickerDestinations.MEDIA_SET_GRID.route)
207 .savedStateHandle
208 .set(CategoryGridFeature.GROUP_KEY, category)
209 }
210 }
211