Handling Control Messages longpress and deletion

pull/1518/head
ThomasSession 7 months ago
parent c50d38e85c
commit 94688c2b07

@ -130,6 +130,7 @@ import org.thoughtcrime.securesms.conversation.v2.mention.MentionViewModel
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
@ -1311,8 +1312,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private fun showConversationReaction(message: MessageRecord, messageView: View) { private fun showConversationReaction(message: MessageRecord, messageView: View) {
val messageContentView = when(messageView){ val messageContentView = when(messageView){
is VisibleMessageView -> messageView.messageContentView is VisibleMessageView -> messageView.messageContentView
else -> messageView is ControlMessageView -> messageView.controlContentView
} else -> null
} ?: return
val messageContentBitmap = try { val messageContentBitmap = try {
messageContentView.drawToBitmap() messageContentView.drawToBitmap()

@ -223,7 +223,6 @@ class ConversationReactionOverlay : FrameLayout {
endScale = spaceAvailableForItem / conversationItemSnapshot.height endScale = spaceAvailableForItem / conversationItemSnapshot.height
endX += Util.halfOffsetFromScale(conversationItemSnapshot.width, endScale) * if (isMessageOnLeft) -1 else 1 endX += Util.halfOffsetFromScale(conversationItemSnapshot.width, endScale) * if (isMessageOnLeft) -1 else 1
endY = spaceForReactionBar - Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale) endY = spaceForReactionBar - Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale)
val contextMenuTop = endY + conversationItemSnapshot.height * endScale
reactionBarBackgroundY = reactionBarTopPadding //getReactionBarOffsetForTouch(selectedConversationModel.getBubbleY(), contextMenuTop + Util.halfOffsetFromScale(conversationItemSnapshot.getHeight(), endScale), menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY); reactionBarBackgroundY = reactionBarTopPadding //getReactionBarOffsetForTouch(selectedConversationModel.getBubbleY(), contextMenuTop + Util.halfOffsetFromScale(conversationItemSnapshot.getHeight(), endScale), menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY);
endApparentTop = endY + Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale) endApparentTop = endY + Util.halfOffsetFromScale(conversationItemSnapshot.height, endScale)
} else { } else {
@ -272,11 +271,17 @@ class ConversationReactionOverlay : FrameLayout {
revealAnimatorSet.start() revealAnimatorSet.start()
if (isWideLayout) { if (isWideLayout) {
val scrubberRight = scrubberX + scrubberWidth val scrubberRight = scrubberX + scrubberWidth
val offsetX = if (isMessageOnLeft) scrubberRight + menuPadding else scrubberX - contextMenu.getMaxWidth() - menuPadding val offsetX = when {
isMessageOnLeft -> scrubberRight + menuPadding
else -> scrubberX - contextMenu.getMaxWidth() - menuPadding
}
contextMenu.show(offsetX.toInt(), Math.min(backgroundView.y, (overlayHeight - contextMenu.getMaxHeight()).toFloat()).toInt()) contextMenu.show(offsetX.toInt(), Math.min(backgroundView.y, (overlayHeight - contextMenu.getMaxHeight()).toFloat()).toInt())
} else { } else {
val contentX = if (isMessageOnLeft) scrubberHorizontalMargin.toFloat() else selectedConversationModel.bubbleX val contentX = if (isMessageOnLeft) scrubberHorizontalMargin.toFloat() else selectedConversationModel.bubbleX
val offsetX = if (isMessageOnLeft) contentX else -contextMenu.getMaxWidth() + contentX + bubbleWidth val offsetX = when {
isMessageOnLeft -> contentX
else -> -contextMenu.getMaxWidth() + contentX + bubbleWidth
}
val menuTop = endApparentTop + conversationItemSnapshot.height * endScale val menuTop = endApparentTop + conversationItemSnapshot.height * endScale
contextMenu.show(offsetX.toInt(), (menuTop + menuPadding).toInt()) contextMenu.show(offsetX.toInt(), (menuTop + menuPadding).toInt())
} }
@ -527,8 +532,12 @@ class ConversationReactionOverlay : FrameLayout {
val recipient = get(context).threadDatabase().getRecipientForThreadId(message.threadId) val recipient = get(context).threadDatabase().getRecipientForThreadId(message.threadId)
?: return emptyList() ?: return emptyList()
val userPublicKey = getLocalNumber(context)!! val userPublicKey = getLocalNumber(context)!!
// control messages and "marked as deleted" messages can only delete
val isDeleteOnly = message.isDeleted || message.isControlMessage
// Select message // Select message
if(!message.isDeleted) { if(!isDeleteOnly) {
items += ActionItem( items += ActionItem(
R.attr.menu_select_icon, R.attr.menu_select_icon,
R.string.select, R.string.select,
@ -538,15 +547,15 @@ class ConversationReactionOverlay : FrameLayout {
} }
// Reply // Reply
val canWrite = openGroup == null || openGroup.canWrite val canWrite = openGroup == null || openGroup.canWrite
if (canWrite && !message.isPending && !message.isFailed && !message.isOpenGroupInvitation && !message.isDeleted) { if (canWrite && !message.isPending && !message.isFailed && !message.isOpenGroupInvitation && !isDeleteOnly) {
items += ActionItem(R.attr.menu_reply_icon, R.string.reply, { handleActionItemClicked(Action.REPLY) }, R.string.AccessibilityId_reply) items += ActionItem(R.attr.menu_reply_icon, R.string.reply, { handleActionItemClicked(Action.REPLY) }, R.string.AccessibilityId_reply)
} }
// Copy message text // Copy message text
if (!containsControlMessage && hasText && !message.isDeleted) { if (!containsControlMessage && hasText && !isDeleteOnly) {
items += ActionItem(R.attr.menu_copy_icon, R.string.copy, { handleActionItemClicked(Action.COPY_MESSAGE) }) items += ActionItem(R.attr.menu_copy_icon, R.string.copy, { handleActionItemClicked(Action.COPY_MESSAGE) })
} }
// Copy Account ID // Copy Account ID
if (!recipient.isCommunityRecipient && message.isIncoming && !message.isDeleted) { if (!recipient.isCommunityRecipient && message.isIncoming && !isDeleteOnly) {
items += ActionItem(R.attr.menu_copy_icon, R.string.accountIDCopy, { handleActionItemClicked(Action.COPY_ACCOUNT_ID) }) items += ActionItem(R.attr.menu_copy_icon, R.string.accountIDCopy, { handleActionItemClicked(Action.COPY_ACCOUNT_ID) })
} }
// Delete message // Delete message
@ -555,15 +564,15 @@ class ConversationReactionOverlay : FrameLayout {
R.string.AccessibilityId_deleteMessage, message.subtitle, ThemeUtil.getThemedColor(context, R.attr.danger)) R.string.AccessibilityId_deleteMessage, message.subtitle, ThemeUtil.getThemedColor(context, R.attr.danger))
} }
// Ban user // Ban user
if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey) && !message.isDeleted) { if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey) && !isDeleteOnly) {
items += ActionItem(R.attr.menu_block_icon, R.string.banUser, { handleActionItemClicked(Action.BAN_USER) }) items += ActionItem(R.attr.menu_block_icon, R.string.banUser, { handleActionItemClicked(Action.BAN_USER) })
} }
// Ban and delete all // Ban and delete all
if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey) && !message.isDeleted) { if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey) && !isDeleteOnly) {
items += ActionItem(R.attr.menu_trash_icon, R.string.banDeleteAll, { handleActionItemClicked(Action.BAN_AND_DELETE_ALL) }) items += ActionItem(R.attr.menu_trash_icon, R.string.banDeleteAll, { handleActionItemClicked(Action.BAN_AND_DELETE_ALL) })
} }
// Message detail // Message detail
if(!message.isDeleted) { if(!isDeleteOnly) {
items += ActionItem( items += ActionItem(
R.attr.menu_info_icon, R.attr.menu_info_icon,
R.string.messageInfo, R.string.messageInfo,
@ -578,7 +587,7 @@ class ConversationReactionOverlay : FrameLayout {
items += ActionItem(R.attr.menu_reply_icon, R.string.resync, { handleActionItemClicked(Action.RESYNC) }) items += ActionItem(R.attr.menu_reply_icon, R.string.resync, { handleActionItemClicked(Action.RESYNC) })
} }
// Save media.. // Save media..
if (message.isMms && !message.isDeleted) { if (message.isMms && !isDeleteOnly) {
// ..but only provide the save option if the there is a media attachment which has finished downloading. // ..but only provide the save option if the there is a media attachment which has finished downloading.
val mmsMessage = message as MediaMmsMessageRecord val mmsMessage = message as MediaMmsMessageRecord
if (mmsMessage.containsMediaSlide() && !mmsMessage.isMediaPending) { if (mmsMessage.containsMediaSlide() && !mmsMessage.isMediaPending) {
@ -591,8 +600,8 @@ class ConversationReactionOverlay : FrameLayout {
} }
// deleted messages have no emoji reactions // deleted messages have no emoji reactions
backgroundView.isVisible = !message.isDeleted backgroundView.isVisible = !isDeleteOnly
foregroundView.isVisible = !message.isDeleted foregroundView.isVisible = !isDeleteOnly
return items return items
} }

@ -235,8 +235,6 @@ class ConversationViewModel(
// Refer to our figma document for info on message deletion [https://www.figma.com/design/kau6LggVcMMWmZRMibEo8F/Standardise-Message-Deletion?node-id=0-1&t=dEPcU0SZ9G2s4gh2-0] // Refer to our figma document for info on message deletion [https://www.figma.com/design/kau6LggVcMMWmZRMibEo8F/Standardise-Message-Deletion?node-id=0-1&t=dEPcU0SZ9G2s4gh2-0]
//todo DELETION handle control messages deletion ( and make clickable )
//todo DELETION handle multi select scenarios //todo DELETION handle multi select scenarios
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
@ -304,7 +302,7 @@ class ConversationViewModel(
* otherwise they will appear as a special type of message * otherwise they will appear as a special type of message
* that says something like "This message was deleted" * that says something like "This message was deleted"
*/ */
fun deletedLocally(messages: Set<MessageRecord>) { fun deleteLocally(messages: Set<MessageRecord>) {
// make sure to stop audio messages, if any // make sure to stop audio messages, if any
messages.filterIsInstance<MmsMessageRecord>() messages.filterIsInstance<MmsMessageRecord>()
.mapNotNull { it.slideDeck.audioSlide } .mapNotNull { it.slideDeck.audioSlide }
@ -734,7 +732,7 @@ class ConversationViewModel(
it.copy(deleteDeviceOnly = null) it.copy(deleteDeviceOnly = null)
} }
deletedLocally(command.messages) deleteLocally(command.messages)
} }
is Commands.MarkAsDeletedForEveryone -> { is Commands.MarkAsDeletedForEveryone -> {
markAsDeletedForEveryone(command.data) markAsDeletedForEveryone(command.data)

@ -6,6 +6,7 @@ import android.content.Intent
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isGone import androidx.core.view.isGone
@ -54,6 +55,8 @@ class ControlMessageView : LinearLayout {
@Inject lateinit var disappearingMessages: DisappearingMessages @Inject lateinit var disappearingMessages: DisappearingMessages
val controlContentView: View get() = binding.controlContentView
init { init {
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
} }
@ -84,7 +87,7 @@ class ControlMessageView : LinearLayout {
&& message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE)
&& threadRecipient?.isGroupRecipient != true && threadRecipient?.isGroupRecipient != true
followSetting.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } binding.controlContentView.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) }
} }
} }
message.isMediaSavedNotification -> { message.isMediaSavedNotification -> {
@ -128,7 +131,7 @@ class ControlMessageView : LinearLayout {
} }
// remove clicks by default // remove clicks by default
setOnClickListener(null) binding.controlContentView.setOnClickListener(null)
hideInfo() hideInfo()
// handle click behaviour depending on criteria // handle click behaviour depending on criteria
@ -138,7 +141,7 @@ class ControlMessageView : LinearLayout {
// show a dedicated privacy dialog // show a dedicated privacy dialog
!TextSecurePreferences.isCallNotificationsEnabled(context) -> { !TextSecurePreferences.isCallNotificationsEnabled(context) -> {
showInfo() showInfo()
setOnClickListener { binding.controlContentView.setOnClickListener {
context.showSessionDialog { context.showSessionDialog {
val titleTxt = context.getSubbedString( val titleTxt = context.getSubbedString(
R.string.callsMissedCallFrom, R.string.callsMissedCallFrom,
@ -165,7 +168,7 @@ class ControlMessageView : LinearLayout {
// show a dedicated permission dialog // show a dedicated permission dialog
!Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO) -> { !Permissions.hasAll(context, Manifest.permission.RECORD_AUDIO) -> {
showInfo() showInfo()
setOnClickListener { binding.controlContentView.setOnClickListener {
context.showSessionDialog { context.showSessionDialog {
val titleTxt = context.getSubbedString( val titleTxt = context.getSubbedString(
R.string.callsMissedCallFrom, R.string.callsMissedCallFrom,
@ -201,11 +204,8 @@ class ControlMessageView : LinearLayout {
binding.callView.isVisible = message.isCallLog binding.callView.isVisible = message.isCallLog
// handle long clicked if it was passed on // handle long clicked if it was passed on
//todo DELETION currently control messages lose their ability to be clickable due to the long click, like the "mised phone call" CM
Log.d("", "*** Has long click? $longPress")
longPress?.let { longPress?.let {
binding.root.setOnLongClickListener { binding.controlContentView.setOnLongClickListener {
Log.d("", "*** Long clicking")
longPress.invoke() longPress.invoke()
true true
} }

@ -552,7 +552,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} else { } else {
showMuteDialog(this) { until -> showMuteDialog(this) { until ->
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
Log.d("", "**** until: $until")
recipientDatabase.setMuted(thread.recipient, until) recipientDatabase.setMuted(thread.recipient, until)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.recyclerView.adapter!!.notifyDataSetChanged() binding.recyclerView.adapter!!.notifyDataSetChanged()

@ -27,7 +27,7 @@
android:visibility="gone" android:visibility="gone"
app:tint="?android:textColorTertiary" app:tint="?android:textColorTertiary"
tools:src="@drawable/ic_timer" tools:src="@drawable/ic_timer"
tools:visibility="visible"/> tools:visibility="visible" />
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView <org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
android:id="@+id/expirationTimerView" android:id="@+id/expirationTimerView"
@ -37,47 +37,57 @@
android:visibility="gone" android:visibility="gone"
app:tint="?android:textColorTertiary" app:tint="?android:textColorTertiary"
tools:src="@drawable/ic_timer" tools:src="@drawable/ic_timer"
tools:visibility="visible"/> tools:visibility="visible" />
<TextView <LinearLayout
android:id="@+id/textView" android:id="@+id/controlContentView"
android:contentDescription="@string/AccessibilityId_control_message"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:textColor="?android:textColorTertiary" android:orientation="vertical">
android:textSize="@dimen/very_small_font_size"
tools:text="You disabled disappearing messages" />
<FrameLayout
android:id="@+id/call_view"
style="@style/CallMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView <TextView
android:id="@+id/call_text_view" android:id="@+id/textView"
android:textColor="?message_received_text_color" android:layout_width="match_parent"
android:textAlignment="center" android:layout_height="wrap_content"
android:layout_gravity="center" android:contentDescription="@string/AccessibilityId_control_message"
android:gravity="center" android:gravity="center"
tools:text="You missed a call" android:textColor="?android:textColorTertiary"
android:textSize="@dimen/very_small_font_size"
tools:text="You disabled disappearing messages" />
<FrameLayout
android:id="@+id/call_view"
style="@style/CallMessage"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
app:drawableStartCompat="@drawable/ic_missed_call" /> android:orientation="horizontal">
</FrameLayout> <TextView
android:id="@+id/call_text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textAlignment="center"
android:textColor="?message_received_text_color"
app:drawableStartCompat="@drawable/ic_missed_call"
tools:text="You missed a call" />
<TextView </FrameLayout>
android:id="@+id/followSetting"
style="@style/Widget.Session.Button.Common.Borderless" <TextView
android:layout_marginTop="4dp" android:id="@+id/followSetting"
android:textColor="@color/accent_green" style="@style/Widget.Session.Button.Common.Borderless"
android:textSize="@dimen/very_small_font_size" android:layout_width="match_parent"
android:text="@string/disappearingMessagesFollowSetting" android:layout_height="wrap_content"
android:contentDescription="@string/AccessibilityId_disappearingMessagesFollowSetting" android:layout_marginTop="4dp"
android:layout_width="match_parent" android:background="@null"
android:layout_height="wrap_content"/> android:contentDescription="@string/AccessibilityId_disappearingMessagesFollowSetting"
android:text="@string/disappearingMessagesFollowSetting"
android:textColor="@color/accent_green"
android:textSize="@dimen/very_small_font_size" />
</LinearLayout>
</LinearLayout> </LinearLayout>
Loading…
Cancel
Save