diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt index 52c7e1123..8bda60027 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.android.kt @@ -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( diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 30badcd3a..ac613767a 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -508,6 +508,8 @@ No completed episodes No downloads yet %1$d downloaded episode(s) + Open downloads folder + Couldn't open downloads folder Active Movies Shows diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.kt index 9fb32ced3..077b8530f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.kt @@ -23,4 +23,6 @@ internal expect object DownloadsPlatformDownloader { fun removePartialFile(destinationFileName: String): Boolean fun resolveLocalFileUri(localFileUri: String?, destinationFileName: String): String? + + fun openDownloadsDirectory(): Boolean } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsScreen.kt index b1fe7e333..de8ba9ad3 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/downloads/DownloadsScreen.kt @@ -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(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), + ) + } + }, ) } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt index a0bfc724e..dd37f8e62 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/downloads/DownloadsPlatformDownloader.ios.kt @@ -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(), + completionHandler = null, + ) + return true + } } private class IosDownloadsTaskHandle(