|
@@ -1,16 +1,18 @@
|
|
|
package com.convenient.android.lib.ui.sample.media
|
|
|
|
|
|
-import android.annotation.SuppressLint
|
|
|
+import android.Manifest
|
|
|
+import android.app.Activity
|
|
|
import android.content.Intent
|
|
|
+import android.content.pm.PackageManager
|
|
|
+import android.net.Uri
|
|
|
import android.os.Build
|
|
|
+import android.os.Environment
|
|
|
+import android.provider.Settings
|
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
|
-import androidx.annotation.RequiresApi
|
|
|
import androidx.compose.foundation.Image
|
|
|
import androidx.compose.foundation.background
|
|
|
import androidx.compose.foundation.layout.*
|
|
|
-import androidx.compose.foundation.lazy.LazyColumn
|
|
|
-import androidx.compose.foundation.lazy.items
|
|
|
import androidx.compose.foundation.rememberScrollState
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
@@ -18,15 +20,13 @@ import androidx.compose.foundation.verticalScroll
|
|
|
import androidx.compose.material.*
|
|
|
import androidx.compose.material.icons.Icons
|
|
|
import androidx.compose.material.icons.filled.Delete
|
|
|
-import androidx.compose.material3.MaterialTheme
|
|
|
-import androidx.compose.runtime.Composable
|
|
|
-import androidx.compose.runtime.collectAsState
|
|
|
+import androidx.compose.material3.ElevatedButton
|
|
|
+import androidx.compose.runtime.*
|
|
|
import androidx.compose.ui.Alignment
|
|
|
import androidx.compose.ui.Modifier
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
-import androidx.compose.ui.res.painterResource
|
|
|
import androidx.compose.ui.res.vectorResource
|
|
|
import androidx.compose.ui.text.TextStyle
|
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
@@ -36,14 +36,15 @@ import androidx.compose.ui.unit.dp
|
|
|
import androidx.compose.ui.unit.sp
|
|
|
import androidx.constraintlayout.compose.ConstraintLayout
|
|
|
import androidx.constraintlayout.compose.Dimension
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
|
import com.convenient.android.common.extension.lengthToFitMemorySize
|
|
|
import com.convenient.android.common.extension.spSave
|
|
|
import com.convenient.android.common.extension.toFile
|
|
|
+import com.convenient.android.common.utils.ToastUtil
|
|
|
import com.convenient.android.common.utils.date.DateUtils
|
|
|
-import com.convenient.android.common.utils.image.BitmapUtils
|
|
|
import com.convenient.android.lib.R
|
|
|
-import com.kdanmobile.jetpackcompose.sample.ui.theme.SampleTheme
|
|
|
+import com.convenient.android.lib.ui.theme.SampleTheme
|
|
|
|
|
|
/**
|
|
|
* @classname:
|
|
@@ -52,10 +53,28 @@ import com.kdanmobile.jetpackcompose.sample.ui.theme.SampleTheme
|
|
|
* description:
|
|
|
*/
|
|
|
|
|
|
+@Composable
|
|
|
+fun MediaPage(){
|
|
|
+ val context = LocalContext.current
|
|
|
+
|
|
|
+ var rememberPermission by remember {
|
|
|
+ mutableStateOf(ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
|
|
|
+ }
|
|
|
+ if (rememberPermission){
|
|
|
+ MediaFilesPage()
|
|
|
+ }else{
|
|
|
+ PermissionPage{
|
|
|
+ rememberPermission = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
|
|
|
@Composable
|
|
|
fun MediaFilesPage() {
|
|
|
+
|
|
|
val viewModel: MediaFilesViewModel = viewModel()
|
|
|
+
|
|
|
val context = LocalContext.current
|
|
|
|
|
|
val chooseDirLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult(), onResult = {
|
|
@@ -66,7 +85,6 @@ fun MediaFilesPage() {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
-
|
|
|
Column(
|
|
|
modifier = Modifier
|
|
|
.fillMaxWidth()
|
|
@@ -80,10 +98,22 @@ fun MediaFilesPage() {
|
|
|
) {
|
|
|
val (btnFromFiles, btnFromMediaStore, btnChooseDir) = createRefs()
|
|
|
|
|
|
- Button(onClick = { viewModel.changeQueryType(MediaQueryType.FILES) }, modifier = Modifier
|
|
|
+ ElevatedButton(
|
|
|
+ onClick = { chooseDirLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) },
|
|
|
+ modifier = Modifier
|
|
|
+ .fillMaxWidth()
|
|
|
+ .constrainAs(btnChooseDir) {
|
|
|
+ top.linkTo(parent.top)
|
|
|
+ start.linkTo(parent.start)
|
|
|
+ end.linkTo(parent.end)
|
|
|
+ }) {
|
|
|
+ Text(text = "选择文件夹")
|
|
|
+ }
|
|
|
+
|
|
|
+ ElevatedButton(onClick = { viewModel.changeQueryType(MediaQueryType.FILES) }, modifier = Modifier
|
|
|
.padding(end = 8.dp)
|
|
|
.constrainAs(btnFromFiles) {
|
|
|
- top.linkTo(parent.top)
|
|
|
+ top.linkTo(btnChooseDir.bottom)
|
|
|
start.linkTo(parent.start)
|
|
|
end.linkTo(btnFromMediaStore.start)
|
|
|
width = Dimension.fillToConstraints
|
|
@@ -92,7 +122,7 @@ fun MediaFilesPage() {
|
|
|
|
|
|
}
|
|
|
|
|
|
- Button(onClick = { viewModel.changeQueryType(MediaQueryType.MEDIA_STORE) },
|
|
|
+ ElevatedButton(onClick = { viewModel.changeQueryType(MediaQueryType.MEDIA_STORE) },
|
|
|
modifier = Modifier
|
|
|
.padding(start = 8.dp)
|
|
|
.constrainAs(btnFromMediaStore) {
|
|
@@ -103,18 +133,6 @@ fun MediaFilesPage() {
|
|
|
}) {
|
|
|
Text(text = "从MediaStore获取")
|
|
|
}
|
|
|
- Button(
|
|
|
- onClick = { chooseDirLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) },
|
|
|
- modifier = Modifier
|
|
|
- .fillMaxWidth()
|
|
|
- .constrainAs(btnChooseDir) {
|
|
|
- top.linkTo(btnFromFiles.bottom)
|
|
|
- start.linkTo(parent.start)
|
|
|
- end.linkTo(parent.end)
|
|
|
- }) {
|
|
|
- Text(text = "选择文件夹")
|
|
|
- }
|
|
|
-
|
|
|
}
|
|
|
|
|
|
QueryInfoPage(viewModel = viewModel)
|
|
@@ -129,37 +147,81 @@ fun MediaFilesPage() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+@Composable
|
|
|
+private fun PermissionPage(permissionCallback : ()-> Unit) {
|
|
|
+
|
|
|
+ val context = LocalContext.current
|
|
|
+ val androidRStoragePermissionLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult(), onResult = {
|
|
|
+ if (it.resultCode == Activity.RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
+ if (Environment.isExternalStorageManager()) {
|
|
|
+ ToastUtil.showToast(context, "访问所有文件权限获取成功")
|
|
|
+ permissionCallback.invoke()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ val permissionLaunch = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission(), onResult = {
|
|
|
+ if (it) {
|
|
|
+ ToastUtil.showToast(context, "存储权限获取成功")
|
|
|
+ permissionCallback.invoke()
|
|
|
+
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ Column(modifier = Modifier
|
|
|
+ .fillMaxWidth()
|
|
|
+ .fillMaxHeight(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
|
|
|
+
|
|
|
+ ElevatedButton(onClick = {
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
+ if (Environment.isExternalStorageManager().not()) {
|
|
|
+ androidRStoragePermissionLauncher.launch(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:${context.packageName}")))
|
|
|
+ } else {
|
|
|
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){
|
|
|
+ ToastUtil.showToast(context, "已获取存储权限")
|
|
|
+ }else{
|
|
|
+ permissionLaunch.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ permissionLaunch.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
|
+ }
|
|
|
+ }) {
|
|
|
+ Text(text = "获取存储权限")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
@Composable
|
|
|
fun QueryInfoPage(viewModel: MediaFilesViewModel) {
|
|
|
val config = viewModel.config.collectAsState()
|
|
|
-
|
|
|
val context = LocalContext.current
|
|
|
+
|
|
|
val addIgnoreFolderLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult(), onResult = {
|
|
|
it.data?.data?.let {
|
|
|
val folder = resolveContentUri(context, it)
|
|
|
- viewModel.changeConfig(config = config.value.copy(ignoreChildFiles = config.value.ignoreChildFiles.toMutableList().apply {
|
|
|
- folder.toFile()?.let { it1 -> add(it1) }
|
|
|
- }))
|
|
|
+ viewModel.addConfigIgnoreChildFiles(folder.toFile())
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+
|
|
|
val addIgnoreFileLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent(), onResult = {
|
|
|
it?.let { uri ->
|
|
|
val folder = UriTofilePath.getFilePathByUri(context, uri)
|
|
|
- viewModel.changeConfig(config = config.value.copy(ignoreChildFiles = config.value.ignoreChildFiles.toMutableList().apply {
|
|
|
- folder?.toFile()?.let { it1 -> add(it1) }
|
|
|
- }))
|
|
|
+ viewModel.addConfigIgnoreChildFiles(folder.toFile())
|
|
|
}
|
|
|
})
|
|
|
|
|
|
val dir = viewModel.dir.collectAsState()
|
|
|
val queryType = viewModel.queryType.collectAsState()
|
|
|
|
|
|
- InfoItem(title = "目录:", info = dir.value)
|
|
|
- InfoItem(title = "获取方式:", info = queryType.value.name)
|
|
|
+ InfoItem(title = "目录:", info = dir.value, infoPosition = InfoPosition.END)
|
|
|
+ InfoItem(title = "获取方式:", info = queryType.value.name, infoPosition = InfoPosition.END)
|
|
|
|
|
|
Column() {
|
|
|
- InfoItem(title = "查询的文件格式:", info = config.value.supportMimeTypes.toString())
|
|
|
+ InfoItem(title = "查询的文件格式:", info = config.value.supportMimeTypes.toString(), infoPosition = InfoPosition.END)
|
|
|
TextField(
|
|
|
keyboardOptions = KeyboardOptions(autoCorrect = false, capitalization = KeyboardCapitalization.None),
|
|
|
value = viewModel.getSupportMimeTypesFiledTextValue(config.value),
|
|
@@ -175,31 +237,34 @@ fun QueryInfoPage(viewModel: MediaFilesViewModel) {
|
|
|
}
|
|
|
|
|
|
Row {
|
|
|
- InfoItem(title = "结果是否包含文件夹:", info = config.value.includeFolder.toString())
|
|
|
val includeFolder = config.value.includeFolder
|
|
|
RadioButton(selected = includeFolder, onClick = {
|
|
|
viewModel.changeConfigIncludeFolder(includeFolder.not())
|
|
|
})
|
|
|
+ InfoItem(title = "结果是否包含文件夹:", info = config.value.includeFolder.toString())
|
|
|
+
|
|
|
}
|
|
|
|
|
|
Row {
|
|
|
- InfoItem(title = "遍历子文件夹:", info = config.value.recursively.toString())
|
|
|
val recursively = config.value.recursively
|
|
|
RadioButton(selected = recursively, onClick = {
|
|
|
viewModel.changeConfig(config.value.copy(recursively = recursively.not()))
|
|
|
})
|
|
|
+ InfoItem(title = "遍历子文件夹:", info = config.value.recursively.toString())
|
|
|
+
|
|
|
}
|
|
|
|
|
|
Column {
|
|
|
InfoItem(title = "忽略的文件:", info = "")
|
|
|
- Button(onClick = { addIgnoreFolderLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) }) {
|
|
|
- Text(text = "选择忽略的文件夹")
|
|
|
- }
|
|
|
+ Row {
|
|
|
+ ElevatedButton(onClick = { addIgnoreFolderLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) }) {
|
|
|
+ Text(text = "选择忽略的文件夹")
|
|
|
+ }
|
|
|
|
|
|
- Button(onClick = { addIgnoreFileLauncher.launch("*/*") }) {
|
|
|
- Text(text = "选择忽略的文件")
|
|
|
+ ElevatedButton(onClick = { addIgnoreFileLauncher.launch("*/*") }) {
|
|
|
+ Text(text = "选择忽略的文件")
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
for (ignoreChildFile in config.value.ignoreChildFiles) {
|
|
|
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
|
|
Text(modifier = Modifier.weight(3F), text = ignoreChildFile.absolutePath)
|
|
@@ -211,22 +276,43 @@ fun QueryInfoPage(viewModel: MediaFilesViewModel) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
-
|
|
|
+enum class InfoPosition {
|
|
|
+ TOP, BOTTOM, START, END
|
|
|
}
|
|
|
|
|
|
@Composable
|
|
|
-fun InfoItem(title: String, info: String) {
|
|
|
+fun InfoItem(title: String, info: String, infoPosition: InfoPosition = InfoPosition.BOTTOM) {
|
|
|
|
|
|
- Column {
|
|
|
- Text(text = title, style = TextStyle(fontSize = 14.sp, color = Color.Black, fontWeight = FontWeight.Bold))
|
|
|
- if (info.isNotEmpty() && info.equals("[]").not()) {
|
|
|
- Text(text = info, style = TextStyle(fontSize = 12.sp))
|
|
|
+
|
|
|
+ Column(verticalArrangement = Arrangement.Center) {
|
|
|
+ if (infoPosition == InfoPosition.TOP) {
|
|
|
+ info(info)
|
|
|
+ }
|
|
|
+ Row {
|
|
|
+ if (infoPosition == InfoPosition.START) {
|
|
|
+ info(info)
|
|
|
+ }
|
|
|
+ Text(text = title, style = TextStyle(fontSize = 14.sp, color = Color.Black, fontWeight = FontWeight.Bold))
|
|
|
+ if (infoPosition == InfoPosition.END) {
|
|
|
+ info(info)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (infoPosition == InfoPosition.BOTTOM) {
|
|
|
+ info(info)
|
|
|
}
|
|
|
Spacer(modifier = Modifier.padding(top = 8.dp))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+@Composable
|
|
|
+fun info(info: String) {
|
|
|
+ if (info.isNotEmpty() && info.equals("[]").not()) {
|
|
|
+ Text(text = info, style = TextStyle(fontSize = 12.sp), modifier = Modifier.padding(horizontal = 8.dp))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
@Composable
|
|
|
fun ResultInfoPage(viewModel: MediaFilesViewModel) {
|
|
|
|
|
@@ -245,18 +331,23 @@ fun ResultInfoPage(viewModel: MediaFilesViewModel) {
|
|
|
|
|
|
result.value.forEach { item ->
|
|
|
|
|
|
- Row(modifier = Modifier.padding(vertical = 8.dp).fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
|
|
+ Row(
|
|
|
+ modifier = Modifier
|
|
|
+ .padding(vertical = 8.dp)
|
|
|
+ .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically
|
|
|
+ ) {
|
|
|
|
|
|
Image(
|
|
|
imageVector = ImageVector.vectorResource(id = if (item.isFile) R.drawable.ic_icons8_file else R.drawable.ic_icons8_folder),
|
|
|
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp),
|
|
|
- contentDescription = null)
|
|
|
+ contentDescription = null
|
|
|
+ )
|
|
|
|
|
|
Column {
|
|
|
Text(text = item.name, style = TextStyle(color = Color.Black, fontWeight = FontWeight.Bold, fontSize = 14.sp))
|
|
|
Row {
|
|
|
Text(text = DateUtils.getFormatDate(item.lastModified), fontSize = 12.sp)
|
|
|
- Text(text = item.mediaPath.toFile()?.lengthToFitMemorySize()?:"", fontSize = 12.sp)
|
|
|
+ Text(text = item.mediaPath.toFile()?.lengthToFitMemorySize() ?: "", fontSize = 12.sp)
|
|
|
}
|
|
|
}
|
|
|
|