Handling deletion od "marked as deleted" messages

pull/1518/head
ThomasSession 6 months ago
parent 331a8f2ac6
commit c50d38e85c

@ -345,7 +345,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
) {
showConversationReaction(message, view)
} else {
handleLongPress(message, position)
selectMessage(message, position)
}
},
onDeselect = { message, position ->
@ -1290,7 +1290,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
// `position` is the adapter position; not the visual position
private fun handleLongPress(message: MessageRecord, position: Int) {
private fun selectMessage(message: MessageRecord, position: Int) {
val actionMode = this.actionMode
val actionModeCallback = ConversationActionModeCallback(adapter, viewModel.threadId, this)
actionModeCallback.delegate = this
@ -1308,15 +1308,20 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
}
private fun showConversationReaction(message: MessageRecord, visibleMessageView: VisibleMessageView) {
private fun showConversationReaction(message: MessageRecord, messageView: View) {
val messageContentView = when(messageView){
is VisibleMessageView -> messageView.messageContentView
else -> messageView
}
val messageContentBitmap = try {
visibleMessageView.messageContentView.drawToBitmap()
messageContentView.drawToBitmap()
} catch (e: Exception) {
Log.e("Loki", "Failed to show emoji picker", e)
return
}
emojiPickerVisible = true
ViewUtil.hideKeyboard(this, visibleMessageView)
ViewUtil.hideKeyboard(this, messageView)
binding.reactionsShade.isVisible = true
binding.scrollToBottomButton.isVisible = false
binding.conversationRecyclerView.suppressLayout(true)
@ -1338,14 +1343,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
})
val topLeft = intArrayOf(0, 0).also { visibleMessageView.messageContentView.getLocationInWindow(it) }
val topLeft = intArrayOf(0, 0).also { messageContentView.getLocationInWindow(it) }
val selectedConversationModel = SelectedConversationModel(
messageContentBitmap,
topLeft[0].toFloat(),
topLeft[1].toFloat(),
visibleMessageView.messageContentView.width,
messageContentView.width,
message.isOutgoing,
visibleMessageView.messageContentView
messageContentView
)
reactionDelegate.show(this, message, selectedConversationModel, viewModel.blindedPublicKey)
}
@ -2056,7 +2061,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
override fun selectMessages(messages: Set<MessageRecord>) {
handleLongPress(messages.first(), 0) //TODO: begin selection mode
selectMessage(messages.first(), 0) //TODO: begin selection mode
}
// Note: The messages in the provided set may be a single message, or multiple if there are a

@ -5,6 +5,7 @@ import android.database.Cursor
import android.util.SparseArray
import android.util.SparseBooleanArray
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.annotation.WorkerThread
import androidx.core.util.getOrDefault
@ -35,7 +36,7 @@ class ConversationAdapter(
private val isReversed: Boolean,
private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit,
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit,
private val onItemLongPress: (MessageRecord, Int, VisibleMessageView) -> Unit,
private val onItemLongPress: (MessageRecord, Int, View) -> Unit,
private val onDeselect: (MessageRecord, Int) -> Unit,
private val onAttachmentNeedsDownload: (DatabaseAttachment) -> Unit,
private val glide: RequestManager,
@ -156,12 +157,18 @@ class ConversationAdapter(
} else {
visibleMessageView.onPress = null
visibleMessageView.onSwipeToReply = null
visibleMessageView.onLongPress = null
// you can long press on "marked as deleted" messages
visibleMessageView.onLongPress =
{ onItemLongPress(message, viewHolder.adapterPosition, visibleMessageView) }
}
}
is ControlMessageViewHolder -> {
viewHolder.view.bind(message, messageBefore)
viewHolder.view.bind(
message = message,
previous = messageBefore,
longPress = { onItemLongPress(message, viewHolder.adapterPosition, viewHolder.view) }
)
}
}
}

