mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-24 03:35:58 +00:00
Improve remote delete handling in group story threads.
This commit is contained in:
@@ -36,13 +36,22 @@ class StoryGroupReplyDataSource(private val parentStoryId: Long) : PagedDataSour
|
||||
}
|
||||
|
||||
private fun readRowFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
||||
return if (MmsSmsColumns.Types.isStoryReaction(record.type)) {
|
||||
readReactionFromRecord(record)
|
||||
} else {
|
||||
readTextFromRecord(record)
|
||||
return when {
|
||||
record.isRemoteDelete -> readRemoteDeleteFromRecord(record)
|
||||
MmsSmsColumns.Types.isStoryReaction(record.type) -> readReactionFromRecord(record)
|
||||
else -> readTextFromRecord(record)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readRemoteDeleteFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
||||
return StoryGroupReplyItemData(
|
||||
key = StoryGroupReplyItemData.Key.RemoteDelete(record.id),
|
||||
sender = if (record.isOutgoing) Recipient.self() else record.individualRecipient.resolve(),
|
||||
sentAtMillis = record.dateSent,
|
||||
replyBody = StoryGroupReplyItemData.ReplyBody.RemoteDelete(record)
|
||||
)
|
||||
}
|
||||
|
||||
private fun readReactionFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
||||
return StoryGroupReplyItemData(
|
||||
key = StoryGroupReplyItemData.Key.Reaction(record.id),
|
||||
|
||||
@@ -181,9 +181,6 @@ class StoryGroupReplyFragment :
|
||||
requireContext(),
|
||||
it.sender
|
||||
),
|
||||
onPrivateReplyClick = { model ->
|
||||
requireListener<Callback>().onStartDirectReply(model.storyGroupReplyItemData.sender.id)
|
||||
},
|
||||
onCopyClick = { model ->
|
||||
val clipData = ClipData.newPlainText(requireContext().getString(R.string.app_name), model.text.message.getDisplayBody(requireContext()))
|
||||
ServiceUtil.getClipboardManager(requireContext()).setPrimaryClip(clipData)
|
||||
@@ -216,6 +213,25 @@ class StoryGroupReplyFragment :
|
||||
)
|
||||
)
|
||||
}
|
||||
is StoryGroupReplyItemData.ReplyBody.RemoteDelete -> {
|
||||
customPref(
|
||||
StoryGroupReplyItem.RemoteDeleteModel(
|
||||
storyGroupReplyItemData = it,
|
||||
remoteDelete = it.replyBody,
|
||||
nameColor = colorizer.getIncomingGroupSenderColor(
|
||||
requireContext(),
|
||||
it.sender
|
||||
),
|
||||
onDeleteClick = { model ->
|
||||
lifecycleDisposable += DeleteDialog.show(requireActivity(), setOf(model.remoteDelete.messageRecord)).subscribe { didDeleteThread ->
|
||||
if (didDeleteThread) {
|
||||
throw AssertionError("We should never end up deleting a Group Thread like this.")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
import java.util.Locale
|
||||
@@ -38,13 +39,13 @@ object StoryGroupReplyItem {
|
||||
fun register(mappingAdapter: MappingAdapter) {
|
||||
mappingAdapter.registerFactory(TextModel::class.java, LayoutFactory(::TextViewHolder, R.layout.stories_group_text_reply_item))
|
||||
mappingAdapter.registerFactory(ReactionModel::class.java, LayoutFactory(::ReactionViewHolder, R.layout.stories_group_reaction_reply_item))
|
||||
mappingAdapter.registerFactory(RemoteDeleteModel::class.java, LayoutFactory(::RemoteDeleteViewHolder, R.layout.stories_group_remote_delete_item))
|
||||
}
|
||||
|
||||
class TextModel(
|
||||
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
||||
val text: StoryGroupReplyItemData.ReplyBody.Text,
|
||||
@ColorInt val nameColor: Int,
|
||||
val onPrivateReplyClick: (TextModel) -> Unit,
|
||||
val onCopyClick: (TextModel) -> Unit,
|
||||
val onDeleteClick: (TextModel) -> Unit,
|
||||
val onMentionClick: (RecipientId) -> Unit
|
||||
@@ -74,6 +75,35 @@ object StoryGroupReplyItem {
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteDeleteModel(
|
||||
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
||||
val remoteDelete: StoryGroupReplyItemData.ReplyBody.RemoteDelete,
|
||||
val onDeleteClick: (RemoteDeleteModel) -> Unit,
|
||||
@ColorInt val nameColor: Int
|
||||
) : MappingModel<RemoteDeleteModel> {
|
||||
override fun areItemsTheSame(newItem: RemoteDeleteModel): Boolean {
|
||||
return storyGroupReplyItemData.sender == newItem.storyGroupReplyItemData.sender &&
|
||||
storyGroupReplyItemData.sentAtMillis == newItem.storyGroupReplyItemData.sentAtMillis
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(newItem: RemoteDeleteModel): Boolean {
|
||||
return storyGroupReplyItemData == newItem.storyGroupReplyItemData &&
|
||||
storyGroupReplyItemData.sender.hasSameContent(newItem.storyGroupReplyItemData.sender) &&
|
||||
nameColor == newItem.nameColor
|
||||
}
|
||||
|
||||
override fun getChangePayload(newItem: RemoteDeleteModel): Any? {
|
||||
return if (nameColor != newItem.nameColor &&
|
||||
storyGroupReplyItemData == newItem.storyGroupReplyItemData &&
|
||||
storyGroupReplyItemData.sender.hasSameContent(newItem.storyGroupReplyItemData.sender)
|
||||
) {
|
||||
NAME_COLOR_CHANGED
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReactionModel(
|
||||
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
||||
val reaction: StoryGroupReplyItemData.ReplyBody.Reaction,
|
||||
@@ -104,13 +134,12 @@ object StoryGroupReplyItem {
|
||||
}
|
||||
}
|
||||
|
||||
private class TextViewHolder(itemView: View) : MappingViewHolder<TextModel>(itemView) {
|
||||
|
||||
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||
private val name: FromTextView = itemView.findViewById(R.id.name)
|
||||
private val body: EmojiTextView = itemView.findViewById(R.id.body)
|
||||
private val date: TextView = itemView.findViewById(R.id.viewed_at)
|
||||
private val dateBelow: TextView = itemView.findViewById(R.id.viewed_at_below)
|
||||
private abstract class BaseViewHolder<T>(itemView: View) : MappingViewHolder<T>(itemView) {
|
||||
protected val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||
protected val name: FromTextView = itemView.findViewById(R.id.name)
|
||||
protected val body: EmojiTextView = itemView.findViewById(R.id.body)
|
||||
protected val date: TextView = itemView.findViewById(R.id.viewed_at)
|
||||
protected val dateBelow: TextView = itemView.findViewById(R.id.viewed_at_below)
|
||||
|
||||
init {
|
||||
body.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
||||
@@ -123,6 +152,9 @@ object StoryGroupReplyItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TextViewHolder(itemView: View) : BaseViewHolder<TextModel>(itemView) {
|
||||
|
||||
override fun bind(model: TextModel) {
|
||||
itemView.setOnLongClickListener {
|
||||
@@ -179,6 +211,39 @@ object StoryGroupReplyItem {
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoteDeleteViewHolder(itemView: View) : BaseViewHolder<RemoteDeleteModel>(itemView) {
|
||||
|
||||
override fun bind(model: RemoteDeleteModel) {
|
||||
itemView.setOnLongClickListener {
|
||||
displayContextMenu(model)
|
||||
true
|
||||
}
|
||||
|
||||
name.setTextColor(model.nameColor)
|
||||
if (payload.contains(NAME_COLOR_CHANGED)) {
|
||||
return
|
||||
}
|
||||
|
||||
AvatarUtil.loadIconIntoImageView(model.storyGroupReplyItemData.sender, avatar, DimensionUnit.DP.toPixels(28f).toInt())
|
||||
name.text = resolveName(context, model.storyGroupReplyItemData.sender)
|
||||
|
||||
date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.storyGroupReplyItemData.sentAtMillis)
|
||||
dateBelow.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.storyGroupReplyItemData.sentAtMillis)
|
||||
}
|
||||
|
||||
private fun displayContextMenu(model: RemoteDeleteModel) {
|
||||
itemView.isSelected = true
|
||||
SignalContextMenu.Builder(itemView, itemView.rootView as ViewGroup)
|
||||
.preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.START)
|
||||
.onDismiss { itemView.isSelected = false }
|
||||
.show(
|
||||
listOf(
|
||||
ActionItem(R.drawable.ic_trash_24_solid_tinted, context.getString(R.string.StoryGroupReplyItem__delete)) { model.onDeleteClick(model) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ReactionViewHolder(itemView: View) : MappingViewHolder<ReactionModel>(itemView) {
|
||||
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||
private val name: FromTextView = itemView.findViewById(R.id.name)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.stories.viewer.reply.group
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
data class StoryGroupReplyItemData(
|
||||
@@ -12,10 +13,12 @@ data class StoryGroupReplyItemData(
|
||||
sealed class ReplyBody {
|
||||
data class Text(val message: ConversationMessage) : ReplyBody()
|
||||
data class Reaction(val emoji: CharSequence) : ReplyBody()
|
||||
data class RemoteDelete(val messageRecord: MessageRecord) : ReplyBody()
|
||||
}
|
||||
|
||||
sealed class Key {
|
||||
data class Text(val messageId: Long) : Key()
|
||||
data class Reaction(val reactionId: Long) : Key()
|
||||
data class RemoteDelete(val messageId: Long) : Key()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class StoryGroupReplyRepository {
|
||||
|
||||
val threadId = SignalDatabase.mms.getThreadIdForMessage(parentStoryId)
|
||||
|
||||
ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(messageObserver)
|
||||
ApplicationDependencies.getDatabaseObserver().registerMessageInsertObserver(threadId, messageObserver)
|
||||
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(threadId, observer)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
|
||||
/**
|
||||
@@ -40,24 +41,26 @@ object StoryGroupReplySender {
|
||||
Completable.create {
|
||||
MessageSender.send(
|
||||
context,
|
||||
OutgoingMediaMessage(
|
||||
recipient,
|
||||
body.toString(),
|
||||
emptyList(),
|
||||
System.currentTimeMillis(),
|
||||
0,
|
||||
0L,
|
||||
false,
|
||||
0,
|
||||
StoryType.NONE,
|
||||
ParentStoryId.GroupReply(message.id),
|
||||
isReaction,
|
||||
null,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
mentions,
|
||||
emptySet(),
|
||||
emptySet()
|
||||
OutgoingSecureMediaMessage(
|
||||
OutgoingMediaMessage(
|
||||
recipient,
|
||||
body.toString(),
|
||||
emptyList(),
|
||||
System.currentTimeMillis(),
|
||||
0,
|
||||
0L,
|
||||
false,
|
||||
0,
|
||||
StoryType.NONE,
|
||||
ParentStoryId.GroupReply(message.id),
|
||||
isReaction,
|
||||
null,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
mentions,
|
||||
emptySet(),
|
||||
emptySet()
|
||||
)
|
||||
),
|
||||
message.threadId,
|
||||
false,
|
||||
|
||||
@@ -15,6 +15,17 @@ import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask
|
||||
|
||||
object DeleteDialog {
|
||||
|
||||
/**
|
||||
* Displays a deletion dialog for the given set of message records.
|
||||
*
|
||||
* @param context Android Context
|
||||
* @param messageRecords The message records to delete
|
||||
* @param title The dialog title
|
||||
* @param message The dialog message, or null
|
||||
* @param forceRemoteDelete Allow remote deletion, even if it would normally be disallowed
|
||||
*
|
||||
* @return a Single, who's value notes whether or not a thread deletion occurred.
|
||||
*/
|
||||
fun show(
|
||||
context: Context,
|
||||
messageRecords: Set<MessageRecord>,
|
||||
|
||||
Reference in New Issue
Block a user