feat: implement open downloads directory for Android and iOS

This commit is contained in:
tapframe 2026-07-04 00:57:17 +05:30
parent caad155308
commit 0e78513031
5 changed files with 70 additions and 0 deletions

View file

@ -1,6 +1,8 @@
package com.nuvio.app.features.downloads
import android.content.Context
import android.content.Intent
import androidx.core.content.FileProvider
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -186,6 +188,42 @@ internal actual object DownloadsPlatformDownloader {
val localFile = File(downloadsDir, fileName)
return localFile.takeIf { it.exists() }?.toURI()?.toString()
}
actual fun openDownloadsDirectory(): Boolean {
val context = appContext ?: return false
val downloadsDir = File(context.filesDir, "downloads").apply { mkdirs() }
val uri = runCatching {
FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
downloadsDir,
)
}.getOrNull() ?: return false
val intents = listOf(
Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "resource/folder")
},
Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "vnd.android.document/directory")
},
Intent(Intent.ACTION_VIEW).apply {
data = uri
},
)
return intents.any { intent ->
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
runCatching {
context.startActivity(intent)
true
}.getOrDefault(false)
}
}
}
private class AndroidDownloadsTaskHandle(

View file

@ -508,6 +508,8 @@
<string name="downloads_empty_episodes">No completed episodes</string>
<string name="downloads_empty_title">No downloads yet</string>
<string name="downloads_episode_count">%1$d downloaded episode(s)</string>
<string name="downloads_open_directory">Open downloads folder</string>
<string name="downloads_open_directory_failed">Couldn&apos;t open downloads folder</string>
<string name="downloads_section_active">Active</string>
<string name="downloads_section_movies">Movies</string>
<string name="downloads_section_shows">Shows</string>

View file

@ -23,4 +23,6 @@ internal expect object DownloadsPlatformDownloader {
fun removePartialFile(destinationFileName: String): Boolean
fun resolveLocalFileUri(localFileUri: String?, destinationFileName: String): String?
fun openDownloadsDirectory(): Boolean
}

View file

@ -14,6 +14,7 @@ import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Folder
import androidx.compose.material.icons.rounded.Pause
import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.material.icons.rounded.Refresh
@ -38,6 +39,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nuvio.app.core.i18n.localizedByteUnit
import com.nuvio.app.core.ui.NuvioScreen
import com.nuvio.app.core.ui.NuvioScreenHeader
import com.nuvio.app.core.ui.NuvioToastController
import nuvio.composeapp.generated.resources.*
import org.jetbrains.compose.resources.stringResource
@ -52,6 +54,7 @@ fun DownloadsScreen(
}.collectAsStateWithLifecycle()
var selectedShowId by rememberSaveable { mutableStateOf<String?>(null) }
val openDownloadsDirectoryFailedText = stringResource(Res.string.downloads_open_directory_failed)
val completedEpisodes = remember(uiState.items) {
uiState.completedItems
@ -80,6 +83,20 @@ fun DownloadsScreen(
onBack()
}
},
actions = {
IconButton(
onClick = {
if (!DownloadsPlatformDownloader.openDownloadsDirectory()) {
NuvioToastController.show(openDownloadsDirectoryFailedText)
}
},
) {
Icon(
imageVector = Icons.Rounded.Folder,
contentDescription = stringResource(Res.string.downloads_open_directory),
)
}
},
)
}

View file

@ -37,6 +37,7 @@ import platform.Foundation.NSURLSessionDataTask
import platform.Foundation.NSURLSessionTask
import platform.Foundation.setHTTPMethod
import platform.Foundation.setValue
import platform.UIKit.UIApplication
import platform.Foundation.timeIntervalSince1970
import platform.darwin.NSObject
import platform.posix.FILE
@ -176,6 +177,16 @@ internal actual object DownloadsPlatformDownloader {
null
}
}
actual fun openDownloadsDirectory(): Boolean {
val url = NSURL.fileURLWithPath(downloadsDirectoryPath())
UIApplication.sharedApplication.openURL(
url = url,
options = emptyMap<Any?, Any>(),
completionHandler = null,
)
return true
}
}
private class IosDownloadsTaskHandle(