Browse Source

文件扫描测试代码

liuxiaolong 2 years ago
parent
commit
1a781fab74

+ 1 - 1
app/src/main/AndroidManifest.xml

@@ -18,7 +18,7 @@
         android:theme="@style/Theme.Lib"
         tools:targetApi="31">
         <activity
-            android:name=".MediaSampleActivity"
+            android:name=".ui.sample.media.MediaSampleActivity"
             android:exported="false" />
         <activity
             android:name="com.convenient.android.lib.ui.sample.MainActivity"

+ 0 - 187
app/src/main/java/com/convenient/android/lib/MediaSampleActivity.kt

@@ -1,187 +0,0 @@
-package com.convenient.android.lib
-
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.os.Environment
-import android.os.Environment.getExternalStorageDirectory
-import android.provider.DocumentsContract
-import android.util.Log
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
-import com.convenient.android.common.base.viewbinding.BaseBindingActivity
-import com.convenient.android.common.config.MyPdfBaseModule
-import com.convenient.android.common.extension.*
-import com.convenient.android.common.media.MediaBean
-import com.convenient.android.common.media.config.MediaQueryConfig
-import com.convenient.android.common.media.config.MediaSortOrder
-import com.convenient.android.common.media.config.MediaSortType
-import com.convenient.android.common.media.scan.FileStore
-import com.convenient.android.common.media.scan.MediaStore
-import com.convenient.android.common.utils.date.DateUtils
-import com.convenient.android.lib.databinding.ActivityMediaSampleBinding
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import java.io.File
-
-class MediaSampleActivity : BaseBindingActivity<ActivityMediaSampleBinding>(ActivityMediaSampleBinding::inflate) {
-
-    private var dir: String? = ""
-
-    val chooseDirLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
-        it.data?.data?.let {
-            val filepath = resolveContentUri(it)
-            spSave("dir", filepath)
-            dir = filepath
-            binding.tvQueryDir.text = filepath
-        }
-    }
-
-    enum class QueryMode {
-        FILE, MEDIA_STORE
-    }
-
-    private var queryMode: MutableStateFlow<QueryMode> = MutableStateFlow(QueryMode.FILE)
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        MyPdfBaseModule.init(application, true, "1")
-        binding = ActivityMediaSampleBinding.inflate(layoutInflater)
-        setContentView(binding.root)
-        dir = spGetString("dir")
-        binding.tvQueryDir.text = dir
-        setViewsClick(
-            {
-                when (it) {
-                    binding.btnQueryFromFiles -> {
-                        queryMode.value = QueryMode.FILE
-                        query()
-
-                    }
-                    binding.btnQueryFromMediaStore -> {
-                        queryMode.value = QueryMode.MEDIA_STORE
-                        query()
-                    }
-                    binding.btnChooseDir -> {
-                        chooseDir()
-
-                    }
-                }
-            },
-            binding.btnQueryFromFiles,
-            binding.btnQueryFromMediaStore,
-            binding.btnChooseDir
-        )
-
-
-        lifecycleScope.launch {
-            queryMode.collect {
-                binding.tvQueryType.text = when (it) {
-                    QueryMode.MEDIA_STORE -> "MediaStore"
-                    QueryMode.FILE -> "File"
-                }
-            }
-        }
-    }
-
-    private fun chooseDir() {
-        chooseDirLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
-        })
-    }
-
-    private fun getConfig(): MediaQueryConfig = MediaQueryConfig().also {
-        it.supportMimeTypes = listOf("pdf")
-        it.recursively = true
-        it.order = MediaSortOrder.ASC
-        it.sortType = MediaSortType.SIZE
-        it.includeFolder = false
-        it.ignoreChildFiles = mutableListOf(
-//            File(dir, "新建文件夹/"),
-//            File(dir, "Child/Kotlin 语言文档.pdf")
-//            File("/storage/emulated/0/17PDF/")
-        )
-
-    }.also { config ->
-
-        lifecycleScope.launch(Dispatchers.Main) {
-            binding.tvFileType.text = config.supportMimeTypes.toString()
-            binding.tvQueryIgnoreChildDir.text = config.includeFolder.toString()
-            binding.tvQueryRecursively.text = config.recursively.toString()
-            val builder = StringBuilder()
-            config.ignoreChildFiles.forEach {
-                builder.append(it.absolutePath).append("\n")
-            }
-            binding.tvQueryIgnoreChildFiles.text = builder
-
-        }
-
-    }
-
-
-    private fun query() {
-        lifecycleScope.launch(Dispatchers.Main) {
-//            dir = ""
-            val data = when (queryMode.value) {
-                QueryMode.FILE -> {
-                    queryFromFile()
-                }
-                else -> {
-                    queryFromMediaStore()
-                }
-            }
-
-            val mimeTypes = data.map {
-                it.extension
-            }.distinct()
-
-            binding.tvResultCount.text = data.size.toString()
-            binding.tvResultFileTypes.text = mimeTypes.toString()
-
-            for (datum in data) {
-                Log.e("Media", "名称:${datum.mediaPath},   ${DateUtils.getFormatDate(datum.lastModified, "MM-dd HH:mm:ss")},--:${datum.mediaPath.toFile()?.lengthToFitMemorySize()}")
-            }
-
-        }
-
-    }
-
-    private suspend fun queryFromFile(): List<MediaBean> = withContext(Dispatchers.IO) {
-        FileStore.query(dir ?: Environment.getExternalStorageDirectory().absolutePath, getConfig())
-    }
-
-
-    private suspend fun queryFromMediaStore(): List<MediaBean> = withContext(Dispatchers.IO) {
-        MediaStore.query(dir = dir ?: "", config = getConfig())
-    }
-
-
-    fun resolveContentUri(uri: Uri): String {
-
-        val docUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri))
-        val docCursor = contentResolver.query(docUri, null, null, null, null)
-
-        var str: String = ""
-
-        // get a string of the form : primary:Audiobooks or 1407-1105:Audiobooks
-        while (docCursor!!.moveToNext()) {
-            str = docCursor.getString(0)
-            if (str.matches(Regex(".*:.*"))) break //Maybe useless
-        }
-
-        docCursor.close()
-
-        val split = str.split(":")
-
-        val base: File =
-            if (split[0] == "primary") getExternalStorageDirectory()
-            else File("/storage/${split[0]}")
-
-        if (!base.isDirectory) throw Exception("'$uri' cannot be resolved in a valid path")
-
-        return File(base, split[1]).canonicalPath
-    }
-
-}

