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.features.highpriorityuifeature 18 19 import androidx.compose.foundation.layout.Column 20 import androidx.compose.foundation.layout.fillMaxSize 21 import androidx.compose.material3.Button 22 import androidx.compose.material3.Surface 23 import androidx.compose.material3.Text 24 import androidx.compose.runtime.Composable 25 import androidx.compose.runtime.getValue 26 import androidx.compose.runtime.setValue 27 import androidx.compose.ui.Modifier 28 import androidx.compose.ui.window.DialogProperties 29 import androidx.navigation.NamedNavArgument 30 import androidx.navigation.NavBackStackEntry 31 import androidx.navigation.NavDeepLink 32 import com.android.photopicker.core.banners.Banner 33 import com.android.photopicker.core.banners.BannerDefinitions 34 import com.android.photopicker.core.banners.BannerState 35 import com.android.photopicker.core.configuration.PhotopickerConfiguration 36 import com.android.photopicker.core.events.RegisteredEventClass 37 import com.android.photopicker.core.features.FeatureManager 38 import com.android.photopicker.core.features.FeatureRegistration 39 import com.android.photopicker.core.features.LocalFeatureManager 40 import com.android.photopicker.core.features.Location 41 import com.android.photopicker.core.features.LocationParams 42 import com.android.photopicker.core.features.PhotopickerUiFeature 43 import com.android.photopicker.core.features.PrefetchResultKey 44 import com.android.photopicker.core.features.Priority 45 import com.android.photopicker.core.navigation.LocalNavController 46 import com.android.photopicker.core.navigation.PhotopickerDestinations.ALBUM_GRID 47 import com.android.photopicker.core.navigation.PhotopickerDestinations.PHOTO_GRID 48 import com.android.photopicker.core.navigation.Route 49 import com.android.photopicker.core.user.UserMonitor 50 import com.android.photopicker.data.DataService 51 import com.android.photopicker.features.simpleuifeature.SimpleUiFeature 52 import kotlinx.coroutines.Deferred 53 54 /** 55 * Test [PhotopickerUiFeature] that renders a simple string to [Location.COMPOSE_TOP] with the 56 * utmost priority. 57 */ 58 class HighPriorityUiFeature : PhotopickerUiFeature { 59 60 companion object Registration : FeatureRegistration { 61 override val TAG: String = "HighPriorityUiFeature" 62 isEnablednull63 override fun isEnabled( 64 config: PhotopickerConfiguration, 65 deferredPrefetchResultsMap: Map<PrefetchResultKey, Deferred<Any?>>, 66 ) = true 67 68 override fun build(featureManager: FeatureManager) = HighPriorityUiFeature() 69 70 val UI_STRING = "I'm super important." 71 val START_ROUTE = "highpriority/start" 72 val START_STRING = "I'm the start location." 73 val DIALOG_ROUTE = "highpriority/dialog" 74 val DIALOG_STRING = "I'm the dialog location." 75 } 76 77 override val token = TAG 78 79 /** Only one banner is claimed */ 80 override val ownedBanners = setOf(BannerDefinitions.CLOUD_CHOOSE_ACCOUNT) 81 82 override suspend fun getBannerPriority( 83 banner: BannerDefinitions, 84 bannerState: BannerState?, 85 config: PhotopickerConfiguration, 86 dataService: DataService, 87 userMonitor: UserMonitor, 88 ): Int { 89 // If the banner reports as being dismissed, don't show it. 90 if (bannerState?.dismissed == true) { 91 return Priority.DISABLED.priority 92 } 93 94 // Otherwise, show it with medium priority. 95 return Priority.HIGH.priority 96 } 97 buildBannernull98 override suspend fun buildBanner( 99 banner: BannerDefinitions, 100 dataService: DataService, 101 userMonitor: UserMonitor, 102 ): Banner { 103 return object : Banner { 104 override val declaration = BannerDefinitions.CLOUD_CHOOSE_ACCOUNT 105 106 @Composable override fun buildTitle() = "Choose Account Title" 107 108 @Composable override fun buildMessage() = "Choose Account Message" 109 } 110 } 111 112 /** Events consumed by the Photo grid */ 113 override val eventsConsumed = emptySet<RegisteredEventClass>() 114 115 /** Events produced by the Photo grid */ 116 override val eventsProduced = emptySet<RegisteredEventClass>() 117 118 /** Compose Location callback from feature framework */ registerLocationsnull119 override fun registerLocations(): List<Pair<Location, Int>> { 120 return listOf(Pair(Location.COMPOSE_TOP, Priority.HIGH.priority)) 121 } 122 123 /** Navigation registration callback from feature framework */ registerNavigationRoutesnull124 override fun registerNavigationRoutes(): Set<Route> { 125 return setOf( 126 object : Route { 127 override val route = START_ROUTE 128 override val initialRoutePriority = Priority.HIGH.priority 129 override val arguments = emptyList<NamedNavArgument>() 130 override val deepLinks = emptyList<NavDeepLink>() 131 override val isDialog = false 132 override val dialogProperties = null 133 override val enterTransition = null 134 override val exitTransition = null 135 override val popEnterTransition = null 136 override val popExitTransition = null 137 138 @Composable 139 override fun composable(navBackStackEntry: NavBackStackEntry?) { 140 start() 141 } 142 }, 143 object : Route { 144 override val route = DIALOG_ROUTE 145 override val initialRoutePriority = Priority.REGISTRATION_ORDER.priority 146 override val arguments = emptyList<NamedNavArgument>() 147 override val deepLinks = emptyList<NavDeepLink>() 148 override val isDialog = true 149 override val dialogProperties = DialogProperties(usePlatformDefaultWidth = false) 150 override val enterTransition = null 151 override val exitTransition = null 152 override val popEnterTransition = null 153 override val popExitTransition = null 154 155 @Composable 156 override fun composable(navBackStackEntry: NavBackStackEntry?) { 157 dialog() 158 } 159 }, 160 161 // This is implemented for PhotopickerNavGraphTest 162 object : Route { 163 override val route = PHOTO_GRID.route 164 override val initialRoutePriority = Priority.LAST.priority 165 override val arguments = emptyList<NamedNavArgument>() 166 override val deepLinks = emptyList<NavDeepLink>() 167 override val isDialog = false 168 override val dialogProperties = null 169 override val enterTransition = null 170 override val exitTransition = null 171 override val popEnterTransition = null 172 override val popExitTransition = null 173 174 @Composable override fun composable(navBackStackEntry: NavBackStackEntry?) {} 175 }, 176 // This is implemented for PhotopickerNavGraphTest 177 object : Route { 178 override val route = ALBUM_GRID.route 179 override val initialRoutePriority = Priority.LAST.priority 180 override val arguments = emptyList<NamedNavArgument>() 181 override val deepLinks = emptyList<NavDeepLink>() 182 override val isDialog = false 183 override val dialogProperties = null 184 override val enterTransition = null 185 override val exitTransition = null 186 override val popEnterTransition = null 187 override val popExitTransition = null 188 189 @Composable override fun composable(navBackStackEntry: NavBackStackEntry?) {} 190 }, 191 ) 192 } 193 194 /* Feature framework compose-at-location callback */ 195 @Composable composenull196 override fun compose(location: Location, modifier: Modifier, params: LocationParams) { 197 when (location) { 198 Location.COMPOSE_TOP -> composeTop() 199 else -> {} 200 } 201 } 202 203 /* Private composable used for the [Location.COMPOSE_TOP] location */ 204 @Composable composeTopnull205 private fun composeTop() { 206 Text(UI_STRING) 207 } 208 209 /** Composes a dialog location, with a button to navigate back to the [START_ROUTE] */ 210 @Composable dialognull211 private fun dialog() { 212 val navController = LocalNavController.current 213 Surface(modifier = Modifier.fillMaxSize()) { 214 Column { 215 Text(DIALOG_STRING) 216 Button(onClick = { navController.navigate(START_ROUTE) }) { 217 Text("navigate to start") 218 } 219 } 220 } 221 } 222 223 /** 224 * Composes the test start location, and if the [SimpleUiFeature] is enabled, also includes a 225 * button that navigates to the [SimpleUiFeature.SIMPLE_ROUTE] 226 */ 227 @Composable startnull228 private fun start() { 229 val featureManager = LocalFeatureManager.current 230 val navController = LocalNavController.current 231 Surface(modifier = Modifier.fillMaxSize()) { 232 Column { 233 Text(START_STRING) 234 235 Button(onClick = { navController.navigate(DIALOG_ROUTE) }) { 236 Text("navigate to dialog") 237 } 238 239 // Optionally add a navigation button if SimpleUiFeature is enabled. 240 if (featureManager.isFeatureEnabled(SimpleUiFeature::class.java)) { 241 Button(onClick = { navController.navigate(SimpleUiFeature.SIMPLE_ROUTE) }) { 242 Text("navigate to simple ui") 243 } 244 } 245 } 246 } 247 } 248 } 249