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(