+ 54 - 4
app/src/main/java/com/convenient/android/lib/ui/sample/MainActivity.kt

@@ -1,8 +1,17 @@
 package com.convenient.android.lib.ui.sample
 
+import android.Manifest
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
 import android.os.Bundle
+import android.os.Environment
+import android.provider.Settings
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -15,7 +24,9 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.convenient.android.common.extension.readyGo
+import com.convenient.android.common.utils.ToastUtil
 import com.convenient.android.lib.DateUtilActivity
+import com.convenient.android.lib.ui.sample.media.MediaSampleActivity
 import com.kdanmobile.jetpackcompose.sample.ui.theme.SampleTheme
 
 class MainActivity : ComponentActivity() {
@@ -30,20 +41,59 @@ class MainActivity : ComponentActivity() {
 }
 
 
-
 @Composable
 private fun MainPage() {
     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, "访问所有文件权限获取成功")
+            }
+        }
+    })
+    val permissionLaunch = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission(), onResult = {
+        if (it) {
+            ToastUtil.showToast(context, "存储权限获取成功")
+        }
+    })
+
     Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
-        Button(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),onClick = {
+
+        FunItem(title = "获取存储权限") {
+            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{
+                    ToastUtil.showToast(context, "已获取存储权限")
+                }
+            } else {
+                permissionLaunch.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+            }
+        }
+
+        FunItem(title = "DateUtils 测试") {
             context.readyGo(DateUtilActivity::class.java)
-        }) {
-            Text(text = "DateUtils 测试")
         }
+        FunItem(title = "MediaStore 测试") {
+            context.readyGo(MediaSampleActivity::class.java)
+        }
+
     }
 
 }
 
