mirror of
				https://github.com/oxen-io/session-android.git
				synced 2025-11-04 01:01:22 +00:00 
			
		
		
		
	Implement a few basic contextual actions
This commit is contained in:
		@@ -1,7 +1,11 @@
 | 
			
		||||
package org.thoughtcrime.securesms.conversation.v2
 | 
			
		||||
 | 
			
		||||
import android.Manifest
 | 
			
		||||
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
 | 
			
		||||
@@ -21,6 +25,7 @@ import androidx.loader.app.LoaderManager
 | 
			
		||||
import androidx.loader.content.Loader
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import com.annimon.stream.Stream
 | 
			
		||||
import kotlinx.android.synthetic.main.activity_conversation_v2.*
 | 
			
		||||
import kotlinx.android.synthetic.main.activity_conversation_v2.view.*
 | 
			
		||||
import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.*
 | 
			
		||||
@@ -34,11 +39,14 @@ import nl.komponents.kovenant.ui.successUi
 | 
			
		||||
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
 | 
			
		||||
@@ -55,6 +63,7 @@ import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarRecordingViewDelegate
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCandidatesView
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
 | 
			
		||||
@@ -72,8 +81,10 @@ import org.thoughtcrime.securesms.mediasend.Media
 | 
			
		||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity
 | 
			
		||||
import org.thoughtcrime.securesms.mms.*
 | 
			
		||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver
 | 
			
		||||
import org.thoughtcrime.securesms.permissions.Permissions
 | 
			
		||||
import org.thoughtcrime.securesms.util.DateUtils
 | 
			
		||||
import org.thoughtcrime.securesms.util.MediaUtil
 | 
			
		||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.concurrent.ExecutionException
 | 
			
		||||
import kotlin.math.*
 | 
			
		||||
@@ -83,7 +94,7 @@ import kotlin.math.*
 | 
			
		||||
