diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ShareHelper.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ShareHelper.kt
new file mode 100644
index 000000000..94e1e1752
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ShareHelper.kt
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.util.Log
+import androidx.core.content.FileProvider
+import coil3.imageLoader
+import coil3.request.ImageRequest
+import coil3.request.SuccessResult
+import coil3.request.allowHardware
+import coil3.size.Size
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.File
+import java.io.IOException
+
+object ShareHelper {
+ suspend fun shareImageFromUrl(
+ context: Context,
+ imageUrl: String,
+ ) {
+ val file = getCachedFileForUrl(context, imageUrl)
+
+ if (file != null) {
+ shareMediaFile(context, getSharableUri(context, file), "image/*")
+ } else {
+ throw IOException("Image file does not exist at path: $imageUrl")
+ }
+ }
+
+ private suspend fun getCachedFileForUrl(
+ context: Context,
+ url: String,
+ ): File? {
+ val loader = context.imageLoader
+ val request =
+ ImageRequest
+ .Builder(context)
+ .data(url)
+ .size(Size.ORIGINAL)
+ .allowHardware(true)
+ .build()
+
+ val result = loader.execute(request)
+ if (result is SuccessResult) {
+ val diskCacheKey = result.diskCacheKey ?: return null
+ val snapshot = loader.diskCache?.openSnapshot(diskCacheKey)
+ return snapshot?.data?.toFile()
+ } else {
+ Log.d("ShareHelper", "Failed to get cached file for URL: $url")
+ return null
+ }
+ }
+
+ private fun getSharableUri(
+ context: Context,
+ file: File,
+ ): Uri =
+ FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.provider",
+ file,
+ )
+
+ private fun shareMediaFile(
+ context: Context,
+ uri: Uri,
+ mimeType: String = "image/*",
+ ) {
+ val shareIntent =
+ Intent(Intent.ACTION_SEND).apply {
+ type = mimeType
+ putExtra(Intent.EXTRA_STREAM, uri)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+ CoroutineScope(Dispatchers.Main).launch {
+ context.startActivity(Intent.createChooser(shareIntent, "Share Image"))
+ }
+ }
+}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index c3af28ddd..4f0560954 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -47,6 +47,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -104,6 +105,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.seconds
@@ -751,6 +753,22 @@ fun ShareImageAction(
},
)
}
+
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ videoUri?.let {
+ if (videoUri.isNotEmpty()) {
+ DropdownMenuItem(
+ text = { Text("Share media...") },
+ onClick = {
+ scope.launch(Dispatchers.IO) {
+ ShareHelper.shareImageFromUrl(context, videoUri)
+ }
+ onDismiss()
+ },
+ )
+ }
+ }
}
}
diff --git a/amethyst/src/main/res/xml/file_paths.xml b/amethyst/src/main/res/xml/file_paths.xml
index a075ef96b..0b339a9cf 100644
--- a/amethyst/src/main/res/xml/file_paths.xml
+++ b/amethyst/src/main/res/xml/file_paths.xml
@@ -3,4 +3,6 @@
+
+