+@Composable
+private fun FunItem(title: String, onClick: () -> Unit) {
+    Button(
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(horizontal = 16.dp), onClick = onClick
+    ) {
+        Text(text = title)
+    }
+}
+
+
 @Preview(showSystemUi = true)
 @Composable
 fun MainPagePreview() {

+ 277 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/media/MediaFilesPage.kt

@@ -0,0 +1,277 @@
+package com.convenient.android.lib.ui.sample.media
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.os.Build
+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
+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.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
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.Dimension
+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.date.DateUtils
+import com.convenient.android.common.utils.image.BitmapUtils
+import com.convenient.android.lib.R
+import com.kdanmobile.jetpackcompose.sample.ui.theme.SampleTheme
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/8/4
+ * description:
+ */
+
+
+@Composable
+fun MediaFilesPage() {
+    val viewModel: MediaFilesViewModel = viewModel()
+    val context = LocalContext.current
+
+    val chooseDirLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult(), onResult = {
+        it.data?.data?.let {
+            val path = resolveContentUri(context, it)
+            context.spSave("dir", path)
+            viewModel.changeQueryDir(path)
+        }
+    })
+
+
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(horizontal = 16.dp)
+            .verticalScroll(rememberScrollState())
+    ) {
+
+        ConstraintLayout(
+            modifier = Modifier
+                .fillMaxWidth()
+        ) {
+            val (btnFromFiles, btnFromMediaStore, btnChooseDir) = createRefs()
+
+            Button(onClick = { viewModel.changeQueryType(MediaQueryType.FILES) }, modifier = Modifier
+                .padding(end = 8.dp)
+                .constrainAs(btnFromFiles) {
+                    top.linkTo(parent.top)
+                    start.linkTo(parent.start)
+                    end.linkTo(btnFromMediaStore.start)
+                    width = Dimension.fillToConstraints
+                }) {
+                Text(text = "从FilesMedia获取")
+
+            }
+
+            Button(onClick = { viewModel.changeQueryType(MediaQueryType.MEDIA_STORE) },
+                modifier = Modifier
+                    .padding(start = 8.dp)
+                    .constrainAs(btnFromMediaStore) {
+                        top.linkTo(btnFromFiles.top)
+                        start.linkTo(btnFromFiles.end)
+                        end.linkTo(parent.end)
+                        width = Dimension.fillToConstraints
+                    }) {
+                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)
+        Spacer(
+            modifier = Modifier
+                .fillMaxWidth()
+                .height(1.dp)
+                .background(color = Color.LightGray)
+        )
+        ResultInfoPage(viewModel = viewModel)
+
+    }
+}
+
+@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) }
+            }))
+        }
+    })
+
+    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) }
+            }))
+        }
+    })
+
+    val dir = viewModel.dir.collectAsState()
+    val queryType = viewModel.queryType.collectAsState()
+
+    InfoItem(title = "目录:", info = dir.value)
+    InfoItem(title = "获取方式:", info = queryType.value.name)
+
+    Column() {
+        InfoItem(title = "查询的文件格式:", info = config.value.supportMimeTypes.toString())
+        TextField(
+            keyboardOptions = KeyboardOptions(autoCorrect = false, capitalization = KeyboardCapitalization.None),
+            value = viewModel.getSupportMimeTypesFiledTextValue(config.value),
+            singleLine = true,
+            shape = RoundedCornerShape(4.dp),
+            onValueChange = {
+                val a = it.replace("[", "").replace("]", "").trim().lowercase()
+                val supportMimeTypes = a.split(",").toMutableList()
+                viewModel.changeConfig(config = config.value.copy(supportMimeTypes = supportMimeTypes))
+            }, placeholder = {
+                Text(text = "png,jpg 以,分割")
+            })
+    }
+
+    Row {
+        InfoItem(title = "结果是否包含文件夹:", info = config.value.includeFolder.toString())
+        val includeFolder = config.value.includeFolder
+        RadioButton(selected = includeFolder, onClick = {
+            viewModel.changeConfigIncludeFolder(includeFolder.not())
+        })
+    }
+
+    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()))
+        })
+    }
+
+    Column {
+        InfoItem(title = "忽略的文件:", info = "")
+        Button(onClick = { addIgnoreFolderLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) }) {
+            Text(text = "选择忽略的文件夹")
+        }
+
+        Button(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)
+                IconButton(modifier = Modifier.weight(1F), onClick = {
+                    viewModel.removeConfigIgnoreChildFiles(ignoreChildFile)
+                }) {
+                    Icon(imageVector = Icons.Default.Delete, contentDescription = null)
+                }
+            }
+        }
+    }
+
+
+}
+
+@Composable
+fun InfoItem(title: String, info: String) {
+
+    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))
+        }
+        Spacer(modifier = Modifier.padding(top = 8.dp))
+    }
+}
+
+@Composable
+fun ResultInfoPage(viewModel: MediaFilesViewModel) {
+
+    InfoItem(title = "查询结果", info = "")
+
+    val result = viewModel.result.collectAsState()
+    val count = result.value.size
+    val mimeTypes = result.value.distinctBy {
+        it.extension
+    }.map {
+        it.extension
+    }
+
+    InfoItem(title = "数量", info = count.toString())
+    InfoItem(title = "结果文件格式", info = mimeTypes.toString())
+
+    result.value.forEach { item ->
+
+        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)
+
+            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)
+                }
+            }
+
+        }
+    }
+
+
+}
+
+
+@Preview(showSystemUi = true)
+@Composable
+fun MediaFilesPagePreview() {
+    SampleTheme {
+        MediaFilesPage()
+    }
+}
+

