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.navigationbar
18 
19 import androidx.compose.foundation.layout.Arrangement
20 import androidx.compose.foundation.layout.Row
21 import androidx.compose.foundation.layout.padding
22 import androidx.compose.material3.ButtonDefaults
23 import androidx.compose.material3.FilledTonalButton
24 import androidx.compose.material3.MaterialTheme
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.getValue
27 import androidx.compose.ui.Modifier
28 import androidx.compose.ui.unit.dp
29 import androidx.navigation.compose.currentBackStackEntryAsState
30 import com.android.photopicker.core.features.LocalFeatureManager
31 import com.android.photopicker.core.features.Location
32 import com.android.photopicker.core.navigation.LocalNavController
33 import com.android.photopicker.core.navigation.PhotopickerDestinations
34 import com.android.photopicker.core.theme.CustomAccentColorScheme
35 
36 
37 /* Distance between two navigation buttons */
38 private val MEASUREMENT_SPACER_SIZE = 6.dp
39 
40 private val NAV_BAR_ENABLED_ROUTES = setOf(
41     PhotopickerDestinations.ALBUM_GRID.route,
42     PhotopickerDestinations.PHOTO_GRID.route,
43     )
44 
45 /**
46  * Top of the NavigationBar feature.
47  *
48  * Since NavigationBar doesn't register any routes its top composable is drawn at
49  * [Location.NAVIGATION_BAR] which begins here.
50  *
51  * This composable provides a full width row for the navigation bar and calls the feature manager to
52  * provide [NavigationBarButton]-s for the row.
53  */
54 @Composable
NavigationBarnull55 fun NavigationBar(modifier: Modifier) {
56     // The navigation bar hides itself for certain routes
57     val navController = LocalNavController.current
58     val navBackStackEntry by navController.currentBackStackEntryAsState()
59     val currentRoute = navBackStackEntry?.destination?.route
60     if (currentRoute in NAV_BAR_ENABLED_ROUTES) {
61         Row(
62             // Consume the incoming modifier
63             modifier = modifier,
64             horizontalArrangement = Arrangement.Center,
65         ) {
66             // Buttons are provided by registered features, so request for the features to fill this
67             // content.
68             LocalFeatureManager.current.composeLocation(
69                 Location.NAVIGATION_BAR_NAV_BUTTON,
70                 maxSlots = 2,
71                 modifier = Modifier.padding(MEASUREMENT_SPACER_SIZE)
72             )
73         }
74     }
75 }
76 
77 /**
78  * Composable that can be used to build a consistent button for the [NavigationBarFeature]
79  *
80  * @param onClick the handler to run when the button is clicked.
81  * @param modifier A modifier which is applied directly to the button. This should be the modifier
82  *   that is passed via the Location compose call.
83  * @param isCurrentRoute a function which receives the current
84  *   [NavController.currentDestination.route] and returns true if that route matches the route this
85  *   button represents.
86  * @param buttonContent A composable to render as the button's content. Should most likely be a
87  *   string label.
88  */
89 @Composable
NavigationBarButtonnull90 fun NavigationBarButton(
91     onClick: () -> Unit,
92     modifier: Modifier,
93     isCurrentRoute: (String) -> Boolean,
94     buttonContent: @Composable () -> Unit
95 ) {
96     val navController = LocalNavController.current
97     val navBackStackEntry by navController.currentBackStackEntryAsState()
98     val currentRoute = navBackStackEntry?.destination?.route
99 
100     FilledTonalButton(
101         onClick = onClick,
102         modifier = modifier,
103         colors =
104             if (isCurrentRoute(currentRoute ?: "")) {
105                 ButtonDefaults.filledTonalButtonColors(
106                     containerColor = CustomAccentColorScheme.current
107                         .getAccentColorIfDefinedOrElse(
108                             /* fallback */ MaterialTheme.colorScheme.primary
109                         ),
110                     contentColor = CustomAccentColorScheme.current
111                         .getTextColorForAccentComponentsIfDefinedOrElse(
112                             /* fallback */ MaterialTheme.colorScheme.onPrimary
113                         ),
114                 )
115             } else {
116                 ButtonDefaults.filledTonalButtonColors()
117             },
118     ) {
119         buttonContent()
120     }
121 }
122