From 6d6d2772776fa46c403ae2306d65a6b5f5ee5fd4 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 13 Apr 2026 12:13:52 -0400 Subject: [PATCH] Add share and forward entries to all media context menu. --- .../mediaoverview/MediaOverviewContextMenu.kt | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewContextMenu.kt index 68bd0d0583..8304144c45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewContextMenu.kt @@ -1,21 +1,34 @@ package org.thoughtcrime.securesms.mediaoverview +import android.content.ActivityNotFoundException +import android.content.ComponentName +import android.content.Intent +import android.os.Build import android.view.View import android.view.ViewGroup +import android.widget.Toast +import androidx.core.app.ShareCompat import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.schedulers.Schedulers +import org.json.JSONArray import org.signal.core.util.concurrent.LifecycleDisposable import org.signal.core.util.dp +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.menu.ActionItem import org.thoughtcrime.securesms.components.menu.SignalContextMenu import org.thoughtcrime.securesms.conversation.ConversationIntents +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs import org.thoughtcrime.securesms.database.MediaTable import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.linkpreview.LinkPreview +import org.thoughtcrime.securesms.mms.PartAuthority +import org.thoughtcrime.securesms.sharing.v2.ShareActivity import org.signal.core.ui.R as CoreUiR /** @@ -42,6 +55,8 @@ class MediaOverviewContextMenu( } .show( listOfNotNull( + getForwardActionItem(mediaRecord), + getShareActionItem(mediaRecord), getSaveActionItem(mediaRecord), getDeleteActionItem(mediaRecord), getSelectActionItem(mediaRecord), @@ -50,6 +65,90 @@ class MediaOverviewContextMenu( ) } + private fun getForwardActionItem(mediaRecord: MediaTable.MediaRecord): ActionItem? { + if (mediaRecord.linkPreviewJson != null) { + return null + } + + val uri = mediaRecord.attachment?.uri ?: return null + return ActionItem( + iconRes = CoreUiR.drawable.symbol_forward_24, + title = fragment.getString(R.string.StickerManagement_menu_forward_pack) + ) { + MultiselectForwardFragmentArgs.create( + context = fragment.requireContext(), + threadId = mediaRecord.threadId, + mediaUri = uri, + contentType = mediaRecord.attachment?.contentType + ) { args -> + MultiselectForwardFragment.showBottomSheet(fragment.childFragmentManager, args) + } + } + } + + private fun getShareActionItem(mediaRecord: MediaTable.MediaRecord): ActionItem? { + if (mediaRecord.linkPreviewJson != null) { + return getShareLinkActionItem(mediaRecord) + } + + val attachment = mediaRecord.attachment ?: return null + val uri = attachment.uri ?: return null + return ActionItem( + iconRes = CoreUiR.drawable.symbol_share_android_24, + title = fragment.getString(R.string.InviteActivity_share) + ) { + val publicUri = PartAuthority.getAttachmentPublicUri(uri) + val mimeType = Intent.normalizeMimeType(attachment.contentType) + val shareIntent = ShareCompat.IntentBuilder(fragment.requireActivity()) + .setStream(publicUri) + .setType(mimeType) + .createChooserIntent() + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + if (Build.VERSION.SDK_INT < 34) { + shareIntent.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(ComponentName(fragment.requireContext(), ShareActivity::class.java))) + } + + try { + fragment.startActivity(shareIntent) + } catch (e: ActivityNotFoundException) { + Log.w(TAG, "No activity found to share media", e) + Toast.makeText(fragment.requireContext(), R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show() + } + } + } + + private fun getShareLinkActionItem(mediaRecord: MediaTable.MediaRecord): ActionItem? { + val url = try { + val jsonPreviews = JSONArray(mediaRecord.linkPreviewJson) + LinkPreview.deserialize(jsonPreviews.getJSONObject(0).toString()).url + } catch (e: Exception) { + Log.w(TAG, "Failed to deserialize link preview", e) + return null + } + + return ActionItem( + iconRes = CoreUiR.drawable.symbol_share_android_24, + title = fragment.getString(R.string.InviteActivity_share) + ) { + val shareIntent = ShareCompat.IntentBuilder(fragment.requireActivity()) + .setText(url) + .setType("text/plain") + .createChooserIntent() + + if (Build.VERSION.SDK_INT < 34) { + shareIntent.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(ComponentName(fragment.requireContext(), ShareActivity::class.java))) + } + + try { + fragment.startActivity(shareIntent) + } catch (e: ActivityNotFoundException) { + Log.w(TAG, "No activity found to share link", e) + Toast.makeText(fragment.requireContext(), R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show() + } + } + } + private fun getSaveActionItem(mediaRecord: MediaTable.MediaRecord): ActionItem? { if (mediaRecord.attachment == null) return null return ActionItem( @@ -105,4 +204,8 @@ class MediaOverviewContextMenu( fun onDelete(mediaRecord: MediaTable.MediaRecord) fun onSelect(mediaRecord: MediaTable.MediaRecord) } + + companion object { + private val TAG = Log.tag(MediaOverviewContextMenu::class.java) + } }