Allow PDF downloads, route them to Downloads/Amethyst
expose Save-to-Disk button for PDFs add Save-to-Phone row for PDFs
This commit is contained in:
@@ -31,7 +31,7 @@ import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk.PICTURES_SUBDIRECTORY
|
||||
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk.AMETHYST_SUBDIRECTORY
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -87,7 +87,7 @@ object MediaSaverToDisk {
|
||||
/**
|
||||
* Saves the image to the gallery. May require a storage permission.
|
||||
*
|
||||
* @see PICTURES_SUBDIRECTORY
|
||||
* @see AMETHYST_SUBDIRECTORY
|
||||
*/
|
||||
suspend fun downloadAndSave(
|
||||
url: String,
|
||||
@@ -120,9 +120,9 @@ object MediaSaverToDisk {
|
||||
?.trim()
|
||||
|
||||
val realType =
|
||||
headerType?.takeIf(::isMediaMimeType)
|
||||
?: mimeType?.takeIf(::isMediaMimeType)
|
||||
?: getMimeTypeFromExtension(trimmedUrl).takeIf(::isMediaMimeType)
|
||||
headerType?.takeIf(::isSaveableMimeType)
|
||||
?: mimeType?.takeIf(::isSaveableMimeType)
|
||||
?: getMimeTypeFromExtension(trimmedUrl).takeIf(::isSaveableMimeType)
|
||||
?: ""
|
||||
check(realType.isNotBlank()) { "Can't find out the content type" }
|
||||
|
||||
@@ -154,12 +154,13 @@ object MediaSaverToDisk {
|
||||
MimeTypeMap.getSingleton().getMimeTypeFromExtension(it).orEmpty()
|
||||
}
|
||||
|
||||
private fun isMediaMimeType(type: String): Boolean =
|
||||
private fun isSaveableMimeType(type: String): Boolean =
|
||||
type.isNotBlank() &&
|
||||
(
|
||||
type.startsWith("image/", ignoreCase = true) ||
|
||||
type.startsWith("video/", ignoreCase = true) ||
|
||||
type.startsWith("audio/", ignoreCase = true)
|
||||
type.startsWith("audio/", ignoreCase = true) ||
|
||||
type.equals(PDF_MIME_TYPE, ignoreCase = true)
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
@@ -205,22 +206,32 @@ object MediaSaverToDisk {
|
||||
contentResolver: ContentResolver,
|
||||
) {
|
||||
val cleanMimeType = contentType.substringBefore(";").trim()
|
||||
|
||||
val (masterUri, baseDir) =
|
||||
when {
|
||||
cleanMimeType.startsWith("image/", ignoreCase = true) -> {
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI to Environment.DIRECTORY_PICTURES
|
||||
}
|
||||
|
||||
cleanMimeType.equals(PDF_MIME_TYPE, ignoreCase = true) -> {
|
||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI to Environment.DIRECTORY_DOWNLOADS
|
||||
}
|
||||
|
||||
else -> {
|
||||
MediaStore.Video.Media.EXTERNAL_CONTENT_URI to Environment.DIRECTORY_PICTURES
|
||||
}
|
||||
}
|
||||
|
||||
val contentValues =
|
||||
ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, cleanMimeType)
|
||||
put(
|
||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||
Environment.DIRECTORY_PICTURES + File.separatorChar + PICTURES_SUBDIRECTORY,
|
||||
baseDir + File.separatorChar + AMETHYST_SUBDIRECTORY,
|
||||
)
|
||||
}
|
||||
|
||||
val masterUri =
|
||||
when {
|
||||
contentType.startsWith("image") -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||
else -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
|
||||
val uri = contentResolver.insert(masterUri, contentValues)
|
||||
checkNotNull(uri) { "Can't insert the new content" }
|
||||
|
||||
@@ -244,7 +255,7 @@ object MediaSaverToDisk {
|
||||
val subdirectory =
|
||||
File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
|
||||
PICTURES_SUBDIRECTORY,
|
||||
AMETHYST_SUBDIRECTORY,
|
||||
).apply {
|
||||
if (!exists()) mkdirs()
|
||||
}
|
||||
@@ -260,5 +271,6 @@ object MediaSaverToDisk {
|
||||
|
||||
private fun trimInlineMetaData(url: String): String = url.substringBefore("#")
|
||||
|
||||
private const val PICTURES_SUBDIRECTORY = "Amethyst"
|
||||
private const val AMETHYST_SUBDIRECTORY = "Amethyst"
|
||||
private const val PDF_MIME_TYPE = "application/pdf"
|
||||
}
|
||||
|
||||
+18
-4
@@ -87,6 +87,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaPreloadedContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlPdf
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoViewInner
|
||||
@@ -377,7 +378,9 @@ private fun DialogContent(
|
||||
|
||||
ShareMediaAction(accountViewModel = accountViewModel, popupExpanded = sharePopupExpanded, myContent, onDismiss = { sharePopupExpanded.value = false })
|
||||
}
|
||||
}
|
||||
|
||||
if (myContent is MediaUrlImage || myContent is MediaLocalImage || myContent is MediaUrlPdf) {
|
||||
if (myContent !is MediaUrlContent || !isLiveStreaming(myContent.url)) {
|
||||
val localContext = LocalContext.current
|
||||
|
||||
@@ -447,22 +450,33 @@ private fun showToastOnMain(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveMediaToGallery(
|
||||
internal suspend fun saveMediaToGallery(
|
||||
content: BaseMediaContent,
|
||||
localContext: Context,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val isImage = content is MediaUrlImage || content is MediaLocalImage
|
||||
val isPdf = content is MediaUrlPdf
|
||||
|
||||
val success = if (isImage) R.string.image_saved_to_the_gallery else R.string.video_saved_to_the_gallery
|
||||
val failure = if (isImage) R.string.failed_to_save_the_image else R.string.failed_to_save_the_video
|
||||
val success =
|
||||
when {
|
||||
isImage -> R.string.image_saved_to_the_gallery
|
||||
isPdf -> R.string.pdf_saved_to_the_gallery
|
||||
else -> R.string.video_saved_to_the_gallery
|
||||
}
|
||||
val failure =
|
||||
when {
|
||||
isImage -> R.string.failed_to_save_the_image
|
||||
isPdf -> R.string.failed_to_save_the_pdf
|
||||
else -> R.string.failed_to_save_the_video
|
||||
}
|
||||
|
||||
if (content is MediaUrlContent) {
|
||||
MediaSaverToDisk.downloadAndSave(
|
||||
content.url,
|
||||
mimeType = content.mimeType,
|
||||
okHttpClient = {
|
||||
if (isImage) {
|
||||
if (isImage || isPdf) {
|
||||
accountViewModel.httpClientBuilder.okHttpClientForImage(it)
|
||||
} else {
|
||||
accountViewModel.httpClientBuilder.okHttpClientForVideo(it)
|
||||
|
||||
+15
@@ -914,6 +914,21 @@ fun ShareMediaAction(
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
if (content is MediaUrlPdf && videoUri != null && !videoUri.startsWith("file")) {
|
||||
val appContext = LocalContext.current.applicationContext
|
||||
M3ActionRow(icon = MaterialSymbols.Download, text = stringRes(R.string.download_to_phone)) {
|
||||
accountViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||
saveMediaToGallery(content, appContext, accountViewModel)
|
||||
}
|
||||
Toast
|
||||
.makeText(
|
||||
appContext,
|
||||
stringRes(appContext, R.string.media_download_has_started_toast),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
postNostrUri?.let {
|
||||
M3ActionRow(icon = MaterialSymbols.ContentCopy, text = stringRes(R.string.copy_the_note_id_to_the_clipboard)) {
|
||||
scope.launch {
|
||||
|
||||
@@ -176,6 +176,8 @@
|
||||
<string name="failed_to_save_the_image">Failed to save the image</string>
|
||||
<string name="video_saved_to_the_gallery">Video saved to the phone\'s video gallery</string>
|
||||
<string name="failed_to_save_the_video">Failed to save the video</string>
|
||||
<string name="pdf_saved_to_the_gallery">PDF saved to Downloads/Amethyst</string>
|
||||
<string name="failed_to_save_the_pdf">Failed to save the PDF</string>
|
||||
<string name="upload_image">Upload Image</string>
|
||||
<string name="upload_file">Upload File</string>
|
||||
<string name="take_a_picture">Take a picture</string>
|
||||
|
||||
Reference in New Issue
Block a user