+ 39 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/media/MediaFilesRepository.kt

@@ -0,0 +1,39 @@
+package com.convenient.android.lib.ui.sample.media
+
+import com.convenient.android.common.media.MediaBean
+import com.convenient.android.common.media.config.MediaQueryConfig
+import com.convenient.android.common.media.scan.FileStore
+import com.convenient.android.common.media.scan.MediaStore
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/8/4
+ * description:
+ * */
+
+enum class MediaQueryType{
+    FILES,MEDIA_STORE
+}
+
+class MediaFilesRepository {
+
+    companion object{
+
+        val instance by lazy {
+            MediaFilesRepository()
+        }
+
+    }
+
+
+    suspend fun queryMediaFiles(queryType: MediaQueryType,dir : String, config: MediaQueryConfig) : List<MediaBean>{
+        return when(queryType){
+            MediaQueryType.MEDIA_STORE-> MediaStore.query(dir = dir, config)
+            MediaQueryType.FILES -> FileStore.query(dir, config)
+        }
+    }
+
+
+
+}

+ 93 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/media/MediaFilesViewModel.kt

@@ -0,0 +1,93 @@
+package com.convenient.android.lib.ui.sample.media
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.convenient.android.common.extension.spGetString
+import com.convenient.android.common.media.MediaBean
+import com.convenient.android.common.media.config.MediaQueryConfig
+import com.convenient.android.common.utils.string.SharedPreferencesSave
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import java.io.File
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/8/4
+ * description:
+ */
+class MediaFilesViewModel(application: Application) : AndroidViewModel(application) {
+
+    private var _config : MutableStateFlow<MediaQueryConfig> = MutableStateFlow(MediaQueryConfig())
+    var config : StateFlow<MediaQueryConfig> = _config
+
+    private var _dir : MutableStateFlow<String> = MutableStateFlow(application.spGetString(key = "dir"))
+    var dir : StateFlow<String> = _dir
+
+    private var _queryType : MutableStateFlow<MediaQueryType> = MutableStateFlow(MediaQueryType.FILES)
+    var queryType : StateFlow<MediaQueryType> = _queryType
+
+    private var _result : MutableStateFlow<List<MediaBean>> = MutableStateFlow(mutableListOf())
+    var result :StateFlow<List<MediaBean>>  = _result
+
+
+    fun changeQueryType(queryType: MediaQueryType){
+        _queryType.value = queryType
+        viewModelScope.launch {
+            query()
+        }
+    }
+
+    fun changeConfig(config: MediaQueryConfig){
+        _config.value = config
+    }
+
+
+    fun changeQueryDir(dir : String){
+        _dir.value = dir
+    }
+
+    fun removeConfigIgnoreChildFiles(file : File){
+        changeConfig(copyNewConfig {
+            this.ignoreChildFiles.toMutableList().apply {
+                remove(file)
+            }
+        })
+    }
+
+    fun changeConfigIncludeFolder(includeFolder : Boolean){
+        changeConfig(copyNewConfig {
+            this.includeFolder = includeFolder
+        })
+    }
+
+    private fun copyNewConfig(config: MediaQueryConfig.()-> Unit) : MediaQueryConfig{
+        return _config.value.copy().also(config)
+    }
+
+    fun getSupportMimeTypesFiledTextValue(config: MediaQueryConfig) : String{
+        val value = if (config.supportMimeTypes.isNotEmpty()) {
+            buildString {
+                config.supportMimeTypes.forEachIndexed { index, s ->
+                    if (index != config.supportMimeTypes.lastIndex) {
+                        append(s).append(",")
+                    } else {
+                        append(s)
+                    }
+                }
+            }
+        } else {
+            ""
+        }
+        return value
+    }
+
+    suspend fun query(){
+        _result.value = MediaFilesRepository.instance.queryMediaFiles(_queryType.value, _dir.value, _config.value)
+    }
+
+
+
+}

