From bef74130551ec16332cb7c9f65cdcdfc07f12811 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 30 Jun 2021 10:30:10 +1000 Subject: [PATCH] Scroll to message upon tapping quote & fix various bugs --- .../conversation/v2/ConversationActivityV2.kt | 32 +++++++++++++------ .../conversation/v2/ConversationAdapter.kt | 18 +++++++++-- .../v2/dialogs/JoinOpenGroupDialog.kt | 9 ++++-- .../v2/messages/ControlMessageView.kt | 3 ++ .../v2/messages/VisibleMessageContentView.kt | 14 ++++++++ .../v2/messages/VisibleMessageView.kt | 3 ++ .../main/res/layout/expiration_timer_menu.xml | 4 +-- .../main/res/layout/view_control_message.xml | 4 ++- 8 files changed, 71 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 2208265831..a16970e6c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -3,15 +3,14 @@ package org.thoughtcrime.securesms.conversation.v2 import android.Manifest import android.animation.FloatEvaluator import android.animation.ValueAnimator -import android.content.Context import android.content.ClipData import android.content.ClipboardManager +import android.content.Context import android.content.Intent import android.content.res.Resources import android.database.Cursor import android.graphics.Rect import android.graphics.Typeface -import android.os.Bundle import android.net.Uri import android.os.* import android.text.TextUtils @@ -72,6 +71,7 @@ import org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCand 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.VisibleMessageContentViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager import org.thoughtcrime.securesms.database.DatabaseFactory @@ -84,11 +84,11 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState -import org.thoughtcrime.securesms.loki.utilities.ActivityDispatcher -import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity.Companion.selectedContactsKey +import org.thoughtcrime.securesms.loki.utilities.ActivityDispatcher import org.thoughtcrime.securesms.loki.utilities.MentionUtilities +import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaSendActivity @@ -108,7 +108,7 @@ import kotlin.math.* class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate, InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher, - ConversationActionModeCallbackDelegate { + ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate { private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private var linkPreviewViewModel: LinkPreviewViewModel? = null private var threadID: Long = -1 @@ -147,6 +147,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe }, glide ) + adapter.visibleMessageContentViewDelegate = this adapter } @@ -737,6 +738,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe this.previousText = newText } + override fun scrollToMessageIfPossible(timestamp: Long) { + val lastSeenItemPosition = adapter.getItemPositionForTimestamp(timestamp) ?: return + conversationRecyclerView.scrollToPosition(lastSeenItemPosition) + } + override fun sendMessage() { if (thread.isContactRecipient && thread.isBlocked) { BlockedDialog(thread).show(supportFragmentManager, "Blocked Dialog") @@ -905,10 +911,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun startRecordingVoiceMessage() { - showVoiceMessageUI() - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - audioRecorder.startRecording() - stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each + if (Permissions.hasAll(this, Manifest.permission.RECORD_AUDIO)) { + showVoiceMessageUI() + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + audioRecorder.startRecording() + stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each + } else { + Permissions.with(this) + .request(Manifest.permission.RECORD_AUDIO) + .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) + .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) + .execute() + } } override fun sendVoiceMessage() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index c5c0d1e028..6a9273d01f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -8,6 +8,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView.ViewHolder import kotlinx.android.synthetic.main.view_visible_message.view.* import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView +import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.DatabaseFactory @@ -20,6 +21,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr : CursorRecyclerViewAdapter(context, cursor) { private val messageDB = DatabaseFactory.getMmsSmsDatabase(context) var selectedItems = mutableSetOf() + var visibleMessageContentViewDelegate: VisibleMessageContentViewDelegate? = null sealed class ViewType(val rawValue: Int) { object Visible : ViewType(0) @@ -72,6 +74,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr view.onPress = { rawX, rawY -> onItemPress(message, viewHolder.adapterPosition, view, Rect(rawX, rawY, rawX, rawY)) } view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } + view.contentViewDelegate = visibleMessageContentViewDelegate } is ControlMessageViewHolder -> viewHolder.view.bind(message) } @@ -113,8 +116,19 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null for (i in 0 until itemCount) { cursor.moveToPosition(i) - val messageRecord = messageDB.readerFor(cursor).current - if (messageRecord.isOutgoing || messageRecord.dateReceived <= lastSeenTimestamp) { return i } + val message = messageDB.readerFor(cursor).current + if (message.isOutgoing || message.dateReceived <= lastSeenTimestamp) { return i } + } + return null + } + + fun getItemPositionForTimestamp(timestamp: Long): Int? { + val cursor = this.cursor + if (timestamp <= 0L || cursor == null || !isActiveCursor) return null + for (i in 0 until itemCount) { + cursor.moveToPosition(i) + val message = messageDB.readerFor(cursor).current + if (message.dateSent == timestamp) { return i } } return null } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt index 4b89e9d80b..51d85c3651 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt @@ -6,10 +6,12 @@ import android.text.SpannableStringBuilder import android.text.style.StyleSpan import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.dialog_join_open_group.view.* import network.loki.messenger.R import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.utilities.OpenGroupUrlParser +import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog import org.thoughtcrime.securesms.loki.api.OpenGroupManager import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol @@ -33,8 +35,11 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : B private fun join() { val openGroup = OpenGroupUrlParser.parseUrl(url) - OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, requireContext()) - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()) + val activity = requireContext() as AppCompatActivity + ThreadUtils.queue { + OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, activity) + MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(activity) + } dismiss() } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index b4f3810e16..78e926d041 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -32,6 +32,9 @@ class ControlMessageView : LinearLayout { if (message.isExpirationTimerUpdate) { iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_timer, context.theme)) iconImageView.visibility = View.VISIBLE + } else if (message.isMediaSavedNotification) { + iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme)) + iconImageView.visibility = View.VISIBLE } textView.text = message.getDisplayBody(context) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 801a9c6c26..1984de8795 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -23,6 +23,7 @@ import androidx.core.graphics.BlendModeCompat import androidx.core.text.getSpans import androidx.core.text.toSpannable import androidx.core.text.util.LinkifyCompat +import kotlinx.android.synthetic.main.view_link_preview.view.* import kotlinx.android.synthetic.main.view_visible_message_content.view.* import network.loki.messenger.R import org.session.libsession.utilities.ThemeUtil @@ -41,6 +42,7 @@ import kotlin.math.roundToInt class VisibleMessageContentView : LinearLayout { var onContentClick: ((rawRect: Rect) -> Unit)? = null var onContentDoubleTap: (() -> Unit)? = null + var delegate: VisibleMessageContentViewDelegate? = null // region Lifecycle constructor(context: Context) : super(context) { initialize() } @@ -87,6 +89,13 @@ class VisibleMessageContentView : LinearLayout { val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message) ViewUtil.setPaddingTop(bodyTextView, 0) mainContainer.addView(bodyTextView) + onContentClick = { rect -> + val r = Rect() + quoteView.getGlobalVisibleRect(r) + if (r.contains(rect)) { + delegate?.scrollToMessageIfPossible(quote.id) + } + } } else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) { val voiceMessageView = VoiceMessageView(context) voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster) @@ -188,4 +197,9 @@ class VisibleMessageContentView : LinearLayout { } } // endregion +} + +interface VisibleMessageContentViewDelegate { + + fun scrollToMessageIfPossible(timestamp: Long) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 88e4ee9ced..dbcde5c1f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -48,6 +48,7 @@ class VisibleMessageView : LinearLayout { var onPress: ((rawX: Int, rawY: Int) -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null + var contentViewDelegate: VisibleMessageContentViewDelegate? = null companion object { const val swipeToReplyThreshold = 80.0f // dp @@ -139,6 +140,7 @@ class VisibleMessageView : LinearLayout { if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width } // Populate content view messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread) + messageContentView.delegate = contentViewDelegate onDoubleTap = { messageContentView.onContentDoubleTap?.invoke() } } @@ -239,6 +241,7 @@ class VisibleMessageView : LinearLayout { } else { longPressCallback?.let { gestureHandler.removeCallbacks(it) } } + if (translationX > 0) { return } // Only allow swipes to the left // The idea here is to asymptotically approach a maximum drag distance val damping = 50.0f val sign = -1.0f diff --git a/app/src/main/res/layout/expiration_timer_menu.xml b/app/src/main/res/layout/expiration_timer_menu.xml index 9fc7a6091b..aec0b39e69 100644 --- a/app/src/main/res/layout/expiration_timer_menu.xml +++ b/app/src/main/res/layout/expiration_timer_menu.xml @@ -10,8 +10,8 @@ + android:layout_marginBottom="@dimen/small_spacing" + app:tint="@color/text" />