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.simpleuifeature
18 
19 import androidx.compose.material3.Text
20 import androidx.compose.material3.TextButton
21 import androidx.compose.runtime.Composable
22 import androidx.compose.runtime.getValue
23 import androidx.compose.runtime.setValue
24 import androidx.compose.ui.Modifier
25 import androidx.navigation.NamedNavArgument
26 import androidx.navigation.NavBackStackEntry
27 import androidx.navigation.NavDeepLink
28 import com.android.photopicker.core.banners.Banner
29 import com.android.photopicker.core.banners.BannerDefinitions
30 import com.android.photopicker.core.banners.BannerState
31 import com.android.photopicker.core.configuration.PhotopickerConfiguration
32 import com.android.photopicker.core.events.RegisteredEventClass
33 import com.android.photopicker.core.features.FeatureManager
34 import com.android.photopicker.core.features.FeatureRegistration
35 import com.android.photopicker.core.features.Location
36 import com.android.photopicker.core.features.LocationParams
37 import com.android.photopicker.core.features.PhotopickerUiFeature
38 import com.android.photopicker.core.features.PrefetchResultKey
39 import com.android.photopicker.core.features.Priority
40 import com.android.photopicker.core.navigation.Route
41 import com.android.photopicker.core.user.UserMonitor
42 import com.android.photopicker.data.DataService
43 import com.android.photopicker.features.overflowmenu.OverflowMenuItem
44 import kotlinx.coroutines.Deferred
45 
46 /** Test [PhotopickerUiFeature] that renders a simple string to [Location.COMPOSE_TOP] */
47 open class SimpleUiFeature : PhotopickerUiFeature {
48 
49     companion object Registration : FeatureRegistration {
50         override val TAG: String = "SimpleUiFeature"
51 
isEnablednull52         override fun isEnabled(
53             config: PhotopickerConfiguration,
54             deferredPrefetchResultsMap: Map<PrefetchResultKey, Deferred<Any?>>,
55         ) = true
56 
57         override fun build(featureManager: FeatureManager) = SimpleUiFeature()
58 
59         val UI_STRING = "I'm a simple string, from a SimpleUiFeature"
60         val SIMPLE_ROUTE = "simple"
61         val BUTTON_LABEL = "Simple"
62     }
63 
64     override val token = TAG
65 
66     /** Only one banner is claimed */
67     override val ownedBanners = setOf(BannerDefinitions.PRIVACY_EXPLAINER)
68 
69     override suspend fun getBannerPriority(
70         banner: BannerDefinitions,
71         bannerState: BannerState?,
72         config: PhotopickerConfiguration,
73         dataService: DataService,
74         userMonitor: UserMonitor,
75     ): Int {
76         // If the banner reports as being dismissed, don't show it.
77         if (bannerState?.dismissed == true) {
78             return Priority.DISABLED.priority
79         }
80 
81         // Otherwise, show it with medium priority.
82         return Priority.MEDIUM.priority
83     }
84 
buildBannernull85     override suspend fun buildBanner(
86         banner: BannerDefinitions,
87         dataService: DataService,
88         userMonitor: UserMonitor,
89     ): Banner {
90         return object : Banner {
91             override val declaration = BannerDefinitions.PRIVACY_EXPLAINER
92 
93             @Composable override fun buildTitle() = "Privacy Explainer Title"
94 
95             @Composable override fun buildMessage() = "Privacy Explainer Message"
96         }
97     }
98 
99     override val eventsConsumed = emptySet<RegisteredEventClass>()
100 
101     override val eventsProduced = emptySet<RegisteredEventClass>()
102 
103     /** Compose Location callback from feature framework */
registerLocationsnull104     override fun registerLocations(): List<Pair<Location, Int>> {
105         return listOf(
106             Pair(Location.COMPOSE_TOP, Priority.REGISTRATION_ORDER.priority),
107             Pair(Location.OVERFLOW_MENU_ITEMS, Priority.REGISTRATION_ORDER.priority),
108             Pair(Location.SELECTION_BAR_SECONDARY_ACTION, Priority.REGISTRATION_ORDER.priority),
109         )
110     }
111 
112     /** Navigation registration callback from feature framework */
registerNavigationRoutesnull113     override fun registerNavigationRoutes(): Set<Route> {
114         return setOf(
115             object : Route {
116                 override val route = SIMPLE_ROUTE
117                 override val initialRoutePriority = Priority.MEDIUM.priority
118                 override val arguments = emptyList<NamedNavArgument>()
119                 override val deepLinks = emptyList<NavDeepLink>()
120                 override val isDialog = false
121                 override val dialogProperties = null
122                 override val enterTransition = null
123                 override val exitTransition = null
124                 override val popEnterTransition = null
125                 override val popExitTransition = null
126 
127                 @Composable
128                 override fun composable(navBackStackEntry: NavBackStackEntry?) {
129                     simpleRoute()
130                 }
131             }
132         )
133     }
134 
135     /* Feature framework compose-at-location callback */
136     @Composable
composenull137     override fun compose(location: Location, modifier: Modifier, params: LocationParams) {
138         when (location) {
139             Location.COMPOSE_TOP -> composeTop(params)
140             Location.SELECTION_BAR_SECONDARY_ACTION -> selectionBarAction()
141             Location.OVERFLOW_MENU_ITEMS -> overflowMenuItem(params)
142             else -> {}
143         }
144     }
145 
146     /* Private composable used for the [Location.COMPOSE_TOP] location */
147     @Composable
composeTopnull148     private fun composeTop(params: LocationParams) {
149         TextButton(
150             onClick = {
151                 val clickHandler = params as? LocationParams.WithClickAction
152                 clickHandler?.onClick()
153             }
154         ) {
155             Text(UI_STRING)
156         }
157     }
158 
159     /* Composes the [SIMPLE_ROUTE] */
160     @Composable
simpleRoutenull161     private fun simpleRoute() {
162         Text(UI_STRING)
163     }
164 
165     /* Composes the action button for the selection bar */
166     @Composable
selectionBarActionnull167     private fun selectionBarAction() {
168         TextButton(onClick = {}) { Text(BUTTON_LABEL) }
169     }
170 
171     @Composable
overflowMenuItemnull172     private fun overflowMenuItem(params: LocationParams) {
173         val clickAction = params as? LocationParams.WithClickAction
174         OverflowMenuItem(label = BUTTON_LABEL, onClick = { clickAction?.onClick() })
175     }
176 }
177