+ 174 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/media/MediaSampleActivity.kt

@@ -0,0 +1,174 @@
+package com.convenient.android.lib.ui.sample.media
+
+import android.content.ContentResolver
+import android.content.ContentUris
+import android.content.Context
+import android.content.Intent
+import android.database.Cursor
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.os.Environment.getExternalStorageDirectory
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.util.Log
+import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.lifecycle.lifecycleScope
+import com.convenient.android.common.base.viewbinding.BaseBindingActivity
+import com.convenient.android.common.config.MyPdfBaseModule
+import com.convenient.android.common.extension.*
+import com.convenient.android.common.media.MediaBean
+import com.convenient.android.common.media.config.MediaQueryConfig
+import com.convenient.android.common.media.config.MediaSortOrder
+import com.convenient.android.common.media.config.MediaSortType
+import com.convenient.android.common.media.scan.FileStore
+import com.convenient.android.common.utils.date.DateUtils
+import com.convenient.android.lib.databinding.ActivityMediaSampleBinding
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.io.File
+
+class MediaSampleActivity : BaseBindingActivity<ActivityMediaSampleBinding>(ActivityMediaSampleBinding::inflate) {
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        MyPdfBaseModule.init(application, true, "1")
+        setContent {
+            MediaFilesPage()
+        }
+
+    }
+
+
+
+}
+
+fun resolveContentUri(context: Context, uri: Uri): String {
+
+
+    val docUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri))
+
+    val docCursor = context.contentResolver.query(docUri, null, null, null, null)
+
+    var str: String = ""
+
+    // get a string of the form : primary:Audiobooks or 1407-1105:Audiobooks
+    while (docCursor!!.moveToNext()) {
+        str = docCursor.getString(0)
+        if (str.matches(Regex(".*:.*"))) break //Maybe useless
+    }
+
+    docCursor.close()
+
+    val split = str.split(":")
+
+    val base: File =
+        if (split[0] == "primary") getExternalStorageDirectory()
+        else File("/storage/${split[0]}")
+
+    if (!base.isDirectory) throw Exception("'$uri' cannot be resolved in a valid path")
+
+    return File(base, split[1]).canonicalPath
+}
+
+
+object UriTofilePath {
+    fun getFilePathByUri(context: Context, uri: Uri): String? {
+        var path: String? = null
+        // 4.4及之后的 是以 content:// 开头的,比如 content://com.android.providers.media.documents/document/image%3A235700
+        if (ContentResolver.SCHEME_CONTENT.equals(uri.scheme) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            if (DocumentsContract.isDocumentUri(context, uri)) {
+                if (isExternalStorageDocument(uri)) {
+                    // ExternalStorageProvider
+                    val docId = DocumentsContract.getDocumentId(uri)
+                    val split = docId.split(":".toRegex()).toTypedArray()
+                    val type = split[0]
+                    if ("primary".equals(type, ignoreCase = true)) {
+                        path = getExternalStorageDirectory().toString() + "/" + split[1]
+                        return path
+                    }
+                } else if (isDownloadsDocument(uri)) {
+                    // DownloadsProvider
+                    val id = DocumentsContract.getDocumentId(uri)
+                    val contentUri: Uri = ContentUris.withAppendedId(
+                        Uri.parse("content://downloads/public_downloads"),
+                        java.lang.Long.valueOf(id)
+                    )
+                    path = getDataColumn(context, contentUri, null, null)
+                    return path
+                } else if (isMediaDocument(uri)) {
+                    // MediaProvider
+                    val docId = DocumentsContract.getDocumentId(uri)
+                    val split = docId.split(":".toRegex()).toTypedArray()
+                    val type = split[0]
+                    var contentUri: Uri? = null
+                    if ("image" == type) {
+                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+                    } else if ("video" == type) {
+                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+                    } else if ("audio" == type) {
+                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+                    }
+                    val selection = "_id=?"
+                    val selectionArgs = arrayOf(split[1])
+                    path = getDataColumn(context, contentUri, selection, selectionArgs)
+                    return path
+                }
+            }
+        } else {
+            // 以 file:// 开头的
+            if (ContentResolver.SCHEME_FILE.equals(uri.scheme)) {
+                path = uri.path
+                return path
+            }
+            // 以 content:// 开头的,比如 content://media/extenral/images/media/17766
+            if (ContentResolver.SCHEME_CONTENT.equals(uri.scheme) && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+                val cursor: Cursor? = context.contentResolver.query(uri, arrayOf(MediaStore.Images.Media.DATA), null, null, null)
+                if (cursor != null) {
+                    if (cursor.moveToFirst()) {
+                        val columnIndex: Int = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
+                        if (columnIndex > -1) {
+                            path = cursor.getString(columnIndex)
+                        }
+                    }
+                    cursor.close()
+                }
+                return path
+            }
+        }
+        return null
+    }
+
+    private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
+        var cursor: Cursor? = null
+        val column = "_data"
+        val projection = arrayOf(column)
+        try {
+            cursor = context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
+            if (cursor != null && cursor.moveToFirst()) {
+                val column_index: Int = cursor.getColumnIndexOrThrow(column)
+                return cursor.getString(column_index)
+            }
+        } finally {
+            if (cursor != null) cursor.close()
+        }
+        return null
+    }
+
+    private fun isExternalStorageDocument(uri: Uri): Boolean {
+        return "com.android.externalstorage.documents" == uri.authority
+    }
+
+    private fun isDownloadsDocument(uri: Uri): Boolean {
+        return "com.android.providers.downloads.documents" == uri.authority
+    }
+
+    private fun isMediaDocument(uri: Uri): Boolean {
+        return "com.android.providers.media.documents" == uri.authority
+    }
+}