@ -21,6 +21,7 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.doOnLayout
import androidx.core.view.isVisible
import androidx.vectordrawable.graphics.drawable.AnimatorInflaterCompat
import com.squareup.phrase.Phrase
import dagger.hilt.android.AndroidEntryPoint
@ -527,18 +528,25 @@ class ConversationReactionOverlay : FrameLayout {
?: return emptyList()
val userPublicKey = getLocalNumber(context)!!
// Select message
items += ActionItem(R.attr.menu_select_icon, R.string.select, { handleActionItemClicked(Action.SELECT) }, R.string.AccessibilityId_select)
if(!message.isDeleted) {
items += ActionItem(
R.attr.menu_select_icon,
R.string.select,
{ handleActionItemClicked(Action.SELECT) },
R.string.AccessibilityId_select
)
}
// Reply
val canWrite = openGroup == null || openGroup.canWrite
if (canWrite && !message.isPending && !message.isFailed && !message.isOpenGroupInvitation) {
if (canWrite && !message.isPending && !message.isFailed && !message.isOpenGroupInvitation && !message.isDeleted) {
items += ActionItem(R.attr.menu_reply_icon, R.string.reply, { handleActionItemClicked(Action.REPLY) }, R.string.AccessibilityId_reply)
}
// Copy message text
if (!containsControlMessage && hasText) {
if (!containsControlMessage && hasText && !message.isDeleted) {
items += ActionItem(R.attr.menu_copy_icon, R.string.copy, { handleActionItemClicked(Action.COPY_MESSAGE) })
}
// Copy Account ID
if (!recipient.isCommunityRecipient && message.isIncoming) {
if (!recipient.isCommunityRecipient && message.isIncoming && !message.isDeleted) {
items += ActionItem(R.attr.menu_copy_icon, R.string.accountIDCopy, { handleActionItemClicked(Action.COPY_ACCOUNT_ID) })
}
// Delete message
@ -547,15 +555,20 @@ class ConversationReactionOverlay : FrameLayout {
R.string.AccessibilityId_deleteMessage, message.subtitle, ThemeUtil.getThemedColor(context, R.attr.danger))
}
// Ban user
if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey)) {
if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey) && !message.isDeleted) {
items += ActionItem(R.attr.menu_block_icon, R.string.banUser, { handleActionItemClicked(Action.BAN_USER) })
}
// Ban and delete all
if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey)) {
if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey) && !message.isDeleted) {
items += ActionItem(R.attr.menu_trash_icon, R.string.banDeleteAll, { handleActionItemClicked(Action.BAN_AND_DELETE_ALL) })
}
// Message detail
items += ActionItem(R.attr.menu_info_icon, R.string.messageInfo, { handleActionItemClicked(Action.VIEW_INFO) })
if(!message.isDeleted) {
items += ActionItem(
R.attr.menu_info_icon,
R.string.messageInfo,
{ handleActionItemClicked(Action.VIEW_INFO) })
}
// Resend
if (message.isFailed) {
items += ActionItem(R.attr.menu_reply_icon, R.string.resend, { handleActionItemClicked(Action.RESEND) })
@ -565,7 +578,7 @@ class ConversationReactionOverlay : FrameLayout {
items += ActionItem(R.attr.menu_reply_icon, R.string.resync, { handleActionItemClicked(Action.RESYNC) })
}
// Save media..
if (message.isMms) {
if (message.isMms && !message.isDeleted) {
// ..but only provide the save option if the there is a media attachment which has finished downloading.
val mmsMessage = message as MediaMmsMessageRecord
if (mmsMessage.containsMediaSlide() && !mmsMessage.isMediaPending) {
@ -576,8 +589,10 @@ class ConversationReactionOverlay : FrameLayout {
)
}
}
backgroundView.visibility = VISIBLE
foregroundView.visibility = VISIBLE
// deleted messages have no emoji reactions
backgroundView.isVisible = !message.isDeleted
foregroundView.isVisible = !message.isDeleted
return items
}

@ -245,12 +245,11 @@ class ConversationViewModel(
val conversationType = conversation.getType()
// hashes are required if wanting to delete messages from the 'storage server' - they are not required for communities
val canDeleteForEveryone = conversationType == MessageType.COMMUNITY || messages.all {
lokiMessageDb.getMessageServerHash(
it.id,
it.isMms
) != null
}
// also we can only delete deleted messages (marked as deleted) locally
val canDeleteForEveryone = messages.all{ !it.isDeleted } && (
conversationType == MessageType.COMMUNITY ||
messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null
})
// There are three types of dialogs for deletion:
// 1- Delete on device only OR all devices - Used for Note to self
@ -290,7 +289,6 @@ class ConversationViewModel(
// for non admins, users interacting with someone else's message, or control messages
else -> {
//todo DELETION this should also happen for ControlMessages
_dialogsState.update {
it.copy(deleteDeviceOnly = messages)
}
@ -300,23 +298,29 @@ class ConversationViewModel(
}
/**
* This will mark the messages as deleted, locally only.
* Attachments and other related data will be removed from the db,
* but the messages themselves won't be removed from the db.
* Instead they will appear as a special type of message
* This delete the message locally only.
* Attachments and other related data will be removed from the db.
* If the messages were already marked as deleted they will be removed fully from the db,
* otherwise they will appear as a special type of message
* that says something like "This message was deleted"
*/
fun markAsDeletedLocally(messages: Set<MessageRecord>) {
fun deletedLocally(messages: Set<MessageRecord>) {
// make sure to stop audio messages, if any
messages.filterIsInstance<MmsMessageRecord>()
.mapNotNull { it.slideDeck.audioSlide }
.forEach(::stopMessageAudio)
repository.markAsDeletedLocally(
messages = messages,
displayedMessage = application.getString(R.string.deleteMessageDeletedLocally)
)
// if the message was already marked as deleted, remove it from the db instead
if(messages.all { it.isDeleted }){
// Remove the message locally (leave nothing behind)
repository.deleteMessages(messages = messages, threadId = threadId)
} else {
// only mark as deleted (message remains behind with "This message was deleted on this device" )
repository.markAsDeletedLocally(
messages = messages,
displayedMessage = application.getString(R.string.deleteMessageDeletedLocally)
)
}
// show confirmation toast
Toast.makeText(
@ -730,7 +734,7 @@ class ConversationViewModel(
it.copy(deleteDeviceOnly = null)
}
markAsDeletedLocally(command.messages)
deletedLocally(command.messages)
}
is Commands.MarkAsDeletedForEveryone -> {
markAsDeletedForEveryone(command.data)

@ -58,7 +58,7 @@ class ControlMessageView : LinearLayout {
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
}
fun bind(message: MessageRecord, previous: MessageRecord?) {
fun bind(message: MessageRecord, previous: MessageRecord?, longPress: (() -> Unit)? = null) {
binding.dateBreakTextView.showDateBreak(message, previous)
binding.iconImageView.isGone = true
binding.expirationTimerView.isGone = true
@ -199,6 +199,17 @@ class ControlMessageView : LinearLayout {
binding.textView.isGone = message.isCallLog
binding.callView.isVisible = message.isCallLog
// 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 {
binding.root.setOnLongClickListener {
Log.d("", "*** Long clicking")
longPress.invoke()
true
}
}
}
fun showInfo(){

@ -485,10 +485,13 @@ class VisibleMessageView : FrameLayout {
// region Interaction
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (onPress == null || onSwipeToReply == null || onLongPress == null) { return false }
if (onPress == null && onSwipeToReply == null && onLongPress == null) { return false }
when (event.action) {
MotionEvent.ACTION_DOWN -> onDown(event)
MotionEvent.ACTION_MOVE -> onMove(event)
MotionEvent.ACTION_MOVE -> {
// only bother with movements if we have swipe to reply
onSwipeToReply?.let { onMove(event) }
}
MotionEvent.ACTION_CANCEL -> onCancel(event)
MotionEvent.ACTION_UP -> onUp(event)
}

Loading…
Cancel
Save