fix: image sharing fails due to coroutine scope cancellation

The share media dialog used `rememberCoroutineScope()` which shadowed
the outer `accountViewModel.viewModelScope`. When onDismiss() was called
immediately after launching the share coroutine, the dialog-scoped
coroutine was cancelled, causing CancellationException to be caught
as a generic error and showing "Unable to share image" toast.

Fix: use viewModelScope for share operations so they survive dialog
dismissal, and re-throw CancellationException in shareImageFile.

https://claude.ai/code/session_01Euj5mXfjneNCx9m49YTp92
This commit is contained in:
Claude
2026-04-04 15:43:10 +00:00
parent e2a7f14c9a
commit 7a70c98158
@@ -762,7 +762,7 @@ fun ShareMediaAction(
content: BaseMediaContent? = null, content: BaseMediaContent? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
val scope = accountViewModel.viewModelScope val viewModelScope = accountViewModel.viewModelScope
// Track if video is downloading - hoisted here to block menu dismiss during download // Track if video is downloading - hoisted here to block menu dismiss during download
val isDownloadingVideo = remember { mutableStateOf(false) } val isDownloadingVideo = remember { mutableStateOf(false) }
@@ -817,7 +817,7 @@ fun ShareMediaAction(
videoUri?.let { videoUri?.let {
if (videoUri.isNotEmpty()) { if (videoUri.isNotEmpty()) {
M3ActionRow(icon = Icons.Outlined.Share, text = stringRes(R.string.share_image)) { M3ActionRow(icon = Icons.Outlined.Share, text = stringRes(R.string.share_image)) {
scope.launch { shareImageFile(context, videoUri, mimeType) } viewModelScope.launch { shareImageFile(context, videoUri, mimeType) }
onDismiss() onDismiss()
} }
} }
@@ -833,7 +833,7 @@ fun ShareMediaAction(
enabled = !isDownloadingVideo.value, enabled = !isDownloadingVideo.value,
) { ) {
isDownloadingVideo.value = true isDownloadingVideo.value = true
scope.launch { viewModelScope.launch {
shareVideoFile( shareVideoFile(
context = context, context = context,
videoUrl = videoUri, videoUrl = videoUri,
@@ -858,7 +858,7 @@ fun ShareMediaAction(
is MediaLocalVideo -> { is MediaLocalVideo -> {
content.localFile?.let { localFile -> content.localFile?.let { localFile ->
M3ActionRow(icon = Icons.Outlined.Share, text = stringRes(R.string.share_video)) { M3ActionRow(icon = Icons.Outlined.Share, text = stringRes(R.string.share_video)) {
scope.launch { shareLocalVideoFile(context, localFile, mimeType) } viewModelScope.launch { shareLocalVideoFile(context, localFile, mimeType) }
onDismiss() onDismiss()
} }
} }
@@ -894,6 +894,7 @@ private suspend fun shareImageFile(
context.startActivity(Intent.createChooser(shareIntent, null)) context.startActivity(Intent.createChooser(shareIntent, null))
} catch (e: Exception) { } catch (e: Exception) {
if (e is CancellationException) throw e
Log.w("ZoomableContentView", "Failed to share image: $videoUri", 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() Toast.makeText(context, context.getString(R.string.unable_to_share_image), Toast.LENGTH_SHORT).show()
} }