1 /*
<lambda>null2  * 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.photogrid
18 
19 import androidx.compose.foundation.gestures.detectHorizontalDragGestures
20 import androidx.compose.foundation.layout.Column
21 import androidx.compose.foundation.layout.fillMaxSize
22 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
23 import androidx.compose.material3.Text
24 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.getValue
27 import androidx.compose.runtime.remember
28 import androidx.compose.ui.Modifier
29 import androidx.compose.ui.input.pointer.pointerInput
30 import androidx.compose.ui.res.stringResource
31 import androidx.lifecycle.compose.collectAsStateWithLifecycle
32 import androidx.paging.compose.collectAsLazyPagingItems
33 import com.android.photopicker.R
34 import com.android.photopicker.core.components.MediaGridItem
35 import com.android.photopicker.core.components.mediaGrid
36 import com.android.photopicker.core.configuration.LocalPhotopickerConfiguration
37 import com.android.photopicker.core.features.LocalFeatureManager
38 import com.android.photopicker.core.navigation.LocalNavController
39 import com.android.photopicker.core.navigation.PhotopickerDestinations
40 import com.android.photopicker.core.navigation.PhotopickerDestinations.PHOTO_GRID
41 import com.android.photopicker.core.obtainViewModel
42 import com.android.photopicker.core.selection.LocalSelection
43 import com.android.photopicker.core.theme.LocalWindowSizeClass
44 import com.android.photopicker.extensions.navigateToAlbumGrid
45 import com.android.photopicker.extensions.navigateToPhotoGrid
46 import com.android.photopicker.extensions.navigateToPreviewMedia
47 import com.android.photopicker.features.albumgrid.AlbumGridFeature
48 import com.android.photopicker.features.navigationbar.NavigationBarButton
49 import com.android.photopicker.features.preview.PreviewFeature
50 
51 /**
52  * Primary composable for drawing the main PhotoGrid on [PhotopickerDestinations.PHOTO_GRID]
53  *
54  * @param viewModel - A viewModel override for the composable. Normally, this is fetched via hilt
55  *   from the backstack entry by using obtainViewModel()
56  */
57 @Composable
58 fun PhotoGrid(viewModel: PhotoGridViewModel = obtainViewModel()) {
59     val navController = LocalNavController.current
60     val items = viewModel.data.collectAsLazyPagingItems()
61     val featureManager = LocalFeatureManager.current
62     val isPreviewEnabled = remember { featureManager.isFeatureEnabled(PreviewFeature::class.java) }
63 
64     val state = rememberLazyGridState()
65 
66     val selection by LocalSelection.current.flow.collectAsStateWithLifecycle()
67 
68     /* Use the expanded layout any time the Width is Medium or larger. */
69     val isExpandedScreen: Boolean =
70         when (LocalWindowSizeClass.current.widthSizeClass) {
71             WindowWidthSizeClass.Medium -> true
72             WindowWidthSizeClass.Expanded -> true
73             else -> false
74         }
75 
76     val selectionLimit = LocalPhotopickerConfiguration.current.selectionLimit
77     val selectionLimitExceededMessage =
78         stringResource(R.string.photopicker_selection_limit_exceeded_snackbar, selectionLimit)
79 
80     Column(
81         modifier =
82             Modifier.fillMaxSize().pointerInput(Unit) {
83                 detectHorizontalDragGestures(
84                     onHorizontalDrag = { _, dragAmount ->
85                         // This may need some additional fine tuning by looking at a certain
86                         // distance in dragAmount, but initial testing suggested this worked
87                         // pretty well as is.
88                         if (dragAmount < 0) {
89                             // Negative is a left swipe
90                             if (featureManager.isFeatureEnabled(AlbumGridFeature::class.java))
91                                 navController.navigateToAlbumGrid()
92                         }
93                     }
94                 )
95             }
96     ) {
97         mediaGrid(
98             items = items,
99             isExpandedScreen = isExpandedScreen,
100             selection = selection,
101             onItemClick = { item ->
102                 if (item is MediaGridItem.MediaItem) {
103                     viewModel.handleGridItemSelection(
104                         item = item.media,
105                         selectionLimitExceededMessage = selectionLimitExceededMessage
106                     )
107                 }
108             },
109             onItemLongPress = { item ->
110                 // If the [PreviewFeature] is enabled, launch the preview route.
111                 if (isPreviewEnabled) {
112                     if (item is MediaGridItem.MediaItem)
113                         navController.navigateToPreviewMedia(item.media)
114                 }
115             },
116             state = state,
117         )
118     }
119 }
120 
121 /**
122  * The navigation button for the main photo grid. Composable for
123  * [Location.NAVIGATION_BAR_NAV_BUTTON]
124  */
125 @Composable
PhotoGridNavButtonnull126 fun PhotoGridNavButton(modifier: Modifier) {
127     val navController = LocalNavController.current
128 
129     NavigationBarButton(
130         onClick = navController::navigateToPhotoGrid,
131         modifier = modifier,
132         isCurrentRoute = { route -> route == PHOTO_GRID.route },
133     ) {
134         Text(stringResource(R.string.photopicker_photos_nav_button_label))
135     }
136 }
137