// price we pay is a bit of back and forth between the input bar and the conversation activity.
 | 
			
		||||
 | 
			
		||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
 | 
			
		||||
    InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener {
 | 
			
		||||
    InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ConversationActionModeCallbackDelegate {
 | 
			
		||||
    private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
 | 
			
		||||
    private var linkPreviewViewModel: LinkPreviewViewModel? = null
 | 
			
		||||
    private var threadID: Long = -1
 | 
			
		||||
@@ -552,6 +563,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
 | 
			
		||||
        if (actionMode != null) {
 | 
			
		||||
            adapter.toggleSelection(message, position)
 | 
			
		||||
            val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this)
 | 
			
		||||
            actionModeCallback.delegate = this
 | 
			
		||||
            actionModeCallback.updateActionModeMenu(actionMode.menu)
 | 
			
		||||
            if (adapter.selectedItems.isEmpty()) {
 | 
			
		||||
                actionMode.finish()
 | 
			
		||||
@@ -575,6 +587,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
 | 
			
		||||
    private fun handleLongPress(message: MessageRecord, position: Int) {
 | 
			
		||||
        val actionMode = this.actionMode
 | 
			
		||||
        val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this)
 | 
			
		||||
        actionModeCallback.delegate = this
 | 
			
		||||
        if (actionMode == null) { // Nothing should be selected if this is the case
 | 
			
		||||
            adapter.toggleSelection(message, position)
 | 
			
		||||
            this.actionMode = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
 | 
			
		||||
@@ -655,7 +668,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun unblock() {
 | 
			
		||||
        // TODO: Implement
 | 
			
		||||
        if (!thread.isContactRecipient) { return }
 | 
			
		||||
        DatabaseFactory.getRecipientDatabase(this).setBlocked(thread, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleMentionSelected(mention: Mention) {
 | 
			
		||||
@@ -846,6 +860,75 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
 | 
			
		||||
        audioRecorder.stopRecording()
 | 
			
		||||
        stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun deleteMessage(messages: Set<MessageRecord>) {
 | 
			
		||||
        // TODO: Implement
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun banUser(messages: Set<MessageRecord>) {
 | 
			
		||||
        // TODO: Implement
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun copyMessage(messages: Set<MessageRecord>) {
 | 
			
		||||
        // TODO: Implement
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun copySessionID(messages: Set<MessageRecord>) {
 | 
			
		||||
        val sessionID = messages.first().individualRecipient.address.toString()
 | 
			
		||||
        val clip = ClipData.newPlainText("Session ID", sessionID)
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun resendMessage(messages: Set<MessageRecord>) {
 | 
			
		||||
        // TODO: Implement
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun saveAttachment(messages: Set<MessageRecord>) {
 | 
			
		||||
        val message = messages.first() as MmsMessageRecord
 | 
			
		||||
        SaveAttachmentTask.showWarningDialog(this, { dialog: DialogInterface?, which: Int ->
 | 
			
		||||
            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() }
 | 
			
		||||
                .onAllGranted {
 | 
			
		||||
                    val attachments: List<SaveAttachmentTask.Attachment?> = Stream.of<Slide>(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()
 | 
			
		||||
                    if (attachments.isNotEmpty()) {
 | 
			
		||||
                        val saveTask = SaveAttachmentTask(this)
 | 
			
		||||
                        saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, *attachments.toTypedArray())
 | 
			
		||||
                        if (!message.isOutgoing) {
 | 
			
		||||
                            sendMediaSavedNotification()
 | 
			
		||||
                        }
 | 
			
		||||
                        return@onAllGranted
 | 
			
		||||
                    }
 | 
			
		||||
                    Toast.makeText(this,
 | 
			
		||||
                        resources.getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1),
 | 
			
		||||
                        Toast.LENGTH_LONG).show()
 | 
			
		||||
                }
 | 
			
		||||
                .execute()
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun reply(messages: Set<MessageRecord>) {
 | 
			
		||||
        inputBar.draftQuote(messages.first())
 | 
			
		||||
        actionMode?.finish()
 | 
			
		||||
        actionMode = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun sendMediaSavedNotification() {
 | 
			
		||||
        if (thread.isGroupRecipient) { return }
 | 
			
		||||
        val timestamp = System.currentTimeMillis()
 | 
			
		||||
        val kind = DataExtractionNotification.Kind.MediaSaved(timestamp)
 | 
			
		||||
        val message = DataExtractionNotification(kind)
 | 
			
		||||
        MessageSender.send(message, thread.address)
 | 
			
		||||
    }
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    // region General
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
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
 | 
			
		||||
@@ -10,9 +11,11 @@ import org.session.libsession.utilities.TextSecurePreferences
 | 
			
		||||
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapter
 | 
			
		||||
import org.thoughtcrime.securesms.database.DatabaseFactory
 | 
			
		||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
 | 
			
		||||
import org.thoughtcrime.securesms.database.model.MessageRecord
 | 
			
		||||
 | 
			
		||||
class ConversationActionModeCallback(private val adapter: ConversationAdapter, private val threadID: Long,
 | 
			
		||||
    private val context: Context) : ActionMode.Callback {
 | 
			
		||||
    var delegate: ConversationActionModeCallbackDelegate? = null
 | 
			
		||||
 | 
			
		||||
    override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
 | 
			
		||||
        val inflater = mode.menuInflater
 | 
			
		||||
@@ -44,8 +47,6 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
 | 
			
		||||
            if (selectedUsers.size > 1) { return false }
 | 
			
		||||
            return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
 | 
			
		||||
        }
 | 
			
		||||
        // Message info
 | 
			
		||||
        menu.findItem(R.id.menu_context_details).isVisible = (selectedItems.size == 1)
 | 
			
		||||
        // Delete message
 | 
			
		||||
        menu.findItem(R.id.menu_context_delete_message).isVisible = userCanDeleteSelectedItems()
 | 
			
		||||
        // Ban user
 | 
			
		||||
@@ -70,6 +71,16 @@ 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_ban_user -> delegate?.banUser(selectedItems)
 | 
			
		||||
            R.id.menu_context_copy -> delegate?.copyMessage(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)
 | 
			
		||||
            R.id.menu_context_reply -> delegate?.reply(selectedItems)
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -77,4 +88,15 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
 | 
			
		||||
        adapter.selectedItems.clear()
 | 
			
		||||
        adapter.notifyDataSetChanged()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ConversationActionModeCallbackDelegate {
 | 
			
		||||
 | 
			
		||||
    fun deleteMessage(messages: Set<MessageRecord>)
 | 
			
		||||
    fun banUser(messages: Set<MessageRecord>)
 | 
			
		||||
    fun copyMessage(messages: Set<MessageRecord>)
 | 
			
		||||
    fun copySessionID(messages: Set<MessageRecord>)
 | 
			
		||||
    fun resendMessage(messages: Set<MessageRecord>)
 | 
			
		||||
    fun saveAttachment(messages: Set<MessageRecord>)
 | 
			
		||||
    fun reply(messages: Set<MessageRecord>)
 | 
			
		||||
}
 | 
			
		||||
@@ -32,11 +32,6 @@
 | 
			
		||||
        android:id="@+id/menu_context_resend"
 | 
			
		||||
        app:showAsAction="never" />
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:title="@string/conversation_context__menu_message_details"
 | 
			
		||||
        android:id="@+id/menu_context_details"
 | 
			
		||||
        app:showAsAction="never" />
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:title="@string/conversation_context__menu_ban_user"
 | 
			
		||||
        android:id="@+id/menu_context_ban_user"
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ class DataExtractionNotification() : ControlMessage() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal constructor(kind: Kind) : this() {
 | 
			
		||||
    constructor(kind: Kind) : this() {
 | 
			
		||||
        this.kind = kind
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user