diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 94e2829c..457a4c65 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -143,6 +143,7 @@ dependencies { implementation(libs.kotlinx.immutable.collections) implementation(libs.truetype.parser) implementation(libs.fsaf) + implementation(libs.storage.util) } detekt { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6b57937e..fa8268ec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,11 @@ xmlns:tools="http://schemas.android.com/tools"> + + () val context = LocalContext.current Scaffold( topBar = { @@ -80,34 +79,75 @@ data class FilePickerScreen(val uri: String) : Screen() { ) }, ) { paddingValues -> - FilePicker( - directory = fileManager.fromUri(uri.toUri())!!, - onNavigate = { newFile -> - if (fileManager.isFile(newFile)) { - HomeScreen.playFile(newFile.getFullPath(), context) - return@FilePicker - } - navigator.push(FilePickerScreen(newFile.getFullPath())) - }, - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - ) + if (path == null) { + StoragePicker( + onNavigate = { device -> navigator.push(FilePickerScreen(device.absolutePath)) }, + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + ) + } else { + FilePicker( + directory = File(path), + onNavigate = { newFile -> + if (newFile.isFile) { + HomeScreen.playFile(Uri.fromFile(newFile).toString(), context) + return@FilePicker + } + navigator.push(FilePickerScreen(newFile.absolutePath)) + }, + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + ) + } + } + } + + @Composable + fun StoragePicker( + modifier: Modifier = Modifier, + onNavigate: (File) -> Unit, + ) { + val context = LocalContext.current + val deviceList = StorageUtil.getStorageDirectories(context) + .map { File(it) } + .filter { it.exists() } + + LazyColumn(modifier) { + itemsIndexed(deviceList, key = { _, file -> file.absolutePath }) { index, file -> + FileListing( + name = file.absolutePath, + isDirectory = true, + lastModified = null, + length = null, + modifier = Modifier.background( + if (index % 2 == 0) { + MaterialTheme.colorScheme.surfaceContainerLow + } else { + MaterialTheme.colorScheme.surfaceContainerHigh + }, + ), + items = null, + onClick = { onNavigate(file) }, + ) + } } } @Composable fun FilePicker( - directory: AbstractFile, + directory: File, modifier: Modifier = Modifier, - onNavigate: (AbstractFile) -> Unit, + onNavigate: (File) -> Unit, ) { val navigator = LocalNavigator.currentOrThrow - val fileManager = koinInject() - val fileList = fileManager.listFiles(directory).filterNot { - !Utils.MEDIA_EXTENSIONS.contains(fileManager.getName(it).substringAfterLast('.')) && - fileManager.isFile(it) || fileManager.getName(it).startsWith('.') - }.sortedWith(FilesComparator(fileManager)) + val fileList = directory.listFiles { file -> + val name = file.name + if (name.startsWith('.')) return@listFiles false + if (file.isDirectory) return@listFiles true + file.isFile && Utils.MEDIA_EXTENSIONS.contains(name.substringAfterLast('.').lowercase(Locale.ENGLISH)) + }?.sortedWith(FilesComparator()) ?: emptyList() LazyColumn(modifier) { item { @@ -120,12 +160,12 @@ data class FilePickerScreen(val uri: String) : Screen() { modifier = Modifier.background(MaterialTheme.colorScheme.surfaceContainerLow), ) } - itemsIndexed(fileList, key = { _, file -> fileManager.getName(file) }) { index, file -> + itemsIndexed(fileList, key = { _, file -> file.name }) { index, file -> FileListing( - name = fileManager.getName(file), - isDirectory = fileManager.isDirectory(file), - lastModified = fileManager.lastModified(file), - length = if (fileManager.isFile(file)) fileManager.getLength(file) else null, + name = file.name, + isDirectory = file.isDirectory, + lastModified = file.lastModified(), + length = if (file.isFile) file.length() else null, modifier = Modifier.background( if (index % 2 == 1) { MaterialTheme.colorScheme.surfaceContainerLow @@ -133,7 +173,7 @@ data class FilePickerScreen(val uri: String) : Screen() { MaterialTheme.colorScheme.surfaceContainerHigh }, ), - items = if (fileManager.isDirectory(file)) fileManager.listFiles(file).size else null, + items = if (file.isDirectory) file.listFiles()?.size else null, onClick = { onNavigate(file) }, ) } diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/home/HomeScreen.kt b/app/src/main/java/live/mehiz/mpvkt/ui/home/HomeScreen.kt index 78fa0e4b..537db092 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/home/HomeScreen.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/home/HomeScreen.kt @@ -1,7 +1,12 @@ package live.mehiz.mpvkt.ui.home +import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +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.compose.foundation.Image @@ -11,7 +16,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.FileOpen import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Link @@ -36,10 +40,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.core.content.ContextCompat import androidx.core.net.toUri import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import com.github.k1rakishou.fsaf.FileManager import `is`.xyz.mpv.Utils.PROTOCOLS import live.mehiz.mpvkt.R import live.mehiz.mpvkt.presentation.Screen @@ -108,31 +112,36 @@ object HomeScreen : Screen() { Text(text = stringResource(R.string.home_open_url)) } } - val documentPicker = rememberLauncherForActivityResult( - ActivityResultContracts.OpenDocument(), - ) { - if (it == null) return@rememberLauncherForActivityResult - playFile(it.toString(), context) - } - OutlinedButton( - onClick = { documentPicker.launch(arrayOf("*/*")) }, - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon(Icons.Default.FileOpen, null) - Text(text = stringResource(R.string.home_pick_file)) + val manageStoragePermission = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) { + navigator.push(FilePickerScreen()) } } - val fileManager = FileManager(context) - val directoryPicker = rememberLauncherForActivityResult( - ActivityResultContracts.OpenDocumentTree(), - ) { - if (it == null) return@rememberLauncherForActivityResult - navigator.push(FilePickerScreen(fileManager.fromUri(it)!!.getFullPath())) + val readStoragePermission = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + navigator.push(FilePickerScreen()) + } } - OutlinedButton(onClick = { directoryPicker.launch(null) }) { + OutlinedButton(onClick = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) { + manageStoragePermission.launch( + Intent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + "package:${context.packageName}".toUri(), + ) + ) + } else { + navigator.push(FilePickerScreen()) + } + } else { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + readStoragePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE) + } else { + navigator.push(FilePickerScreen()) + } + } + }) { Row( horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), verticalAlignment = Alignment.CenterVertically, diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/utils/FilesComparator.kt b/app/src/main/java/live/mehiz/mpvkt/ui/utils/FilesComparator.kt index f7e2a852..74433ac5 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/utils/FilesComparator.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/utils/FilesComparator.kt @@ -1,19 +1,16 @@ package live.mehiz.mpvkt.ui.utils -import com.github.k1rakishou.fsaf.FileManager -import com.github.k1rakishou.fsaf.file.AbstractFile +import java.io.File /** * Sorts files/directories alphabetically while giving directories priority * credit goes to mpv-android */ -class FilesComparator( - private val fileManager: FileManager -) : Comparator { - override fun compare(o1: AbstractFile?, o2: AbstractFile?): Int { - val iso1ADirectory = fileManager.isDirectory(o1!!) - val iso2ADirectory = fileManager.isDirectory(o2!!) +class FilesComparator : Comparator { + override fun compare(o1: File?, o2: File?): Int { + val iso1ADirectory = o1!!.isDirectory + val iso2ADirectory = o2!!.isDirectory if (iso1ADirectory != iso2ADirectory) return if (iso2ADirectory) 1 else -1 - return fileManager.getName(o1).compareTo(fileManager.getName(o2), ignoreCase = true) + return o1.name.compareTo(o2.name, ignoreCase = true) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 27d351ee..3d452630 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,6 +57,7 @@ kotlinx-immutable-collections = { module = "org.jetbrains.kotlinx:kotlinx-collec truetype-parser = { module = "io.github.yubyf:truetypeparser-light", version = "2.1.4" } fsaf = { module = "com.github.K1rakishou:Fuck-Storage-Access-Framework", version = "1.1.3" } +storage-util = { module = "com.github.hendrawd:StorageUtil", version = "1.1.0" } about-libs-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "about-libs" } about-libs-ui-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "about-libs" }