diff --git a/app/src/main/java/com/readrops/app/item/ItemScreen.kt b/app/src/main/java/com/readrops/app/item/ItemScreen.kt index 27fb9e9c..865039a8 100644 --- a/app/src/main/java/com/readrops/app/item/ItemScreen.kt +++ b/app/src/main/java/com/readrops/app/item/ItemScreen.kt @@ -135,6 +135,12 @@ class ItemScreen( } } + LaunchedEffect(state.error) { + if (state.error != null) { + snackbarHostState.showSnackbar(state.error!!) + } + } + if (state.itemWithFeed != null) { val itemWithFeed = state.itemWithFeed!! val item = itemWithFeed.item diff --git a/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt b/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt index f474c023..a16b6359 100644 --- a/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt +++ b/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt @@ -1,9 +1,11 @@ package com.readrops.app.item +import android.content.ClipData import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Bitmap +import android.media.MediaScannerConnection import android.net.Uri import android.os.Environment import androidx.compose.runtime.Stable @@ -14,6 +16,7 @@ import coil3.imageLoader import coil3.request.ImageRequest import coil3.request.allowHardware import coil3.toBitmap +import com.readrops.app.R import com.readrops.app.repositories.BaseRepository import com.readrops.app.util.Preferences import com.readrops.db.Database @@ -33,7 +36,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.get import org.koin.core.parameter.parametersOf import java.io.File -import java.io.FileOutputStream +import java.net.URI @OptIn(ExperimentalCoroutinesApi::class) class ItemScreenModel( @@ -107,6 +110,7 @@ class ItemScreenModel( action = Intent.ACTION_SEND type = "text/plain" putExtra(Intent.EXTRA_TEXT, item.link) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION }.also { context.startActivity(Intent.createChooser(it, null)) } @@ -132,16 +136,23 @@ class ItemScreenModel( screenModelScope.launch(dispatcher) { val bitmap = getImage(url, context) + if (bitmap == null) { + mutableState.update { it.copy(error = context.getString(R.string.error_image_download)) } + return@launch + } + val target = File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), url.substringAfterLast('/') - ) - FileOutputStream(target).apply { - bitmap.compress(Bitmap.CompressFormat.PNG, 90, this) - flush() - close() + ).apply { + outputStream().apply { + bitmap.compress(Bitmap.CompressFormat.PNG, 90, this) + flush() + close() + } } + MediaScannerConnection.scanFile(context, arrayOf(target.absolutePath), null, null) mutableState.update { it.copy(fileDownloadedEvent = true) } } } @@ -149,38 +160,51 @@ class ItemScreenModel( fun shareImage(url: String, context: Context) { screenModelScope.launch(dispatcher) { val bitmap = getImage(url, context) + if (bitmap == null) { + mutableState.update { it.copy(error = context.getString(R.string.error_image_download)) } + return@launch + } + val uri = saveImageInCache(bitmap, url, context) Intent().apply { action = Intent.ACTION_SEND - type = "image/*" + + clipData = ClipData.newRawUri(null, uri) putExtra(Intent.EXTRA_STREAM, uri) + + type = "image/*" + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION }.also { context.startActivity(Intent.createChooser(it, null)) } } } - private suspend fun getImage(url: String, context: Context): Bitmap { + private suspend fun getImage(url: String, context: Context): Bitmap? { val downloader = context.imageLoader - return (downloader.execute( + val image = downloader.execute( ImageRequest.Builder(context) .data(url) .allowHardware(false) .build() - ).image!!.toBitmap()) + ).image + + return image?.toBitmap() } private fun saveImageInCache(bitmap: Bitmap, url: String, context: Context): Uri { val imagesFolder = File(context.cacheDir.absolutePath, "images") if (!imagesFolder.exists()) imagesFolder.mkdirs() - val image = File(imagesFolder, url.substringAfterLast('/')) - FileOutputStream(image).apply { - bitmap.compress(Bitmap.CompressFormat.PNG, 90, this) - flush() - close() + val name = URI.create(url).path.substringAfterLast('/') + val image = File(imagesFolder, name).apply { + outputStream().apply { + bitmap.compress(Bitmap.CompressFormat.PNG, 90, this) + flush() + close() + } } return FileProvider.getUriForFile(context, context.packageName, image) @@ -194,5 +218,6 @@ data class ItemState( val imageDialogUrl: String? = null, val fileDownloadedEvent: Boolean = false, val openInExternalBrowser: Boolean = false, - val theme: String? = "" + val theme: String? = "", + val error: String? = null ) \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 86a50b19..0bcf509e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -185,4 +185,5 @@ Le flux existe déjà Dossier invalide Flux invalide + Une erreur s\'est produite lors du téléchargement de l\'image \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b48dee4..b9e147f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -194,4 +194,5 @@ Feed already exists Invalid folder Invalid feed + An error occurred while downloading the image \ No newline at end of file