+ 1 - 1
app/src/main/res/layout/activity_media_sample.xml

@@ -4,7 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".MediaSampleActivity">
+    tools:context=".ui.sample.media.MediaSampleActivity">
 
 
     <androidx.appcompat.widget.AppCompatButton

+ 1 - 0
lib_common/src/main/java/com/convenient/android/common/extension/FileExtensions.kt

@@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull
 import java.io.File
 import java.io.FileFilter
 import java.io.FileOutputStream
+import java.net.URI
 import java.security.DigestInputStream
 import java.security.MessageDigest
 

+ 7 - 12
lib_common/src/main/java/com/convenient/android/common/media/config/MediaQueryConfig.kt

@@ -8,44 +8,39 @@ import java.io.File
  * @date: 2022/8/2
  * description: 文件媒体查询的配置
  */
-class MediaQueryConfig {
-
+data class MediaQueryConfig(
     /**
      * 排序方式
      */
-    var order: MediaSortOrder = MediaSortOrder.ASC
-
+    var order: MediaSortOrder = MediaSortOrder.DESC,
     /**
      * 排序类型
      */
-    var sortType : MediaSortType = MediaSortType.DATE
-
+    var sortType : MediaSortType = MediaSortType.DATE,
     /**
      * 忽略的子目录或文件路径,要完整路径
      * 不管是文件还是文件夹,只要向匹配就忽略,
      */
-    var ignoreChildFiles: List<File> = mutableListOf()
-
+    var ignoreChildFiles: List<File> = mutableListOf(),
     /**
      * 返回的结果中中是否包含文件夹
      * 仅在FileMedia中生效
      * 仅在FileMedia中生效
      * 仅在FileMedia中生效
      */
-    var includeFolder : Boolean = false
-
+    var includeFolder : Boolean = false,
     /**
      * 支持查询的文件格式
      * 根据文件名称后缀 进行判断 例如:png jpg
      * 都使用小写
      */
-    var supportMimeTypes: List<String> = mutableListOf()
-
+    var supportMimeTypes: List<String> = mutableListOf(),
     /**
      * 是否递归出该目录下所有子文件夹数据
      */
     var recursively : Boolean = false
 
+) {
 }
 
 

+ 1 - 1
lib_common/src/main/java/com/convenient/android/common/media/scan/FileStore.kt

@@ -52,7 +52,7 @@ object FileStore : Query {
                 return@listFilesInDirWithFilter false
             }
             //如果没有指定需要的文件格式,则全部返回
-            if (config.supportMimeTypes.isEmpty()) {
+            if (config.supportMimeTypes.isEmpty() || (config.supportMimeTypes.size == 1 && config.supportMimeTypes[0].isEmpty())) {
                 true
             } else {
                 config.supportMimeTypes.contains(it.extension.lowercase())

+ 18 - 4
lib_common/src/main/java/com/convenient/android/common/media/scan/MediaStore.kt

@@ -1,6 +1,8 @@
 package com.convenient.android.common.media.scan
 
 import android.content.ContentUris
+import android.content.Context
+import android.net.Uri
 import android.provider.MediaStore
 import android.util.Log
 import com.convenient.android.common.config.MyPdfBaseModule
@@ -78,7 +80,7 @@ object MediaStore : Query {
                 while (moveToNext()) {
                     val path = getString(getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))
                     val mediaFile = path.toFile()
-                    if (mediaFile?.isFile?.not() == true){
+                    if (mediaFile?.isFile?.not() == true) {
                         continue
                     }
                     if (config.ignoreChildFiles.any {
@@ -88,14 +90,13 @@ object MediaStore : Query {
                         continue
                     }
 
-                    if (config.recursively.not() && mediaFile?.parent?.equals(dir)?.not() == true){
+                    if (config.recursively.not() && mediaFile?.parent?.equals(dir)?.not() == true) {
                         //没有开启递归子目录,并且文件所在的文件夹与扫描的目录不一致,则跳过
                         continue
                     }
 
                     val id = getLong(getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
                     val name = getString(getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME))
-                    val lastModified = getLong(getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED))
                     val contentUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id)
 
                     list.add(
@@ -104,7 +105,7 @@ object MediaStore : Query {
                             name = name,
                             extension = mediaFile?.extension ?: "",
                             isFile = true,
-                            lastModified = lastModified,
+                            lastModified = mediaFile?.lastModified()?:0,
                             parentPath = mediaFile?.parent ?: "",
                             length = mediaFile?.length(),
                             uri = contentUri
@@ -116,6 +117,7 @@ object MediaStore : Query {
                 e.printStackTrace()
             }
         }
+        cursor?.close()
 
         return list
     }
@@ -164,4 +166,16 @@ object MediaStore : Query {
     }
 
 
+    fun queryContentUriFilePath(context: Context, uri: Uri): String {
+        var cursor = context.contentResolver.query(uri, projection, null, null, null)
+
+        if (cursor?.moveToFirst() == true) {
+            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))
+            return path
+        } else {
+            return ""
+        }
+
+    }
+
 }