Implement remaining contextual actions

This commit is contained in:
Niels Andriesse 2021-06-29 10:05:39 +10:00
parent 18e345733e
commit 4fc75e5a78
2 changed files with 105 additions and 24 deletions

View File

@ -5,7 +5,6 @@ import android.animation.FloatEvaluator
import android.animation.ValueAnimator
import android.content.ClipData
import android.content.ClipboardManager
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Resources
import android.database.Cursor
@ -13,12 +12,14 @@ import android.graphics.Rect
import android.graphics.Typeface
import android.net.Uri
import android.os.*
import android.text.TextUtils
import android.util.Log
import android.util.Pair
import android.util.TypedValue
import android.view.*
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProviders
import androidx.loader.app.LoaderManager
@ -35,18 +36,18 @@ import kotlinx.android.synthetic.main.view_input_bar.view.*
import kotlinx.android.synthetic.main.view_input_bar_recording.*
import kotlinx.android.synthetic.main.view_input_bar_recording.view.*
import network.loki.messenger.R
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.mentions.Mention
import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.messages.control.DataExtractionNotification
import org.session.libsession.messaging.messages.control.DataExtractionNotification.Kind.MediaSaved
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.MessageSender.send
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
@ -76,6 +77,7 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities
import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivity
@ -117,9 +119,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private val layoutManager: LinearLayoutManager
get() { return conversationRecyclerView.layoutManager as LinearLayoutManager }
// TODO: Selected message background color
// TODO: Overflow menu background + text color
private val adapter by lazy {
val cursor = DatabaseFactory.getMmsSmsDatabase(this).getConversation(threadID)
val adapter = ConversationAdapter(
@ -704,6 +703,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val outgoingTextMessage = OutgoingTextMessage.from(message, thread)
// Clear the input bar
inputBar.text = ""
inputBar.cancelQuoteDraft()
inputBar.cancelLinkPreviewDraft()
// Clear mentions
previousText = ""
currentMentionStartIndex = -1
@ -728,6 +729,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val outgoingTextMessage = OutgoingMediaMessage.from(message, thread, attachments, quote, linkPreview)
// Clear the input bar
inputBar.text = ""
inputBar.cancelQuoteDraft()
inputBar.cancelLinkPreviewDraft()
// Clear mentions
previousText = ""
currentMentionStartIndex = -1
@ -861,16 +864,88 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask)
}
override fun deleteMessage(messages: Set<MessageRecord>) {
// TODO: Implement
override fun deleteMessages(messages: Set<MessageRecord>) {
val messageCount = messages.size
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val messageDB = DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2)
val builder = AlertDialog.Builder(this)
builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
builder.setCancelable(true)
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID)
builder.setPositiveButton(R.string.delete) { _, _ ->
if (openGroup != null) {
val messageServerIDs = mutableMapOf<Long, MessageRecord>()
for (message in messages) {
val messageServerID = messageDB.getServerID(message.id, !message.isMms) ?: continue
messageServerIDs[messageServerID] = message
}
for ((messageServerID, message) in messageServerIDs) {
OpenGroupAPIV2.deleteMessage(messageServerID, openGroup.room, openGroup.server)
.success {
messageDataProvider.deleteMessage(message.id, !message.isMms)
}.failUi { error ->
Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show()
}
}
} else {
for (message in messages) {
if (message.isMms) {
DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id)
} else {
DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id)
}
}
}
endActionMode()
}
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
endActionMode()
}
builder.show()
}
override fun banUser(messages: Set<MessageRecord>) {
// TODO: Implement
val builder = AlertDialog.Builder(this)
val sessionID = messages.first().individualRecipient.address.toString()
builder.setTitle(R.string.ConversationFragment_ban_selected_user)
builder.setMessage("This will ban the selected user from this room. It won't ban them from other rooms. The selected user won't know that they've been banned.")
builder.setCancelable(true)
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID)!!
builder.setPositiveButton(R.string.ban) { _, _ ->
OpenGroupAPIV2.ban(sessionID, openGroup.room, openGroup.server).successUi {
Toast.makeText(this@ConversationActivityV2, "Successfully banned user", Toast.LENGTH_LONG).show()
}.failUi { error ->
Toast.makeText(this@ConversationActivityV2, "Couldn't ban user due to error: $error", Toast.LENGTH_LONG).show()
}
endActionMode()
}
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
endActionMode()
}
builder.show()
}
override fun copyMessage(messages: Set<MessageRecord>) {
// TODO: Implement
override fun copyMessages(messages: Set<MessageRecord>) {
val sortedMessages = messages.sortedBy { it.dateSent }
val builder = StringBuilder()
for (message in sortedMessages) {
val body = MentionUtilities.highlightMentions(message.body, message.threadId, this)
if (TextUtils.isEmpty(body)) { continue }
val formattedTimestamp = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), message.timestamp)
builder.append("$formattedTimestamp: $body").append('\n')
}
if (builder.isNotEmpty() && builder[builder.length - 1] == '\n') {
builder.deleteCharAt(builder.length - 1)
}
val result = builder.toString()
if (TextUtils.isEmpty(result)) { return }
val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
manager.setPrimaryClip(ClipData.newPlainText("Message Content", result))
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
endActionMode()
}
override fun copySessionID(messages: Set<MessageRecord>) {
@ -879,8 +954,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
manager.setPrimaryClip(clip)
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
actionMode?.finish()
actionMode = null
endActionMode()
}
override fun resendMessage(messages: Set<MessageRecord>) {
@ -889,14 +963,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun saveAttachment(messages: Set<MessageRecord>) {
val message = messages.first() as MmsMessageRecord
SaveAttachmentTask.showWarningDialog(this, { dialog: DialogInterface?, which: Int ->
SaveAttachmentTask.showWarningDialog(this, { _, _ ->
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P)
.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.makeText(this@ConversationActivityV2, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show() }
.onAnyDenied {
endActionMode()
Toast.makeText(this@ConversationActivityV2, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()
}
.onAllGranted {
val attachments: List<SaveAttachmentTask.Attachment?> = Stream.of<Slide>(message.slideDeck.slides)
endActionMode()
val attachments: List<SaveAttachmentTask.Attachment?> = Stream.of(message.slideDeck.slides)
.filter { s: Slide -> s.uri != null && (s.hasImage() || s.hasVideo() || s.hasAudio() || s.hasDocument()) }
.map { s: Slide -> SaveAttachmentTask.Attachment(s.uri!!, s.contentType, message.dateReceived, s.fileName.orNull()) }
.toList()
@ -918,8 +996,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun reply(messages: Set<MessageRecord>) {
inputBar.draftQuote(messages.first())
actionMode?.finish()
actionMode = null
endActionMode()
}
private fun sendMediaSavedNotification() {
@ -929,6 +1006,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val message = DataExtractionNotification(kind)
MessageSender.send(message, thread.address)
}
private fun endActionMode() {
actionMode?.finish()
actionMode = null
}
// endregion
// region General

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.conversation.v2.menus
import android.content.Context
import android.util.Log
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
@ -73,9 +72,9 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
val selectedItems = adapter.selectedItems
when (item.itemId) {
R.id.menu_context_delete_message -> delegate?.deleteMessage(selectedItems)
R.id.menu_context_delete_message -> delegate?.deleteMessages(selectedItems)
R.id.menu_context_ban_user -> delegate?.banUser(selectedItems)
R.id.menu_context_copy -> delegate?.copyMessage(selectedItems)
R.id.menu_context_copy -> delegate?.copyMessages(selectedItems)
R.id.menu_context_copy_public_key -> delegate?.copySessionID(selectedItems)
R.id.menu_context_resend -> delegate?.resendMessage(selectedItems)
R.id.menu_context_save_attachment -> delegate?.saveAttachment(selectedItems)
@ -92,9 +91,9 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
interface ConversationActionModeCallbackDelegate {
fun deleteMessage(messages: Set<MessageRecord>)
fun deleteMessages(messages: Set<MessageRecord>)
fun banUser(messages: Set<MessageRecord>)
fun copyMessage(messages: Set<MessageRecord>)
fun copyMessages(messages: Set<MessageRecord>)
fun copySessionID(messages: Set<MessageRecord>)
fun resendMessage(messages: Set<MessageRecord>)
fun saveAttachment(messages: Set<MessageRecord>)