diff --git a/app/src/main/java/de/bollwerk/app/ui/resources/FileOpenHelper.kt b/app/src/main/java/de/bollwerk/app/ui/resources/FileOpenHelper.kt new file mode 100644 index 0000000..dc4beea --- /dev/null +++ b/app/src/main/java/de/bollwerk/app/ui/resources/FileOpenHelper.kt @@ -0,0 +1,55 @@ +package de.bollwerk.app.ui.resources + +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import androidx.compose.material3.SnackbarHostState +import androidx.core.content.FileProvider +import java.io.File + +/** + * Öffnet eine Ressourcen-Datei in einer externen App via FileProvider. + * + * Erteilt vor dem `startActivity`-Aufruf explizit URI-Berechtigungen an alle Apps, + * die den Intent auflösen können – notwendig damit der Android-Chooser die Berechtigung + * korrekt weitergibt (z.B. Firefox für PDFs). + */ +internal suspend fun openResourceFile( + context: Context, + file: File, + mimeType: String, + snackbarHostState: SnackbarHostState +) { + val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) + val intent = Intent(Intent.ACTION_VIEW).apply { + setDataAndType(uri, mimeType) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + // Explizit URI-Berechtigung an alle Apps vergeben, die diesen Intent auflösen können, + // damit der Android-Chooser die Berechtigung korrekt weitergibt (z.B. Firefox für PDFs). + val resolvedActivities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.queryIntentActivities( + intent, + PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()) + ) + } else { + @Suppress("DEPRECATION") + context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) + } + resolvedActivities.forEach { resolveInfo -> + context.grantUriPermission( + resolveInfo.activityInfo.packageName, + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } + + try { + context.startActivity(intent) + } catch (_: ActivityNotFoundException) { + snackbarHostState.showSnackbar("Keine App zum Öffnen dieser Datei gefunden") + } +} diff --git a/app/src/main/java/de/bollwerk/app/ui/resources/ResourceDetailScreen.kt b/app/src/main/java/de/bollwerk/app/ui/resources/ResourceDetailScreen.kt index bcd78e9..20b4608 100644 --- a/app/src/main/java/de/bollwerk/app/ui/resources/ResourceDetailScreen.kt +++ b/app/src/main/java/de/bollwerk/app/ui/resources/ResourceDetailScreen.kt @@ -1,7 +1,5 @@ package de.bollwerk.app.ui.resources -import android.content.ActivityNotFoundException -import android.content.Intent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi @@ -42,7 +40,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.core.content.FileProvider import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import de.bollwerk.shared.model.ResourceDto @@ -67,16 +64,7 @@ internal fun ResourceDetailScreen( // Handle download success if (downloadState is DownloadState.Success) { LaunchedEffect(downloadState) { - val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", downloadState.file) - val intent = Intent(Intent.ACTION_VIEW).apply { - setDataAndType(uri, downloadState.mimeType) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - try { - context.startActivity(intent) - } catch (_: ActivityNotFoundException) { - snackbarHostState.showSnackbar("Keine App zum Öffnen dieser Datei gefunden") - } + openResourceFile(context, downloadState.file, downloadState.mimeType, snackbarHostState) viewModel.clearDownloadState(guid) } } diff --git a/app/src/main/java/de/bollwerk/app/ui/resources/ResourceListScreen.kt b/app/src/main/java/de/bollwerk/app/ui/resources/ResourceListScreen.kt index bb36aef..30665fd 100644 --- a/app/src/main/java/de/bollwerk/app/ui/resources/ResourceListScreen.kt +++ b/app/src/main/java/de/bollwerk/app/ui/resources/ResourceListScreen.kt @@ -1,8 +1,5 @@ package de.bollwerk.app.ui.resources -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -61,7 +58,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.core.content.FileProvider import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import de.bollwerk.shared.model.ResourceDto @@ -112,7 +108,7 @@ internal fun ResourceListScreen( when (state) { is DownloadState.Success -> { LaunchedEffect(guid, state) { - openFile(context, state.file, state.mimeType, snackbarHostState) + openResourceFile(context, state.file, state.mimeType, snackbarHostState) viewModel.clearDownloadState(guid) } } @@ -519,20 +515,4 @@ private fun formatFileSize(bytes: Long): String { } } -private suspend fun openFile( - context: Context, - file: File, - mimeType: String, - snackbarHostState: SnackbarHostState -) { - val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) - val intent = Intent(Intent.ACTION_VIEW).apply { - setDataAndType(uri, mimeType) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - try { - context.startActivity(intent) - } catch (_: ActivityNotFoundException) { - snackbarHostState.showSnackbar("Keine App zum Öffnen dieser Datei gefunden") - } -} +