Improve image share/download in ItemScreen

* Downloaded image now appears in media gallery (fixes #226)

* Fix some shared images which could be corrupted

* Shared image preview now appears in share dialog

* Fix crash when no bitmap is fetched due to various errors
This commit is contained in:
Shinokuni 2024-11-23 21:15:40 +01:00
parent 640744bf6d
commit b803058210
4 changed files with 49 additions and 16 deletions

View File

@ -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

View File

@ -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
)

View File

@ -185,4 +185,5 @@
<string name="feed_already_exists">Le flux existe déjà</string>
<string name="invalid_folder">Dossier invalide</string>
<string name="invalid_feed">Flux invalide</string>
<string name="error_image_download">Une erreur s\'est produite lors du téléchargement de l\'image</string>
</resources>

View File

@ -194,4 +194,5 @@
<string name="feed_already_exists">Feed already exists</string>
<string name="invalid_folder">Invalid folder</string>
<string name="invalid_feed">Invalid feed</string>
<string name="error_image_download">An error occurred while downloading the image</string>
</resources>