mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 02:39:55 +01:00
CFV2 Save to Disk / Copy Text Content.
This commit is contained in:
committed by
Cody Henthorne
parent
399421e20e
commit
b785b3f887
@@ -33,7 +33,7 @@ import androidx.vectordrawable.graphics.drawable.AnimatorInflaterCompat;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.glide.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
|
||||
@@ -394,7 +394,8 @@ public final class ConversationReactionOverlay extends FrameLayout {
|
||||
|
||||
private void updateToolbarShade(@NonNull Activity activity) {
|
||||
View toolbar = activity.findViewById(R.id.toolbar);
|
||||
View bannerContainer = activity.findViewById(R.id.conversation_banner_container);
|
||||
View bannerContainer = activity.findViewById(SignalStore.internalValues().useConversationFragmentV2() ? R.id.conversation_banner
|
||||
: R.id.conversation_banner_container);
|
||||
|
||||
LayoutParams layoutParams = (LayoutParams) toolbarShade.getLayoutParams();
|
||||
layoutParams.height = toolbar.getHeight() + bannerContainer.getHeight();
|
||||
@@ -410,9 +411,9 @@ public final class ConversationReactionOverlay extends FrameLayout {
|
||||
|
||||
private int getInputPanelHeight(@NonNull Activity activity) {
|
||||
if (SignalStore.internalValues().useConversationFragmentV2()) {
|
||||
Barrier conversationBottomPanelBarrier = activity.findViewById(R.id.conversation_bottom_panel_barrier);
|
||||
View bottomPanel = activity.findViewById(R.id.conversation_input_panel);
|
||||
|
||||
return activity.getResources().getDisplayMetrics().heightPixels - conversationBottomPanelBarrier.getTop();
|
||||
return bottomPanel.getHeight();
|
||||
}
|
||||
|
||||
View bottomPanel = activity.findViewById(R.id.conversation_activity_panel_parent);
|
||||
|
||||
@@ -161,7 +161,6 @@ class MultiselectItemDecoration(
|
||||
/**
|
||||
* Draws the background shade.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
val adapter = parent.adapter as ConversationAdapterBridge
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Intent
|
||||
@@ -84,6 +85,8 @@ import org.thoughtcrime.securesms.components.ComposeText
|
||||
import org.thoughtcrime.securesms.components.HidingLinearLayout
|
||||
import org.thoughtcrime.securesms.components.InputAwareConstraintLayout
|
||||
import org.thoughtcrime.securesms.components.InputPanel
|
||||
import org.thoughtcrime.securesms.components.ProgressCardDialogFragment
|
||||
import org.thoughtcrime.securesms.components.ProgressCardDialogFragmentArgs
|
||||
import org.thoughtcrime.securesms.components.ScrollToPositionDelegate
|
||||
import org.thoughtcrime.securesms.components.SendButton
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
@@ -171,6 +174,7 @@ import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -194,7 +198,9 @@ import org.thoughtcrime.securesms.util.DrawableUtil
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentUtil
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
||||
import org.thoughtcrime.securesms.util.StorageUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.WindowUtil
|
||||
@@ -743,9 +749,10 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
ConversationAdapter.initializePool(binding.conversationItemRecycler.recycledViewPool)
|
||||
adapter.setPagingController(viewModel.pagingController)
|
||||
|
||||
binding.conversationItemRecycler.adapter = adapter
|
||||
giphyMp4ProjectionRecycler = initializeGiphyMp4()
|
||||
recyclerViewColorizer = RecyclerViewColorizer(binding.conversationItemRecycler)
|
||||
recyclerViewColorizer.setChatColors(args.chatColors)
|
||||
|
||||
binding.conversationItemRecycler.adapter = adapter
|
||||
multiselectItemDecoration = MultiselectItemDecoration(
|
||||
requireContext()
|
||||
) { viewModel.wallpaperSnapshot }
|
||||
@@ -756,12 +763,11 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
binding.conversationItemRecycler.addItemDecoration(multiselectItemDecoration)
|
||||
viewLifecycleOwner.lifecycle.addObserver(multiselectItemDecoration)
|
||||
|
||||
giphyMp4ProjectionRecycler = initializeGiphyMp4()
|
||||
|
||||
val layoutTransitionListener = BubbleLayoutTransitionListener(binding.conversationItemRecycler)
|
||||
viewLifecycleOwner.lifecycle.addObserver(layoutTransitionListener)
|
||||
|
||||
recyclerViewColorizer = RecyclerViewColorizer(binding.conversationItemRecycler)
|
||||
recyclerViewColorizer.setChatColors(args.chatColors)
|
||||
|
||||
binding.conversationItemRecycler.itemAnimator = ConversationItemAnimator(
|
||||
isInMultiSelectMode = adapter.selectedItems::isNotEmpty,
|
||||
shouldPlayMessageAnimations = {
|
||||
@@ -1118,11 +1124,44 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
}
|
||||
|
||||
private fun handleSaveAttachment(record: MediaMmsMessageRecord) {
|
||||
// TODO [cfv2] -- Not implemented yet.
|
||||
if (record.isViewOnce) {
|
||||
error("Cannot save a view-once message")
|
||||
}
|
||||
|
||||
val attachments = SaveAttachmentUtil.getAttachmentsForRecord(record)
|
||||
|
||||
SaveAttachmentUtil.showWarningDialog(requireContext(), attachments.size) { _, _ ->
|
||||
if (StorageUtil.canWriteToMediaStore()) {
|
||||
performAttachmentSave(attachments)
|
||||
} else {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied { toast(R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, toastDuration = Toast.LENGTH_LONG) }
|
||||
.onAllGranted { performAttachmentSave(attachments) }
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun performAttachmentSave(attachments: Set<SaveAttachmentUtil.SaveAttachment>) {
|
||||
val progressDialog = ProgressCardDialogFragment()
|
||||
progressDialog.arguments = ProgressCardDialogFragmentArgs.Builder(
|
||||
resources.getQuantityString(R.plurals.ConversationFragment_saving_n_attachments_to_sd_card, attachments.size, attachments.size)
|
||||
).build().toBundle()
|
||||
|
||||
SaveAttachmentUtil.saveAttachments(attachments)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSubscribe { progressDialog.show(parentFragmentManager, null) }
|
||||
.doOnTerminate { progressDialog.dismissAllowingStateLoss() }
|
||||
.subscribeBy { it.toast(requireContext()) }
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
private fun handleCopyMessage(messageParts: Set<MultiselectPart>) {
|
||||
// TODO [cfv2] -- Not implemented yet.
|
||||
viewModel.copyToClipboard(requireContext(), messageParts).subscribe().addTo(disposables)
|
||||
}
|
||||
|
||||
private fun handleDisplayDetails(conversationMessage: ConversationMessage) {
|
||||
|
||||
@@ -8,12 +8,14 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.toOptional
|
||||
@@ -27,8 +29,10 @@ import org.thoughtcrime.securesms.components.reminder.PendingGroupJoinRequestsRe
|
||||
import org.thoughtcrime.securesms.components.reminder.Reminder
|
||||
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder
|
||||
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper
|
||||
import org.thoughtcrime.securesms.conversation.colors.NameColor
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationDataSource
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
|
||||
import org.thoughtcrime.securesms.database.GroupTable
|
||||
@@ -59,6 +63,9 @@ import org.thoughtcrime.securesms.recipients.RecipientFormattingException
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.hasTextSlide
|
||||
import org.thoughtcrime.securesms.util.requireTextSlide
|
||||
import java.io.IOException
|
||||
import java.util.Optional
|
||||
import kotlin.math.max
|
||||
@@ -228,15 +235,19 @@ class ConversationRepository(
|
||||
ApplicationDependencies.getJobManager().add(ServiceOutageDetectionJob())
|
||||
ServiceOutageReminder()
|
||||
}
|
||||
|
||||
groupRecord != null && groupRecord.actionableRequestingMembersCount > 0 -> {
|
||||
PendingGroupJoinRequestsReminder(groupRecord.actionableRequestingMembersCount)
|
||||
}
|
||||
|
||||
groupRecord != null && groupRecord.gv1MigrationSuggestions.isNotEmpty() -> {
|
||||
GroupsV1MigrationSuggestionsReminder(groupRecord.gv1MigrationSuggestions)
|
||||
}
|
||||
|
||||
isInBubble && !SignalStore.tooltips().hasSeenBubbleOptOutTooltip() && Build.VERSION.SDK_INT > 29 -> {
|
||||
BubbleOptOutReminder()
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -301,6 +312,50 @@ class ConversationRepository(
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the selected content to the clipboard. Maybe will emit either the copied contents or
|
||||
* a complete which means there were no contents to be copied.
|
||||
*/
|
||||
fun copyToClipboard(context: Context, messageParts: Set<MultiselectPart>): Maybe<CharSequence> {
|
||||
return Maybe.fromCallable { extractBodies(context, messageParts) }
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSuccess {
|
||||
Util.copyToClipboard(context, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractBodies(context: Context, messageParts: Set<MultiselectPart>): CharSequence {
|
||||
return messageParts
|
||||
.asSequence()
|
||||
.sortedBy { it.getMessageRecord().dateReceived }
|
||||
.map { it.conversationMessage }
|
||||
.distinct()
|
||||
.mapNotNull { message ->
|
||||
if (message.messageRecord.hasTextSlide()) {
|
||||
val textSlideUri = message.messageRecord.requireTextSlide().uri
|
||||
if (textSlideUri == null) {
|
||||
message.getDisplayBody(context)
|
||||
} else {
|
||||
try {
|
||||
PartAuthority.getAttachmentStream(context, textSlideUri).use {
|
||||
val body = StreamUtil.readFullyAsString(it)
|
||||
ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, message.messageRecord, body, message.threadRecipient)
|
||||
.getDisplayBody(context)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "failed to read text slide data.")
|
||||
null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.getDisplayBody(context)
|
||||
}
|
||||
}
|
||||
.filterNot(Util::isEmpty)
|
||||
.joinToString("\n")
|
||||
}
|
||||
|
||||
data class MessageCounts(
|
||||
val unread: Int,
|
||||
val mentions: Int
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
@@ -27,6 +28,7 @@ import org.signal.paging.ProxyPagingController
|
||||
import org.thoughtcrime.securesms.components.reminder.Reminder
|
||||
import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper
|
||||
import org.thoughtcrime.securesms.conversation.colors.NameColor
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
@@ -258,4 +260,8 @@ class ConversationViewModel(
|
||||
fun getTemporaryViewOnceUri(mmsMessageRecord: MmsMessageRecord): Maybe<Uri> {
|
||||
return repository.getTemporaryViewOnceUri(mmsMessageRecord).observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun copyToClipboard(context: Context, messageParts: Set<MultiselectPart>): Maybe<CharSequence> {
|
||||
return repository.copyToClipboard(context, messageParts)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user