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 index 2b91c47c1..4be867651 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ShareHelper.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ShareHelper.kt @@ -85,11 +85,11 @@ object ShareHelper { } ?: throw IOException("Unable to open snapshot for: $imageUrl") } - private fun getImageExtension(file: File): String = getMediaExtension(file, isVideo = false) + internal fun getImageExtension(file: File): String = getMediaExtension(file, isVideo = false) - private fun getVideoExtension(file: File): String = getMediaExtension(file, isVideo = true) + internal fun getVideoExtension(file: File): String = getMediaExtension(file, isVideo = true) - private fun getMediaExtension( + internal fun getMediaExtension( file: File, isVideo: Boolean, ): String { 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 45b813408..f57dad4be 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 @@ -762,8 +762,6 @@ fun ShareMediaAction( content: BaseMediaContent? = null, accountViewModel: AccountViewModel, ) { - val scope = accountViewModel.viewModelScope - // Track if video is downloading - hoisted here to block menu dismiss during download val isDownloadingVideo = remember { mutableStateOf(false) } @@ -817,7 +815,7 @@ fun ShareMediaAction( videoUri?.let { if (videoUri.isNotEmpty()) { M3ActionRow(icon = Icons.Outlined.Share, text = stringRes(R.string.share_image)) { - scope.launch { shareImageFile(context, videoUri, mimeType) } + accountViewModel.viewModelScope.launch { shareImageFile(context, videoUri, mimeType) } onDismiss() } } @@ -834,7 +832,7 @@ fun ShareMediaAction( ) { isDownloadingVideo.value = true accountViewModel.toastManager.toast(R.string.share_video, R.string.downloading_video_for_sharing) - scope.launch { + accountViewModel.viewModelScope.launch { shareVideoFile( context = context, videoUrl = videoUri, @@ -859,7 +857,7 @@ fun ShareMediaAction( is MediaLocalVideo -> { content.localFile?.let { localFile -> M3ActionRow(icon = Icons.Outlined.Share, text = stringRes(R.string.share_video)) { - scope.launch { shareLocalVideoFile(context, localFile, mimeType) } + accountViewModel.viewModelScope.launch { shareLocalVideoFile(context, localFile, mimeType) } onDismiss() } } @@ -895,6 +893,7 @@ private suspend fun shareImageFile( context.startActivity(Intent.createChooser(shareIntent, null)) } catch (e: Exception) { + if (e is CancellationException) throw e Log.w("ZoomableContentView", "Failed to share image: $videoUri", e) Toast.makeText(context, context.getString(R.string.unable_to_share_image), Toast.LENGTH_SHORT).show() } diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/ShareHelperTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/ShareHelperTest.kt index 509e40670..e86d33789 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/ShareHelperTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/ShareHelperTest.kt @@ -49,4 +49,118 @@ class ShareHelperTest { targetFile.delete() tempDir.delete() } + + @Test + fun getImageExtension_jpeg() { + val file = createTempFileWithBytes(byteArrayOf(0xFF.toByte(), 0xD8.toByte(), 0x00, 0x00)) + assertEquals("jpg", ShareHelper.getImageExtension(file)) + file.delete() + } + + @Test + fun getImageExtension_png() { + val file = createTempFileWithBytes(byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A)) + assertEquals("png", ShareHelper.getImageExtension(file)) + file.delete() + } + + @Test + fun getImageExtension_gif() { + val file = createTempFileWithBytes("GIF89a".toByteArray()) + assertEquals("gif", ShareHelper.getImageExtension(file)) + file.delete() + } + + @Test + fun getImageExtension_webp() { + // RIFF....WEBP + val header = ByteArray(12) + "RIFF".toByteArray().copyInto(header, 0) + "WEBP".toByteArray().copyInto(header, 8) + val file = createTempFileWithBytes(header) + assertEquals("webp", ShareHelper.getImageExtension(file)) + file.delete() + } + + @Test + fun getImageExtension_unknownDefaultsToJpg() { + val file = createTempFileWithBytes(byteArrayOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)) + assertEquals("jpg", ShareHelper.getImageExtension(file)) + file.delete() + } + + @Test + fun getImageExtension_tooShortDefaultsToJpg() { + val file = createTempFileWithBytes(byteArrayOf(0x00, 0x01)) + assertEquals("jpg", ShareHelper.getImageExtension(file)) + file.delete() + } + + @Test + fun getVideoExtension_webm() { + val file = createTempFileWithBytes(byteArrayOf(0x1A, 0x45, 0xDF.toByte(), 0xA3.toByte(), 0x00, 0x00)) + assertEquals("webm", ShareHelper.getVideoExtension(file)) + file.delete() + } + + @Test + fun getVideoExtension_avi() { + // RIFF....AVI + val header = ByteArray(12) + "RIFF".toByteArray().copyInto(header, 0) + "AVI ".toByteArray().copyInto(header, 8) + val file = createTempFileWithBytes(header) + assertEquals("avi", ShareHelper.getVideoExtension(file)) + file.delete() + } + + @Test + fun getVideoExtension_mp4Isom() { + // ....ftypisom + val header = ByteArray(12) + "ftyp".toByteArray().copyInto(header, 4) + "isom".toByteArray().copyInto(header, 8) + val file = createTempFileWithBytes(header) + assertEquals("mp4", ShareHelper.getVideoExtension(file)) + file.delete() + } + + @Test + fun getVideoExtension_mov() { + // ....ftypqt__ + val header = ByteArray(12) + "ftyp".toByteArray().copyInto(header, 4) + "qt ".toByteArray().copyInto(header, 8) + val file = createTempFileWithBytes(header) + assertEquals("mov", ShareHelper.getVideoExtension(file)) + file.delete() + } + + @Test + fun getVideoExtension_unknownDefaultsToMp4() { + val file = createTempFileWithBytes(byteArrayOf(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)) + assertEquals("mp4", ShareHelper.getVideoExtension(file)) + file.delete() + } + + @Test + fun getMediaExtension_emptyFileDefaultsToImage() { + val file = createTempFileWithBytes(byteArrayOf()) + assertEquals("jpg", ShareHelper.getMediaExtension(file, isVideo = false)) + file.delete() + } + + @Test + fun getMediaExtension_emptyFileDefaultsToVideo() { + val file = createTempFileWithBytes(byteArrayOf()) + assertEquals("mp4", ShareHelper.getMediaExtension(file, isVideo = true)) + file.delete() + } + + private fun createTempFileWithBytes(bytes: ByteArray): File { + val file = File.createTempFile("sharehelpertest", ".tmp") + file.deleteOnExit() + file.writeBytes(bytes) + return file + } }