1 /*
<lambda>null2 * Copyright (C) 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 package com.android.providers.media.tools.photopickerv2.docsui
17 
18 import android.app.Activity
19 import android.net.Uri
20 import android.os.Build
21 import android.widget.VideoView
22 import androidx.activity.compose.rememberLauncherForActivityResult
23 import androidx.activity.result.contract.ActivityResultContracts
24 import androidx.annotation.RequiresApi
25 import androidx.compose.foundation.layout.Arrangement
26 import androidx.compose.foundation.layout.Column
27 import androidx.compose.foundation.layout.Row
28 import androidx.compose.foundation.layout.Spacer
29 import androidx.compose.foundation.layout.fillMaxSize
30 import androidx.compose.foundation.layout.fillMaxWidth
31 import androidx.compose.foundation.layout.height
32 import androidx.compose.foundation.layout.padding
33 import androidx.compose.foundation.layout.width
34 import androidx.compose.foundation.rememberScrollState
35 import androidx.compose.foundation.verticalScroll
36 import androidx.compose.material3.ButtonDefaults
37 import androidx.compose.material3.HorizontalDivider
38 import androidx.compose.material3.Text
39 import androidx.compose.runtime.Composable
40 import androidx.compose.runtime.collectAsState
41 import androidx.compose.runtime.getValue
42 import androidx.compose.runtime.mutableStateOf
43 import androidx.compose.runtime.remember
44 import androidx.compose.runtime.setValue
45 import androidx.compose.ui.Alignment
46 import androidx.compose.ui.Modifier
47 import androidx.compose.ui.graphics.Color
48 import androidx.compose.ui.platform.LocalContext
49 import androidx.compose.ui.res.stringResource
50 import androidx.compose.ui.text.font.FontWeight
51 import androidx.compose.ui.unit.dp
52 import androidx.compose.ui.unit.sp
53 import androidx.compose.ui.viewinterop.AndroidView
54 import androidx.lifecycle.viewmodel.compose.viewModel
55 import com.android.providers.media.tools.photopickerv2.R
56 import com.android.providers.media.tools.photopickerv2.utils.ButtonComponent
57 import com.android.providers.media.tools.photopickerv2.utils.MetaDataDetails
58 import com.android.providers.media.tools.photopickerv2.utils.SwitchComponent
59 import com.android.providers.media.tools.photopickerv2.utils.TextFieldComponent
60 import com.android.providers.media.tools.photopickerv2.utils.isImage
61 import com.android.providers.media.tools.photopickerv2.utils.resetMedia
62 import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
63 import com.bumptech.glide.integration.compose.GlideImage
64 
65 /**
66  * This is the screen for the DocsUI tab.
67  */
68 @RequiresApi(Build.VERSION_CODES.O)
69 @OptIn(ExperimentalGlideComposeApi::class)
70 @Composable
71 fun DocsUIScreen(docsUIViewModel: DocsUIViewModel = viewModel()) {
72     val context = LocalContext.current
73 
74     // The ACTION_GET_CONTENT intent is selected by default
75     var selectedButton by remember { mutableStateOf<Int?>(R.string.action_get_content) }
76 
77     var allowMultiple by remember { mutableStateOf(false) }
78 
79     var isActionGetContentSelected by remember { mutableStateOf(true) }
80     var isOpenDocumentSelected by remember { mutableStateOf(false) }
81     var isCreateDocumentSelected by remember { mutableStateOf(false) }
82 
83     var allowCustomMimeType by remember { mutableStateOf(false) }
84     var selectedMimeType by remember { mutableStateOf("") }
85     var customMimeTypeInput by remember { mutableStateOf("") }
86 
87     var showImagesOnly by remember { mutableStateOf(false) }
88     var showVideosOnly by remember { mutableStateOf(false) }
89 
90     // Meta Data Details
91     var showMetaData by remember { mutableStateOf(false) }
92 
93     // Color of ACTION_GET_CONTENT and OPEN_DOCUMENT button
94     val getContentColor = if (isActionGetContentSelected){
95         ButtonDefaults.buttonColors()
96     } else ButtonDefaults.buttonColors(Color.Gray)
97 
98     val openDocumentColor = if (isOpenDocumentSelected) {
99         ButtonDefaults.buttonColors()
100     } else ButtonDefaults.buttonColors(Color.Gray)
101 
102     val createDocumentColor = if (isCreateDocumentSelected) {
103         ButtonDefaults.buttonColors()
104     } else ButtonDefaults.buttonColors(Color.Gray)
105 
106     val openDocumentTreeColor = if (!isActionGetContentSelected &&
107         !isOpenDocumentSelected &&
108         !isCreateDocumentSelected) {
109         ButtonDefaults.buttonColors()
110     } else ButtonDefaults.buttonColors(Color.Gray)
111 
112     // For handling the result of the photo picking activity
113     val launcher = rememberLauncherForActivityResult(
114         contract = ActivityResultContracts.StartActivityForResult()
115     ) { result ->
116         if (result.resultCode == Activity.RESULT_OK) {
117             // Get the clipData containing multiple selected items.
118             val clipData = result.data?.clipData
119             val uris = mutableListOf<Uri>() // An empty list to store the selected URIs
120 
121             // If multiple items are selected (clipData is not null), iterate through the items.
122             if (clipData != null) {
123                 // Add each selected item to the URIs list,
124                 // up to the maxMediaItemsDisplayed limit if multiple selection is allowed
125                 for (i in 0 until clipData.itemCount) {
126                     uris.add(clipData.getItemAt(i).uri)
127                 }
128             } else {
129                 // If only a single item is selected, add its URI to the list
130                 result.data?.data?.let { uris.add(it) }
131             }
132 
133             // Update the ViewModel with the list of selected URIs
134             docsUIViewModel.updateSelectedMediaList(uris)
135         }
136     }
137 
138     val resultMedia by docsUIViewModel.selectedMedia.collectAsState()
139 
140     fun resetFeatureComponents(
141         isGetContentSelected: Boolean,
142         isOpenDocumentIntentSelected: Boolean,
143         isCreateDocumentIntentSelected: Boolean,
144         selectedButtonType: Int
145     ) {
146         isActionGetContentSelected = isGetContentSelected
147         isOpenDocumentSelected = isOpenDocumentIntentSelected
148         isCreateDocumentSelected = isCreateDocumentIntentSelected
149         selectedButton = selectedButtonType
150         allowMultiple = false
151         showImagesOnly = false
152         showVideosOnly = false
153         selectedMimeType = ""
154         resetMedia(docsUIViewModel)
155         allowCustomMimeType = false
156         customMimeTypeInput = ""
157     }
158 
159     Column(
160         modifier = Modifier
161             .verticalScroll(rememberScrollState())
162             .padding(16.dp)
163             .fillMaxWidth()
164     ){
165         Text(
166             text = stringResource(id = R.string.tab_docsui),
167             fontWeight = FontWeight.Bold,
168             fontSize = 25.sp,
169             modifier = Modifier.padding(16.dp)
170         )
171 
172         Row (
173             modifier = Modifier
174                 .fillMaxWidth()
175                 .padding(vertical = 5.dp),
176             verticalAlignment = Alignment.CenterVertically,
177             horizontalArrangement = Arrangement.spacedBy(8.dp)
178         ){
179             ButtonComponent(
180                 label = stringResource(id = R.string.action_get_content),
181                 onClick = {
182                     resetFeatureComponents(
183                         isGetContentSelected = true,
184                         isOpenDocumentIntentSelected = false,
185                         isCreateDocumentIntentSelected = false,
186                         selectedButtonType = R.string.action_get_content
187                     )
188                 },
189                 modifier = Modifier.weight(1f),
190                 colors = getContentColor
191             )
192 
193             ButtonComponent(
194                 label = stringResource(R.string.open_document),
195                 onClick = {
196                     resetFeatureComponents(
197                         isGetContentSelected = false,
198                         isOpenDocumentIntentSelected = true,
199                         isCreateDocumentIntentSelected = false,
200                         selectedButtonType = R.string.open_document
201                     )
202                 },
203                 modifier = Modifier.weight(1f),
204                 colors = openDocumentColor
205             )
206         }
207 
208         Row (
209             modifier = Modifier
210                 .fillMaxWidth()
211                 .padding(vertical = 5.dp),
212             verticalAlignment = Alignment.CenterVertically,
213             horizontalArrangement = Arrangement.spacedBy(8.dp)
214         ){
215             ButtonComponent(
216                 label = stringResource(id = R.string.open_document_tree),
217                 onClick = {
218                     resetFeatureComponents(
219                         isGetContentSelected = false,
220                         isOpenDocumentIntentSelected = false,
221                         isCreateDocumentIntentSelected = false,
222                         selectedButtonType = R.string.open_document_tree
223                     )
224                 },
225                 modifier = Modifier.weight(1f),
226                 colors = openDocumentTreeColor
227             )
228 
229             ButtonComponent(
230                 label = stringResource(R.string.create_document),
231                 onClick = {
232                     resetFeatureComponents(
233                         isGetContentSelected = false,
234                         isOpenDocumentIntentSelected = false,
235                         isCreateDocumentIntentSelected = true,
236                         selectedButtonType = R.string.create_document
237                     )
238                 },
239                 modifier = Modifier.weight(1f),
240                 colors = createDocumentColor
241             )
242         }
243 
244         if (isActionGetContentSelected || isOpenDocumentSelected){
245             // SHOW ONLY IMAGES OR VIDEOS
246             if (!allowCustomMimeType) {
247                 Row(
248                     modifier = Modifier
249                         .fillMaxWidth()
250                         .padding(vertical = 5.dp),
251                     verticalAlignment = Alignment.CenterVertically
252                 ) {
253                     Column (modifier = Modifier.weight(1f)){
254                         SwitchComponent(
255                             label = stringResource(R.string.show_images_only),
256                             checked = showImagesOnly,
257                             onCheckedChange = {
258                                 showImagesOnly = it
259                                 if (it) {
260                                     showVideosOnly = false
261                                     selectedMimeType = "image/*"
262                                 } else if (!showImagesOnly && !showVideosOnly) {
263                                     selectedMimeType = ""
264                                 }
265                             }
266                         )
267                     }
268 
269                     Spacer(modifier = Modifier.width(6.dp))
270 
271                     Column (modifier = Modifier.weight(1f)){
272                         SwitchComponent(
273                             label = stringResource(R.string.show_videos_only),
274                             checked = showVideosOnly,
275                             onCheckedChange = {
276                                 showVideosOnly = it
277                                 if (it) {
278                                     showImagesOnly = false
279                                     selectedMimeType = "video/*"
280                                 } else if (!showImagesOnly && !showVideosOnly) {
281                                     selectedMimeType = ""
282                                 }
283                             }
284                         )
285                     }
286                 }
287             }
288 
289             // Allow Custom Mime Type
290             SwitchComponent(
291                 label = stringResource(id = R.string.allow_custom_mime_type),
292                 checked = allowCustomMimeType,
293                 onCheckedChange = {
294                     allowCustomMimeType = it
295                 }
296             )
297 
298             if (allowCustomMimeType){
299                 TextFieldComponent(
300                     // Custom Mime Type Input
301                     value = customMimeTypeInput,
302                     onValueChange = { customMimeType ->
303                         customMimeTypeInput = customMimeType
304                     },
305                     label = stringResource(id = R.string.enter_mime_type)
306                 )
307             }
308 
309             // Multiple Selection
310             SwitchComponent(
311                 label = stringResource(id = R.string.allow_multiple_selection),
312                 checked = allowMultiple,
313                 onCheckedChange = {
314                     allowMultiple = it
315                 }
316             )
317         }
318 
319         // Pick Media Button
320         ButtonComponent(
321             label = if (!isCreateDocumentSelected) {
322                 stringResource(R.string.pick_media)
323             } else {
324                 stringResource(R.string.create_file)
325             },
326             onClick = {
327 
328 
329                 // Resetting the custom Mime Type Box when allowCustomMimeType is unselected
330                 if (!allowCustomMimeType){
331                     customMimeTypeInput = ""
332                 }
333 
334                 /*  TODO: (@adityasngh) please check the URI below and fix this intent.
335                 // For CREATE_DOCUMENT intent
336                 val initialUri = Uri.parse("content://some/initial/uri")
337 
338                 val errorMessage = docsUIViewModel.validateAndLaunchPicker(
339                     isActionGetContentSelected = isActionGetContentSelected,
340                     isOpenDocumentSelected = isOpenDocumentSelected,
341                     isCreateDocumentSelected = isCreateDocumentSelected,
342                     allowMultiple = allowMultiple,
343                     selectedMimeType = selectedMimeType,
344                     allowCustomMimeType = allowCustomMimeType,
345                     customMimeTypeInput = customMimeTypeInput,
346                     pickerInitialUri = initialUri,
347                     launcher = launcher::launch
348                 )
349                 if (errorMessage != null) {
350                     Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
351                 }
352                 */
353             }
354         )
355 
356         Spacer(modifier = Modifier.height(16.dp))
357 
358         Column {
359             if (isActionGetContentSelected || isOpenDocumentSelected){
360                 // Switch for showing meta data
361                 SwitchComponent(
362                     label = stringResource(R.string.show_metadata),
363                     checked = showMetaData,
364                     onCheckedChange = { showMetaData = it }
365                 )
366             }
367 
368             if (!isCreateDocumentSelected){
369                 resultMedia.forEach { uri ->
370                     if (showMetaData) {
371                         MetaDataDetails(
372                             uri = uri,
373                             contentResolver = context.contentResolver,
374                             showMetaData = showMetaData,
375                             inDocsUITab = true
376                         )
377                     }
378                     if (isImage(context, uri)) {
379                         // To display image
380                         GlideImage(
381                             model = uri,
382                             contentDescription = null,
383                             modifier = Modifier
384                                 .fillMaxWidth()
385                                 .fillMaxSize()
386                                 .padding(top = 8.dp)
387                         )
388                     } else {
389                         AndroidView(
390                             // To display video
391                             factory = { ctx ->
392                                 VideoView(ctx).apply {
393                                     setVideoURI(uri)
394                                     start()
395                                 }
396                             },
397                             modifier = Modifier
398                                 .fillMaxWidth()
399                                 .height(600.dp)
400                                 .padding(top = 8.dp)
401                         )
402                     }
403                     Spacer(modifier = Modifier.height(20.dp))
404                     HorizontalDivider(thickness = 6.dp)
405                     Spacer(modifier = Modifier.height(17.dp))
406                 }
407             }
408         }
409     }
410 }